From 24d6ec7beb37eb3a5449f1fa49b4adb123391d24 Mon Sep 17 00:00:00 2001 From: nicolasroard Date: Fri, 12 Jul 2013 18:27:54 -0700 Subject: [PATCH] Add background processing service bug:7298624 Change-Id: Ie79f88fd84fdf8f4dab6a8071f06a819e247b357 --- AndroidManifest.xml | 5 + res/values/filtershow_strings.xml | 5 + src/com/android/gallery3d/data/LocalImage.java | 4 +- .../gallery3d/filtershow/FilterShowActivity.java | 215 +++++++++----------- .../gallery3d/filtershow/cache/ImageLoader.java | 22 ++- .../gallery3d/filtershow/crop/CropActivity.java | 6 +- .../filtershow/filters/BaseFiltersManager.java | 108 +++++++--- .../gallery3d/filtershow/imageshow/ImageShow.java | 4 +- .../filtershow/imageshow/MasterImage.java | 3 + .../filtershow/pipeline/CachingPipeline.java | 4 +- .../filtershow/pipeline/FilteringPipeline.java | 1 - .../gallery3d/filtershow/pipeline/ImagePreset.java | 4 +- .../filtershow/pipeline/ImageSavingTask.java | 123 ++++++++++++ .../filtershow/pipeline/ProcessingService.java | 217 +++++++++++++++++++++ .../filtershow/pipeline/ProcessingTask.java | 72 +++++++ .../pipeline/ProcessingTaskController.java | 98 ++++++++++ .../tools/{SaveCopyTask.java => SaveImage.java} | 109 ++++++----- .../android/gallery3d/util/SaveVideoFileUtils.java | 2 +- .../filtershow/filters/FiltersManager.java | 33 ++-- 19 files changed, 811 insertions(+), 224 deletions(-) create mode 100644 src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java create mode 100644 src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java create mode 100644 src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java create mode 100644 src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java rename src/com/android/gallery3d/filtershow/tools/{SaveCopyTask.java => SaveImage.java} (90%) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 68780f70b..ef1d91459 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -219,6 +219,11 @@ android:grantUriPermissions="true" android:readPermission="com.android.gallery3d.filtershow.permission.READ" android:writePermission="com.android.gallery3d.filtershow.permission.WRITE" /> + + + Result + + Saving Image + + Processing... + diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java index a7c98af77..eb1efae66 100644 --- a/src/com/android/gallery3d/data/LocalImage.java +++ b/src/com/android/gallery3d/data/LocalImage.java @@ -37,7 +37,7 @@ import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.BitmapUtils; import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.exif.ExifTag; -import com.android.gallery3d.filtershow.tools.SaveCopyTask; +import com.android.gallery3d.filtershow.tools.SaveImage; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.ThreadPool.Job; import com.android.gallery3d.util.ThreadPool.JobContext; @@ -270,7 +270,7 @@ public class LocalImage extends LocalMediaItem { GalleryUtils.assertNotInRenderThread(); Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; ContentResolver contentResolver = mApplication.getContentResolver(); - SaveCopyTask.deleteAuxFiles(contentResolver, getContentUri()); + SaveImage.deleteAuxFiles(contentResolver, getContentUri()); contentResolver.delete(baseUri, "_id=?", new String[]{String.valueOf(id)}); } diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index bee764b25..41d178411 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -19,28 +19,29 @@ package com.android.gallery3d.filtershow; import android.app.ActionBar; import android.app.AlertDialog; import android.app.ProgressDialog; +import android.content.ComponentName; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentTransaction; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; -import android.view.Display; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -55,20 +56,19 @@ import android.widget.ShareActionProvider.OnShareTargetSelectedListener; import android.widget.Toast; import com.android.gallery3d.R; +import com.android.gallery3d.app.PhotoPage; import com.android.gallery3d.data.LocalAlbum; import com.android.gallery3d.filtershow.pipeline.CachingPipeline; import com.android.gallery3d.filtershow.pipeline.FilteringPipeline; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.category.Action; import com.android.gallery3d.filtershow.category.CategoryAdapter; -import com.android.gallery3d.filtershow.category.CategoryView; import com.android.gallery3d.filtershow.category.MainPanel; import com.android.gallery3d.filtershow.editors.BasicEditor; import com.android.gallery3d.filtershow.editors.Editor; import com.android.gallery3d.filtershow.editors.EditorCrop; import com.android.gallery3d.filtershow.editors.EditorDraw; import com.android.gallery3d.filtershow.editors.EditorFlip; -import com.android.gallery3d.filtershow.editors.EditorInfo; import com.android.gallery3d.filtershow.editors.EditorManager; import com.android.gallery3d.filtershow.editors.EditorPanel; import com.android.gallery3d.filtershow.editors.EditorRedEye; @@ -76,8 +76,6 @@ import com.android.gallery3d.filtershow.editors.EditorRotate; import com.android.gallery3d.filtershow.editors.EditorStraighten; import com.android.gallery3d.filtershow.editors.EditorTinyPlanet; import com.android.gallery3d.filtershow.editors.ImageOnlyEditor; -import com.android.gallery3d.filtershow.filters.FilterFxRepresentation; -import com.android.gallery3d.filtershow.filters.FilterImageBorderRepresentation; import com.android.gallery3d.filtershow.filters.FilterRepresentation; import com.android.gallery3d.filtershow.filters.FiltersManager; import com.android.gallery3d.filtershow.filters.ImageFilter; @@ -88,9 +86,10 @@ import com.android.gallery3d.filtershow.imageshow.ImageCrop; import com.android.gallery3d.filtershow.imageshow.ImageShow; import com.android.gallery3d.filtershow.imageshow.MasterImage; import com.android.gallery3d.filtershow.pipeline.ImagePreset; +import com.android.gallery3d.filtershow.pipeline.ProcessingService; import com.android.gallery3d.filtershow.provider.SharedImageProvider; import com.android.gallery3d.filtershow.state.StateAdapter; -import com.android.gallery3d.filtershow.tools.SaveCopyTask; +import com.android.gallery3d.filtershow.tools.SaveImage; import com.android.gallery3d.filtershow.tools.XmpPresets; import com.android.gallery3d.filtershow.tools.XmpPresets.XMresults; import com.android.gallery3d.filtershow.ui.FramedTextButton; @@ -101,6 +100,7 @@ import com.android.photos.data.GalleryBitmapPool; import java.io.File; import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.Vector; public class FilterShowActivity extends FragmentActivity implements OnItemClickListener, @@ -149,6 +149,75 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL private CategoryAdapter mCategoryFiltersAdapter = null; private int mCurrentPanel = MainPanel.LOOKS; + private ProcessingService mBoundService; + private boolean mIsBound = false; + + public ProcessingService getProcessingService() { + return mBoundService; + } + + public boolean isSimpleEditAction() { + return !PhotoPage.ACTION_NEXTGEN_EDIT.equalsIgnoreCase(mAction); + } + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + /* + * This is called when the connection with the service has been + * established, giving us the service object we can use to + * interact with the service. Because we have bound to a explicit + * service that we know is running in our own process, we can + * cast its IBinder to a concrete class and directly access it. + */ + mBoundService = ((ProcessingService.LocalBinder)service).getService(); + mBoundService.setFiltershowActivity(FilterShowActivity.this); + mBoundService.onStart(); + } + + public void onServiceDisconnected(ComponentName className) { + /* + * This is called when the connection with the service has been + * unexpectedly disconnected -- that is, its process crashed. + * Because it is running in our same process, we should never + * see this happen. + */ + mBoundService = null; + } + }; + + void doBindService() { + /* + * Establish a connection with the service. We use an explicit + * class name because we want a specific service implementation that + * we know will be running in our own process (and thus won't be + * supporting component replacement by other applications). + */ + bindService(new Intent(FilterShowActivity.this, ProcessingService.class), + mConnection, Context.BIND_AUTO_CREATE); + mIsBound = true; + } + + void doUnbindService() { + if (mIsBound) { + // Detach our existing connection. + unbindService(mConnection); + mIsBound = false; + } + } + + private void setupPipeline() { + doBindService(); + ImageFilter.setActivityForMemoryToasts(this); + } + + public void updateUIAfterServiceStarted() { + fillCategories(); + loadMainPanel(); + setDefaultPreset(); + extractXMPData(); + processIntent(); + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -160,19 +229,13 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL MasterImage.setMaster(mMasterImage); clearGalleryBitmapPool(); + setupPipeline(); - CachingPipeline.createRenderscriptContext(this); setupMasterImage(); setDefaultValues(); fillEditors(); loadXML(); - loadMainPanel(); - - setDefaultPreset(); - - extractXMPData(); - processIntent(); UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_EDITOR, "Main"); UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, UsageStatistics.CATEGORY_LIFECYCLE, UsageStatistics.LIFECYCLE_START); @@ -251,26 +314,25 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL setupEditors(); mEditorPlaceHolder.hide(); - mImageShow.bindAsImageLoadListener(); - fillFx(); - fillBorders(); - fillGeometry(); - fillFilters(); - setupStatePanel(); } + public void fillCategories() { + fillLooks(); + fillBorders(); + fillTools(); + fillEffects(); + } + public void setupStatePanel() { MasterImage.getImage().setHistoryManager(mMasterImage.getHistory()); } - private void fillFilters() { - Vector filtersRepresentations = new Vector(); + private void fillEffects() { FiltersManager filtersManager = FiltersManager.getManager(); - filtersManager.addEffects(filtersRepresentations); - + ArrayList filtersRepresentations = filtersManager.getEffects(); mCategoryFiltersAdapter = new CategoryAdapter(this); for (FilterRepresentation representation : filtersRepresentations) { if (representation.getTextId() != 0) { @@ -280,28 +342,9 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL } } - private void fillGeometry() { - Vector filtersRepresentations = new Vector(); + private void fillTools() { FiltersManager filtersManager = FiltersManager.getManager(); - - GeometryMetadata geo = new GeometryMetadata(); - int[] editorsId = geo.getEditorIds(); - for (int i = 0; i < editorsId.length; i++) { - int editorId = editorsId[i]; - GeometryMetadata geometry = new GeometryMetadata(geo); - geometry.setEditorId(editorId); - EditorInfo editorInfo = (EditorInfo) mEditorPlaceHolder.getEditor(editorId); - geometry.setTextId(editorInfo.getTextId()); - geometry.setOverlayId(editorInfo.getOverlayId()); - geometry.setOverlayOnly(editorInfo.getOverlayOnly()); - if (geometry.getTextId() != 0) { - geometry.setName(getString(geometry.getTextId())); - } - filtersRepresentations.add(geometry); - } - - filtersManager.addTools(filtersRepresentations); - + ArrayList filtersRepresentations = filtersManager.getTools(); mCategoryGeometryAdapter = new CategoryAdapter(this); for (FilterRepresentation representation : filtersRepresentations) { mCategoryGeometryAdapter.add(new Action(this, representation)); @@ -331,7 +374,6 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL mEditorPlaceHolder.setContainer((FrameLayout) findViewById(R.id.editorContainer)); EditorManager.addEditors(mEditorPlaceHolder); mEditorPlaceHolder.setOldViews(mImageViews); - } private void fillEditors() { @@ -347,10 +389,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL } private void setDefaultValues() { - ImageFilter.setActivityForMemoryToasts(this); - Resources res = getResources(); - FiltersManager.setResources(res); // TODO: get those values from XML. FramedTextButton.setTextSize((int) getPixelsFromDip(14)); @@ -379,16 +418,11 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL } private void fillBorders() { - Vector borders = new Vector(); - - // The "no border" implementation - borders.add(new FilterImageBorderRepresentation(0)); - - // Google-build borders - FiltersManager.getManager().addBorders(this, borders); + FiltersManager filtersManager = FiltersManager.getManager(); + ArrayList borders = filtersManager.getBorders(); for (int i = 0; i < borders.size(); i++) { - FilterRepresentation filter = borders.elementAt(i); + FilterRepresentation filter = borders.get(i); filter.setName(getString(R.string.borders)); if (i == 0) { filter.setName(getString(R.string.none)); @@ -628,16 +662,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL if (mLoadBitmapTask != null) { mLoadBitmapTask.cancel(false); } - // TODO: refactor, don't use so many singletons. - FilteringPipeline.getPipeline().turnOnPipeline(false); - MasterImage.reset(); - FilteringPipeline.reset(); - ImageFilter.resetStatics(); - FiltersManager.getPreviewManager().freeRSFilterScripts(); - FiltersManager.getManager().freeRSFilterScripts(); - FiltersManager.getHighresManager().freeRSFilterScripts(); - FiltersManager.reset(); - CachingPipeline.destroyRenderScriptContext(); + doUnbindService(); super.onDestroy(); } @@ -713,7 +738,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setType(SharedImageProvider.MIME_TYPE); - mSharedOutputFile = SaveCopyTask.getNewFile(this, MasterImage.getImage().getUri()); + mSharedOutputFile = SaveImage.getNewFile(this, MasterImage.getImage().getUri()); Uri uri = Uri.withAppendedPath(SharedImageProvider.CONTENT_URI, Uri.encode(mSharedOutputFile.getAbsolutePath())); intent.putExtra(Intent.EXTRA_STREAM, uri); @@ -744,7 +769,6 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL @Override public void onPause() { super.onPause(); - rsPause(); if (mShareActionProvider != null) { mShareActionProvider.setOnShareTargetSelectedListener(null); } @@ -753,48 +777,11 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL @Override public void onResume() { super.onResume(); - rsResume(); if (mShareActionProvider != null) { mShareActionProvider.setOnShareTargetSelectedListener(this); } } - private void rsResume() { - ImageFilter.setActivityForMemoryToasts(this); - MasterImage.setMaster(mMasterImage); - if (CachingPipeline.getRenderScriptContext() == null) { - CachingPipeline.createRenderscriptContext(this); - } - FiltersManager.setResources(getResources()); - if (!mLoading) { - Bitmap largeBitmap = MasterImage.getImage().getOriginalBitmapLarge(); - FilteringPipeline pipeline = FilteringPipeline.getPipeline(); - pipeline.setOriginal(largeBitmap); - float previewScale = (float) largeBitmap.getWidth() / - (float) MasterImage.getImage().getOriginalBounds().width(); - pipeline.setPreviewScaleFactor(previewScale); - Bitmap highresBitmap = MasterImage.getImage().getOriginalBitmapHighres(); - if (highresBitmap != null) { - float highResPreviewScale = (float) highresBitmap.getWidth() / - (float) MasterImage.getImage().getOriginalBounds().width(); - pipeline.setHighResPreviewScaleFactor(highResPreviewScale); - } - pipeline.turnOnPipeline(true); - MasterImage.getImage().setOriginalGeometry(largeBitmap); - } - } - - private void rsPause() { - FilteringPipeline.getPipeline().turnOnPipeline(false); - FilteringPipeline.reset(); - ImageFilter.resetStatics(); - FiltersManager.getPreviewManager().freeRSFilterScripts(); - FiltersManager.getManager().freeRSFilterScripts(); - FiltersManager.getHighresManager().freeRSFilterScripts(); - FiltersManager.reset(); - CachingPipeline.destroyRenderScriptContext(); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -844,16 +831,13 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL } } - private void fillFx() { - FilterFxRepresentation nullFx = - new FilterFxRepresentation(getString(R.string.none), 0, R.string.none); - Vector filtersRepresentations = new Vector(); - FiltersManager.getManager().addLooks(this, filtersRepresentations); + private void fillLooks() { + FiltersManager filtersManager = FiltersManager.getManager(); + ArrayList filtersRepresentations = filtersManager.getLooks(); mCategoryLooksAdapter = new CategoryAdapter(this); int verticalItemHeight = (int) getResources().getDimension(R.dimen.action_item_height); mCategoryLooksAdapter.setItemHeight(verticalItemHeight); - mCategoryLooksAdapter.add(new Action(this, nullFx, Action.FULL_VIEW)); for (FilterRepresentation representation : filtersRepresentations) { mCategoryLooksAdapter.add(new Action(this, representation, Action.FULL_VIEW)); } @@ -1030,7 +1014,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL public void saveImage() { if (mImageShow.hasModifications()) { // Get the name of the album, to which the image will be saved - File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mSelectedImageUri); + File saveDir = SaveImage.getFinalSaveDirectory(this, mSelectedImageUri); int bucketId = GalleryUtils.getBucketId(saveDir.getPath()); String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null); showSavingProgress(albumName); @@ -1063,9 +1047,4 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL return mSelectedImageUri; } - static { - System.loadLibrary("jni_filtershow_filters"); - } - - } diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java index 7c594c67f..b6c72fd9d 100644 --- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java +++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java @@ -18,6 +18,7 @@ package com.android.gallery3d.filtershow.cache; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.database.sqlite.SQLiteException; @@ -33,6 +34,7 @@ import android.webkit.MimeTypeMap; import com.adobe.xmp.XMPException; import com.adobe.xmp.XMPMeta; +import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.filtershow.imageshow.MasterImage; @@ -286,17 +288,20 @@ public final class ImageLoader { * @param uri URI of image to open. * @param context context whose ContentResolver to use. * @param maxSideLength max side length of returned bitmap. - * @param originalBounds set to the actual bounds of the stored bitmap. + * @param originalBounds If not null, set to the actual bounds of the stored bitmap. + * @param useMin use min or max side of the original image * @return downsampled bitmap or null if this operation failed. */ public static Bitmap loadConstrainedBitmap(Uri uri, Context context, int maxSideLength, - Rect originalBounds) { - if (maxSideLength <= 0 || originalBounds == null || uri == null || context == null) { + Rect originalBounds, boolean useMin) { + if (maxSideLength <= 0 || uri == null || context == null) { throw new IllegalArgumentException("bad argument to getScaledBitmap"); } // Get width and height of stored bitmap Rect storedBounds = loadBitmapBounds(context, uri); - originalBounds.set(storedBounds); + if (originalBounds != null) { + originalBounds.set(storedBounds); + } int w = storedBounds.width(); int h = storedBounds.height(); @@ -306,7 +311,12 @@ public final class ImageLoader { } // Find best downsampling size - int imageSide = Math.max(w, h); + int imageSide = 0; + if (useMin) { + imageSide = Math.min(w, h); + } else { + imageSide = Math.max(w, h); + } int sampleSize = 1; while (imageSide > maxSideLength) { imageSide >>>= 1; @@ -336,7 +346,7 @@ public final class ImageLoader { */ public static Bitmap loadOrientedConstrainedBitmap(Uri uri, Context context, int maxSideLength, int orientation, Rect originalBounds) { - Bitmap bmap = loadConstrainedBitmap(uri, context, maxSideLength, originalBounds); + Bitmap bmap = loadConstrainedBitmap(uri, context, maxSideLength, originalBounds, false); if (bmap != null) { bmap = orientBitmap(bmap, orientation); } diff --git a/src/com/android/gallery3d/filtershow/crop/CropActivity.java b/src/com/android/gallery3d/filtershow/crop/CropActivity.java index d14c090fa..0a0c36703 100644 --- a/src/com/android/gallery3d/filtershow/crop/CropActivity.java +++ b/src/com/android/gallery3d/filtershow/crop/CropActivity.java @@ -45,7 +45,7 @@ import android.widget.Toast; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; import com.android.gallery3d.filtershow.cache.ImageLoader; -import com.android.gallery3d.filtershow.tools.SaveCopyTask; +import com.android.gallery3d.filtershow.tools.SaveImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -264,7 +264,7 @@ public class CropActivity extends Activity { protected Bitmap doInBackground(Uri... params) { Uri uri = params[0]; Bitmap bmap = ImageLoader.loadConstrainedBitmap(uri, mContext, mBitmapSize, - mOriginalBounds); + mOriginalBounds, false); mOrientation = ImageLoader.getMetadataRotation(mContext, uri); return bmap; } @@ -299,7 +299,7 @@ public class CropActivity extends Activity { } } if (flags == 0) { - destinationUri = SaveCopyTask.makeAndInsertUri(this, mSourceUri); + destinationUri = SaveImage.makeAndInsertUri(this, mSourceUri); if (destinationUri != null) { flags |= DO_EXTRA_OUTPUT; } diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java index c0a6c139e..1d3a10d7a 100644 --- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java +++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java @@ -20,8 +20,10 @@ import android.content.res.Resources; import android.util.Log; import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; import com.android.gallery3d.filtershow.pipeline.ImagePreset; +import java.util.ArrayList; import java.util.HashMap; import java.util.Vector; @@ -30,6 +32,11 @@ public abstract class BaseFiltersManager implements FiltersManagerInterface { protected HashMap mRepresentationLookup = null; private static final String LOGTAG = "BaseFiltersManager"; + protected ArrayList mLooks = new ArrayList(); + protected ArrayList mBorders = new ArrayList(); + protected ArrayList mTools = new ArrayList(); + protected ArrayList mEffects = new ArrayList(); + protected void init() { mFilters = new HashMap(); mRepresentationLookup = new HashMap(); @@ -133,11 +140,27 @@ public abstract class BaseFiltersManager implements FiltersManagerInterface { filters.add(ImageFilterGeometry.class); } - public void addBorders(Context context, Vector representations) { + public ArrayList getLooks() { + return mLooks; + } + + public ArrayList getBorders() { + return mBorders; + } + + public ArrayList getTools() { + return mTools; + } + public ArrayList getEffects() { + return mEffects; } - public void addLooks(Context context, Vector representations) { + public void addBorders(Context context) { + + } + + public void addLooks(Context context) { int[] drawid = { R.drawable.filtershow_fx_0005_punch, R.drawable.filtershow_fx_0000_vintage, @@ -175,37 +198,72 @@ public abstract class BaseFiltersManager implements FiltersManagerInterface { "LUT3D_XPROCESS" }; + FilterFxRepresentation nullFx = + new FilterFxRepresentation(context.getString(R.string.none), + 0, R.string.none); + mLooks.add(nullFx); + for (int i = 0; i < drawid.length; i++) { FilterFxRepresentation fx = new FilterFxRepresentation( context.getString(fxNameid[i]), drawid[i], fxNameid[i]); fx.setSerializationName(serializationNames[i]); - representations.add(fx); + mLooks.add(fx); addRepresentation(fx); } } - public void addEffects(Vector representations) { - representations.add(getRepresentation(ImageFilterTinyPlanet.class)); - representations.add(getRepresentation(ImageFilterWBalance.class)); - representations.add(getRepresentation(ImageFilterExposure.class)); - representations.add(getRepresentation(ImageFilterVignette.class)); - representations.add(getRepresentation(ImageFilterContrast.class)); - representations.add(getRepresentation(ImageFilterShadows.class)); - representations.add(getRepresentation(ImageFilterHighlights.class)); - representations.add(getRepresentation(ImageFilterVibrance.class)); - representations.add(getRepresentation(ImageFilterSharpen.class)); - representations.add(getRepresentation(ImageFilterCurves.class)); - representations.add(getRepresentation(ImageFilterHue.class)); - representations.add(getRepresentation(ImageFilterSaturated.class)); - representations.add(getRepresentation(ImageFilterBwFilter.class)); - representations.add(getRepresentation(ImageFilterNegative.class)); - representations.add(getRepresentation(ImageFilterEdge.class)); - representations.add(getRepresentation(ImageFilterKMeans.class)); - } - - public void addTools(Vector representations) { - representations.add(getRepresentation(ImageFilterRedEye.class)); - representations.add(getRepresentation(ImageFilterDraw.class)); + public void addEffects() { + mEffects.add(getRepresentation(ImageFilterTinyPlanet.class)); + mEffects.add(getRepresentation(ImageFilterWBalance.class)); + mEffects.add(getRepresentation(ImageFilterExposure.class)); + mEffects.add(getRepresentation(ImageFilterVignette.class)); + mEffects.add(getRepresentation(ImageFilterContrast.class)); + mEffects.add(getRepresentation(ImageFilterShadows.class)); + mEffects.add(getRepresentation(ImageFilterHighlights.class)); + mEffects.add(getRepresentation(ImageFilterVibrance.class)); + mEffects.add(getRepresentation(ImageFilterSharpen.class)); + mEffects.add(getRepresentation(ImageFilterCurves.class)); + mEffects.add(getRepresentation(ImageFilterHue.class)); + mEffects.add(getRepresentation(ImageFilterSaturated.class)); + mEffects.add(getRepresentation(ImageFilterBwFilter.class)); + mEffects.add(getRepresentation(ImageFilterNegative.class)); + mEffects.add(getRepresentation(ImageFilterEdge.class)); + mEffects.add(getRepresentation(ImageFilterKMeans.class)); + } + + public void addTools(Context context) { + GeometryMetadata geo = new GeometryMetadata(); + int[] editorsId = geo.getEditorIds(); + + int[] textId = { + R.string.crop, + R.string.straighten, + R.string.rotate, + R.string.mirror + }; + + int[] overlayId = { + R.drawable.filtershow_button_geometry_crop, + R.drawable.filtershow_button_geometry_straighten, + R.drawable.filtershow_button_geometry_rotate, + R.drawable.filtershow_button_geometry_flip + }; + + for (int i = 0; i < editorsId.length; i++) { + int editorId = editorsId[i]; + GeometryMetadata geometry = new GeometryMetadata(geo); + geometry.setEditorId(editorId); + geometry.setTextId(textId[i]); + geometry.setOverlayId(overlayId[i]); + geometry.setOverlayOnly(true); + if (geometry.getTextId() != 0) { + geometry.setName(context.getString(geometry.getTextId())); + } + mTools.add(geometry); + } + + mTools.add(getRepresentation(ImageFilterRedEye.class)); + mTools.add(getRepresentation(ImageFilterDraw.class)); } public void setFilterResources(Resources resources) { diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java index 2da358c2c..1a304db69 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java @@ -40,7 +40,7 @@ import com.android.gallery3d.filtershow.FilterShowActivity; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.filters.ImageFilter; import com.android.gallery3d.filtershow.pipeline.ImagePreset; -import com.android.gallery3d.filtershow.tools.SaveCopyTask; +import com.android.gallery3d.filtershow.tools.SaveImage; import java.io.File; @@ -395,7 +395,7 @@ public class ImageShow extends View implements OnGestureListener, } public void saveImage(FilterShowActivity filterShowActivity, File file) { - SaveCopyTask.saveImage(getImagePreset(), filterShowActivity, file); + SaveImage.saveImage(getImagePreset(), filterShowActivity, file); } diff --git a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java index ed09fb116..01fe3c159 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java +++ b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java @@ -482,6 +482,9 @@ public class MasterImage implements RenderingRequestCaller { } public void needsUpdatePartialPreview() { + if (mPreset == null) { + return; + } if (!mPreset.canDoPartialRendering()) { invalidatePartialPreview(); return; diff --git a/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java b/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java index a7580a835..535d02f0b 100644 --- a/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java +++ b/src/com/android/gallery3d/filtershow/pipeline/CachingPipeline.java @@ -16,7 +16,7 @@ package com.android.gallery3d.filtershow.pipeline; -import android.app.Activity; +import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.support.v8.renderscript.Allocation; @@ -68,7 +68,7 @@ public class CachingPipeline implements PipelineInterface { return sRS; } - public static synchronized void createRenderscriptContext(Activity context) { + public static synchronized void createRenderscriptContext(Context context) { if (sRS != null) { Log.w(LOGTAG, "A prior RS context exists when calling setRenderScriptContext"); destroyRenderScriptContext(); diff --git a/src/com/android/gallery3d/filtershow/pipeline/FilteringPipeline.java b/src/com/android/gallery3d/filtershow/pipeline/FilteringPipeline.java index a302b1907..0e9b83d7f 100644 --- a/src/com/android/gallery3d/filtershow/pipeline/FilteringPipeline.java +++ b/src/com/android/gallery3d/filtershow/pipeline/FilteringPipeline.java @@ -21,7 +21,6 @@ import android.os.*; import android.os.Process; import android.util.Log; -import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.filters.FiltersManager; import com.android.gallery3d.filtershow.imageshow.MasterImage; diff --git a/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java b/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java index 78a4d211d..2b9e3701f 100644 --- a/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java +++ b/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java @@ -55,7 +55,7 @@ public class ImagePreset { private boolean mPartialRendering = false; private Rect mPartialRenderingBounds; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; public ImagePreset() { } @@ -607,7 +607,7 @@ public class ImagePreset { if (DEBUG) { Log.v(LOGTAG, "Serialization: " + sname); if (sname == null) { - Log.v(LOGTAG, "Serialization: " + filter); + Log.v(LOGTAG, "Serialization name null for filter: " + filter); } } writer.name(sname); diff --git a/src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java b/src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java new file mode 100644 index 000000000..e93ec1687 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/ImageSavingTask.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.filtershow.pipeline; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.net.Uri; +import com.android.gallery3d.filtershow.cache.ImageLoader; +import com.android.gallery3d.filtershow.filters.FiltersManager; +import com.android.gallery3d.filtershow.tools.SaveImage; + +import java.io.File; + +public class ImageSavingTask extends ProcessingTask { + private ProcessingService mProcessingService; + + static class SaveRequest implements Request { + Uri sourceUri; + Uri selectedUri; + File destinationFile; + ImagePreset preset; + } + + static class UpdateBitmap implements Update { + Bitmap bitmap; + } + + static class UpdateProgress implements Update { + int max; + int current; + } + + static class URIResult implements Result { + Uri uri; + } + + public ImageSavingTask(ProcessingService service) { + mProcessingService = service; + } + + public void saveImage(Uri sourceUri, Uri selectedUri, + File destinationFile, ImagePreset preset) { + SaveRequest request = new SaveRequest(); + request.sourceUri = sourceUri; + request.selectedUri = selectedUri; + request.destinationFile = destinationFile; + request.preset = preset; + postRequest(request); + } + + public Result doInBackground(Request message) { + SaveRequest request = (SaveRequest) message; + Uri sourceUri = request.sourceUri; + Uri selectedUri = request.selectedUri; + File destinationFile = request.destinationFile; + ImagePreset preset = request.preset; + + // We create a small bitmap showing the result that we can + // give to the notification + UpdateBitmap updateBitmap = new UpdateBitmap(); + updateBitmap.bitmap = createNotificationBitmap(sourceUri, preset); + postUpdate(updateBitmap); + + SaveImage saveImage = new SaveImage(mProcessingService, sourceUri, + selectedUri, destinationFile, + new SaveImage.Callback() { + @Override + public void onProgress(int max, int current) { + UpdateProgress updateProgress = new UpdateProgress(); + updateProgress.max = max; + updateProgress.current = current; + postUpdate(updateProgress); + } + }); + + Uri uri = saveImage.processAndSaveImage(preset); + URIResult result = new URIResult(); + result.uri = uri; + return result; + } + + @Override + public void onResult(Result message) { + URIResult result = (URIResult) message; + mProcessingService.completeSaveImage(result.uri); + } + + @Override + public void onUpdate(Update message) { + if (message instanceof UpdateBitmap) { + Bitmap bitmap = ((UpdateBitmap) message).bitmap; + mProcessingService.updateNotificationWithBitmap(bitmap); + } + if (message instanceof UpdateProgress) { + UpdateProgress progress = (UpdateProgress) message; + mProcessingService.updateProgress(progress.max, progress.current); + } + } + + private Bitmap createNotificationBitmap(Uri sourceUri, ImagePreset preset) { + int notificationBitmapSize = Resources.getSystem().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width); + Bitmap bitmap = ImageLoader.loadConstrainedBitmap(sourceUri, getContext(), + notificationBitmapSize, null, true); + CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(), "Thumb"); + return pipeline.renderFinalImage(bitmap, preset); + } + +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java b/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java new file mode 100644 index 000000000..032024746 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/ProcessingService.java @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.filtershow.pipeline; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Binder; +import android.os.IBinder; +import android.util.Log; +import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.filters.FiltersManager; +import com.android.gallery3d.filtershow.filters.ImageFilter; +import com.android.gallery3d.filtershow.tools.SaveImage; + +import java.io.File; + +public class ProcessingService extends Service { + private static final String LOGTAG = "ProcessingService"; + private static final boolean SHOW_IMAGE = false; + private int mNotificationId; + private NotificationManager mNotifyMgr = null; + private Notification.Builder mBuilder = null; + + private static final String PRESET = "preset"; + private static final String SOURCE_URI = "sourceUri"; + private static final String SELECTED_URI = "selectedUri"; + private static final String DESTINATION_FILE = "destinationFile"; + private static final String SAVING = "saving"; + + private ProcessingTaskController mProcessingTaskController; + private ImageSavingTask mImageSavingTask; + + private final IBinder mBinder = new LocalBinder(); + private FilterShowActivity mFiltershowActivity; + + private boolean mSaving = false; + private boolean mNeedsAlive = false; + + public void setFiltershowActivity(FilterShowActivity filtershowActivity) { + mFiltershowActivity = filtershowActivity; + } + + public class LocalBinder extends Binder { + public ProcessingService getService() { + return ProcessingService.this; + } + } + + public static Intent getSaveIntent(Context context, ImagePreset preset, File destination, + Uri selectedImageUri, Uri sourceImageUri) { + Intent processIntent = new Intent(context, ProcessingService.class); + processIntent.putExtra(ProcessingService.SOURCE_URI, + sourceImageUri.toString()); + processIntent.putExtra(ProcessingService.SELECTED_URI, + selectedImageUri.toString()); + if (destination != null) { + processIntent.putExtra(ProcessingService.DESTINATION_FILE, destination.toString()); + } + processIntent.putExtra(ProcessingService.PRESET, + preset.getJsonString(context.getString(R.string.saved))); + processIntent.putExtra(ProcessingService.SAVING, true); + return processIntent; + } + + + @Override + public void onCreate() { + mProcessingTaskController = new ProcessingTaskController(this); + mImageSavingTask = new ImageSavingTask(this); + mProcessingTaskController.add(mImageSavingTask); + setupPipeline(); + } + + @Override + public void onDestroy() { + tearDownPipeline(); + mProcessingTaskController.quit(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + mNeedsAlive = true; + if (intent != null && intent.getBooleanExtra(SAVING, false)) { + // we save using an intent to keep the service around after the + // activity has been destroyed. + String presetJson = intent.getStringExtra(PRESET); + String source = intent.getStringExtra(SOURCE_URI); + String selected = intent.getStringExtra(SELECTED_URI); + String destination = intent.getStringExtra(DESTINATION_FILE); + Uri sourceUri = Uri.parse(source); + Uri selectedUri = null; + if (selected != null) { + selectedUri = Uri.parse(selected); + } + File destinationFile = null; + if (destination != null) { + destinationFile = new File(destination); + } + ImagePreset preset = new ImagePreset(); + preset.readJsonFromString(presetJson); + mNeedsAlive = false; + mSaving = true; + handleSaveRequest(sourceUri, selectedUri, destinationFile, preset); + } + return START_REDELIVER_INTENT; + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + public void onStart() { + mNeedsAlive = true; + if (!mSaving && mFiltershowActivity != null) { + mFiltershowActivity.updateUIAfterServiceStarted(); + } + } + + public void handleSaveRequest(Uri sourceUri, Uri selectedUri, + File destinationFile, ImagePreset preset) { + mNotifyMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + mNotificationId++; + + mBuilder = + new Notification.Builder(this) + .setSmallIcon(R.drawable.filtershow_button_fx) + .setContentTitle(getString(R.string.filtershow_notification_label)) + .setContentText(getString(R.string.filtershow_notification_message)); + + startForeground(mNotificationId, mBuilder.build()); + + updateProgress(SaveImage.MAX_PROCESSING_STEPS, 0); + + // Process the image + + mImageSavingTask.saveImage(sourceUri, selectedUri, destinationFile, preset); + } + + public void updateNotificationWithBitmap(Bitmap bitmap) { + mBuilder.setLargeIcon(bitmap); + mNotifyMgr.notify(mNotificationId, mBuilder.build()); + } + + public void updateProgress(int max, int current) { + mBuilder.setProgress(max, current, false); + mNotifyMgr.notify(mNotificationId, mBuilder.build()); + } + + public void completeSaveImage(Uri result) { + if (SHOW_IMAGE) { + // TODO: we should update the existing image in Gallery instead + Intent viewImage = new Intent(Intent.ACTION_VIEW, result); + viewImage.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(viewImage); + } + stopForeground(true); + stopSelf(); + if (mNeedsAlive) { + // If the app has been restarted while we were saving... + mFiltershowActivity.updateUIAfterServiceStarted(); + } else if (mFiltershowActivity.isSimpleEditAction()) { + // terminate now + mFiltershowActivity.completeSaveImage(result); + } + } + + private void setupPipeline() { + Resources res = getResources(); + FiltersManager.setResources(res); + CachingPipeline.createRenderscriptContext(this); + + FiltersManager filtersManager = FiltersManager.getManager(); + filtersManager.addLooks(this); + filtersManager.addBorders(this); + filtersManager.addTools(this); + filtersManager.addEffects(); + } + + private void tearDownPipeline() { + FilteringPipeline.getPipeline().turnOnPipeline(false); + FilteringPipeline.reset(); + ImageFilter.resetStatics(); + FiltersManager.getPreviewManager().freeRSFilterScripts(); + FiltersManager.getManager().freeRSFilterScripts(); + FiltersManager.getHighresManager().freeRSFilterScripts(); + FiltersManager.reset(); + CachingPipeline.destroyRenderScriptContext(); + } + + static { + System.loadLibrary("jni_filtershow_filters"); + } +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java new file mode 100644 index 000000000..c3687ee6a --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTask.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.filtershow.pipeline; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; + +public abstract class ProcessingTask { + private ProcessingTaskController mTaskController; + private Handler mProcessingHandler; + private Handler mResultHandler; + private int mType; + + static interface Request {} + static interface Update {} + static interface Result {} + + public void postRequest(Request message) { + Message msg = mProcessingHandler.obtainMessage(mType); + msg.obj = message; + mProcessingHandler.sendMessage(msg); + } + + public void postUpdate(Update message) { + Message msg = mResultHandler.obtainMessage(mType); + msg.obj = message; + msg.arg1 = ProcessingTaskController.UPDATE; + mResultHandler.sendMessage(msg); + } + + public void processRequest(Request message) { + Object result = doInBackground(message); + Message msg = mResultHandler.obtainMessage(mType); + msg.obj = result; + msg.arg1 = ProcessingTaskController.RESULT; + mResultHandler.sendMessage(msg); + } + + public void added(ProcessingTaskController taskController) { + mTaskController = taskController; + mResultHandler = taskController.getResultHandler(); + mProcessingHandler = taskController.getProcessingHandler(); + mType = taskController.getReservedType(); + } + + public int getType() { + return mType; + } + + public Context getContext() { + return mTaskController.getContext(); + } + + public abstract Result doInBackground(Request message); + public abstract void onResult(Result message); + public void onUpdate(Update message) {} +} diff --git a/src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java new file mode 100644 index 000000000..218ea630a --- /dev/null +++ b/src/com/android/gallery3d/filtershow/pipeline/ProcessingTaskController.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.filtershow.pipeline; + +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; + +import java.util.HashMap; + +public class ProcessingTaskController implements Handler.Callback { + private static final String LOGTAG = "ProcessingTaskController"; + + private Context mContext; + private HandlerThread mHandlerThread = null; + private Handler mProcessingHandler = null; + private int mCurrentType; + private HashMap mTasks = new HashMap(); + + public final static int RESULT = 1; + public final static int UPDATE = 2; + + private final Handler mResultHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + ProcessingTask task = mTasks.get(msg.what); + if (task != null) { + if (msg.arg1 == RESULT) { + task.onResult((ProcessingTask.Result) msg.obj); + } else if (msg.arg1 == UPDATE) { + task.onUpdate((ProcessingTask.Update) msg.obj); + } else { + Log.w(LOGTAG, "received unknown message! " + msg.arg1); + } + } + } + }; + + @Override + public boolean handleMessage(Message msg) { + ProcessingTask task = mTasks.get(msg.what); + if (task != null) { + task.processRequest((ProcessingTask.Request) msg.obj); + return true; + } + return false; + } + + public ProcessingTaskController(Context context) { + mContext = context; + mHandlerThread = new HandlerThread("ProcessingTaskController", + android.os.Process.THREAD_PRIORITY_FOREGROUND); + mHandlerThread.start(); + mProcessingHandler = new Handler(mHandlerThread.getLooper(), this); + } + + public Handler getProcessingHandler() { + return mProcessingHandler; + } + + public Handler getResultHandler() { + return mResultHandler; + } + + public int getReservedType() { + return mCurrentType++; + } + + public Context getContext() { + return mContext; + } + + public void add(ProcessingTask task) { + task.added(this); + mTasks.put(task.getType(), task); + } + + public void quit() { + mHandlerThread.quit(); + } +} diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveImage.java similarity index 90% rename from src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java rename to src/com/android/gallery3d/filtershow/tools/SaveImage.java index dcf0ae166..9b13af13d 100644 --- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java +++ b/src/com/android/gallery3d/filtershow/tools/SaveImage.java @@ -19,18 +19,18 @@ package com.android.gallery3d.filtershow.tools; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.net.Uri; -import android.os.AsyncTask; import android.os.Environment; import android.provider.MediaStore; import android.provider.MediaStore.Images; import android.provider.MediaStore.Images.ImageColumns; -import android.provider.MediaStore.Images.Media; import android.util.Log; +import com.android.gallery3d.R; +import com.android.gallery3d.app.PhotoPage; import com.android.gallery3d.common.Utils; import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.filtershow.FilterShowActivity; @@ -39,6 +39,7 @@ import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.filters.FiltersManager; import com.android.gallery3d.filtershow.imageshow.MasterImage; import com.android.gallery3d.filtershow.pipeline.ImagePreset; +import com.android.gallery3d.filtershow.pipeline.ProcessingService; import com.android.gallery3d.util.UsageStatistics; import com.android.gallery3d.util.XmpUtilHelper; @@ -52,17 +53,16 @@ import java.text.SimpleDateFormat; import java.util.TimeZone; /** - * Asynchronous task for saving edited photo as a new copy. + * Handles saving edited photo */ -public class SaveCopyTask extends AsyncTask { - - private static final String LOGTAG = "SaveCopyTask"; +public class SaveImage { + private static final String LOGTAG = "SaveImage"; /** - * Callback for the completed asynchronous task. + * Callback for updates */ public interface Callback { - void onComplete(Uri result); + void onProgress(int max, int current); } public interface ContentResolverQueryCallback { @@ -85,6 +85,10 @@ public class SaveCopyTask extends AsyncTask { private final Callback mCallback; private final File mDestinationFile; private final Uri mSelectedImageUri; + + private int mCurrentProcessingStep = 1; + + public static final int MAX_PROCESSING_STEPS = 6; public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; // In order to support the new edit-save behavior such that user won't see @@ -129,8 +133,8 @@ public class SaveCopyTask extends AsyncTask { * @param callback Let the caller know the saving has completed. * @return the newSourceUri */ - public SaveCopyTask(Context context, Uri sourceUri, Uri selectedImageUri, - File destination, Callback callback) { + public SaveImage(Context context, Uri sourceUri, Uri selectedImageUri, + File destination, Callback callback) { mContext = context; mSourceUri = sourceUri; mCallback = callback; @@ -144,10 +148,10 @@ public class SaveCopyTask extends AsyncTask { } public static File getFinalSaveDirectory(Context context, Uri sourceUri) { - File saveDirectory = SaveCopyTask.getSaveDirectory(context, sourceUri); + File saveDirectory = SaveImage.getSaveDirectory(context, sourceUri); if ((saveDirectory == null) || !saveDirectory.canWrite()) { saveDirectory = new File(Environment.getExternalStorageDirectory(), - SaveCopyTask.DEFAULT_SAVE_DIRECTORY); + SaveImage.DEFAULT_SAVE_DIRECTORY); } // Create the directory if it doesn't exist if (!saveDirectory.exists()) @@ -275,17 +279,7 @@ public class SaveCopyTask extends AsyncTask { return ret; } - /** - * The task should be executed with one given bitmap to be saved. - */ - @Override - protected Uri doInBackground(ImagePreset... params) { - // TODO: Support larger dimensions for photo saving. - if (params[0] == null || mSourceUri == null || mSelectedImageUri == null) { - return null; - } - - ImagePreset preset = params[0]; + private Uri resetToOriginalImageIfNeeded(ImagePreset preset) { Uri uri = null; if (!preset.hasModifications()) { // This can happen only when preset has no modification but save @@ -298,12 +292,32 @@ public class SaveCopyTask extends AsyncTask { // create a local copy as usual. if (srcFile != null) { srcFile.renameTo(mDestinationFile); - uri = SaveCopyTask.insertContent(mContext, mSelectedImageUri, mDestinationFile, + uri = SaveImage.insertContent(mContext, mSelectedImageUri, mDestinationFile, System.currentTimeMillis()); removeSelectedImage(); - return uri; } } + return uri; + } + + private void resetProgress() { + mCurrentProcessingStep = 0; + } + + private void updateProgress() { + if (mCallback != null) { + mCallback.onProgress(MAX_PROCESSING_STEPS, ++mCurrentProcessingStep); + } + } + + public Uri processAndSaveImage(ImagePreset preset) { + + Uri uri = resetToOriginalImageIfNeeded(preset); + if (uri != null) { + return null; + } + + resetProgress(); boolean noBitmap = true; int num_tries = 0; @@ -321,15 +335,19 @@ public class SaveCopyTask extends AsyncTask { // Stopgap fix for low-memory devices. while (noBitmap) { try { + updateProgress(); // Try to do bitmap operations, downsample if low-memory Bitmap bitmap = ImageLoader.loadOrientedBitmapWithBackouts(mContext, newSourceUri, sampleSize); if (bitmap == null) { return null; } + updateProgress(); CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(), "Saving"); + bitmap = pipeline.renderFinalImage(bitmap, preset); + updateProgress(); Object xmp = getPanoramaXMPData(mSelectedImageUri, preset); ExifInterface exif = getExifData(mSelectedImageUri); @@ -347,28 +365,34 @@ public class SaveCopyTask extends AsyncTask { // If we succeed in writing the bitmap as a jpeg, return a uri. if (putExifData(mDestinationFile, exif, bitmap)) { putPanoramaXMPData(mDestinationFile, xmp); - uri = SaveCopyTask.insertContent(mContext, mSelectedImageUri, mDestinationFile, + uri = SaveImage.insertContent(mContext, mSelectedImageUri, mDestinationFile, time); } + updateProgress(); // mDestinationFile will save the newSourceUri info in the XMP. XmpPresets.writeFilterXMP(mContext, newSourceUri, mDestinationFile, preset); + updateProgress(); // Since we have a new image inserted to media store, we can // safely remove the old one which is selected by the user. + // TODO: we should fix that, do an update instead of insert+remove, + // as well as asking Gallery to update its cached version of the image if (USE_AUX_DIR) { removeSelectedImage(); } + updateProgress(); noBitmap = false; UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, "SaveComplete", null); - } catch (java.lang.OutOfMemoryError e) { + } catch (OutOfMemoryError e) { // Try 5 times before failing for good. if (++num_tries >= 5) { throw e; } System.gc(); sampleSize *= 2; + resetProgress(); } } return uri; @@ -434,13 +458,6 @@ public class SaveCopyTask extends AsyncTask { return auxDiretory; } - @Override - protected void onPostExecute(Uri result) { - if (mCallback != null) { - mCallback.onComplete(result); - } - } - public static Uri makeAndInsertUri(Context context, Uri sourceUri) { long time = System.currentTimeMillis(); String filename = new SimpleDateFormat(TIME_STAMP_NAME).format(new Date(time)); @@ -452,18 +469,18 @@ public class SaveCopyTask extends AsyncTask { public static void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity, File destination) { Uri selectedImageUri = filterShowActivity.getSelectedImageUri(); - new SaveCopyTask(filterShowActivity, MasterImage.getImage().getUri(), selectedImageUri, - destination, - new SaveCopyTask.Callback() { + Uri sourceImageUri = MasterImage.getImage().getUri(); - @Override - public void onComplete(Uri result) { - filterShowActivity.completeSaveImage(result); - } + Intent processIntent = ProcessingService.getSaveIntent(filterShowActivity, preset, + destination, selectedImageUri, sourceImageUri); - }).execute(preset); - } + filterShowActivity.startService(processIntent); + if (!filterShowActivity.isSimpleEditAction()) { + // terminate for now + filterShowActivity.completeSaveImage(selectedImageUri); + } + } public static void querySource(Context context, Uri sourceUri, String[] projection, ContentResolverQueryCallback callback) { @@ -586,8 +603,8 @@ public class SaveCopyTask extends AsyncTask { ImageColumns.DATE_TAKEN, ImageColumns.LATITUDE, ImageColumns.LONGITUDE, }; - SaveCopyTask.querySource(context, sourceUri, projection, - new SaveCopyTask.ContentResolverQueryCallback() { + SaveImage.querySource(context, sourceUri, projection, + new SaveImage.ContentResolverQueryCallback() { @Override public void onCursorResult(Cursor cursor) { diff --git a/src/com/android/gallery3d/util/SaveVideoFileUtils.java b/src/com/android/gallery3d/util/SaveVideoFileUtils.java index da0970b1d..10c41de90 100644 --- a/src/com/android/gallery3d/util/SaveVideoFileUtils.java +++ b/src/com/android/gallery3d/util/SaveVideoFileUtils.java @@ -25,7 +25,7 @@ import android.os.Environment; import android.provider.MediaStore.Video; import android.provider.MediaStore.Video.VideoColumns; -import com.android.gallery3d.filtershow.tools.SaveCopyTask.ContentResolverQueryCallback; +import com.android.gallery3d.filtershow.tools.SaveImage.ContentResolverQueryCallback; import java.io.File; import java.sql.Date; diff --git a/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java b/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java index c5a6435aa..d4035cdce 100644 --- a/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java +++ b/src_pd/com/android/gallery3d/filtershow/filters/FiltersManager.java @@ -22,9 +22,6 @@ import android.graphics.Color; import com.android.gallery3d.R; -import java.util.HashMap; -import java.util.Vector; - public class FiltersManager extends BaseFiltersManager { private static FiltersManager sInstance = null; private static FiltersManager sPreviewInstance = null; @@ -49,7 +46,7 @@ public class FiltersManager extends BaseFiltersManager { } @Override - public void addBorders(Context context, Vector representations) { + public void addBorders(Context context) { // Do not localize String[] serializationNames = { @@ -66,55 +63,59 @@ public class FiltersManager extends BaseFiltersManager { "FRAME_CREAM_ROUNDED" }; + // The "no border" implementation int i = 0; + FilterRepresentation rep = new FilterImageBorderRepresentation(0); + mBorders.add(rep); + // Regular borders - FilterRepresentation rep = new FilterImageBorderRepresentation(R.drawable.filtershow_border_4x5); + rep = new FilterImageBorderRepresentation(R.drawable.filtershow_border_4x5); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); rep = new FilterImageBorderRepresentation(R.drawable.filtershow_border_brush); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); rep = new FilterImageBorderRepresentation(R.drawable.filtershow_border_grunge); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); rep = new FilterImageBorderRepresentation(R.drawable.filtershow_border_sumi_e); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); rep = new FilterImageBorderRepresentation(R.drawable.filtershow_border_tape); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); rep = new FilterColorBorderRepresentation(Color.BLACK, mImageBorderSize, 0); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); rep = new FilterColorBorderRepresentation(Color.BLACK, mImageBorderSize, mImageBorderSize); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); rep = new FilterColorBorderRepresentation(Color.WHITE, mImageBorderSize, 0); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); rep = new FilterColorBorderRepresentation(Color.WHITE, mImageBorderSize, mImageBorderSize); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); int creamColor = Color.argb(255, 237, 237, 227); rep = new FilterColorBorderRepresentation(creamColor, mImageBorderSize, 0); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); rep = new FilterColorBorderRepresentation(creamColor, mImageBorderSize, mImageBorderSize); rep.setSerializationName(serializationNames[i++]); - representations.add(rep); + mBorders.add(rep); } public static FiltersManager getHighresManager() { -- 2.11.0