am 7e11af8f: Reconcile with ics-mr1-release

* commit '7e11af8f5813972dd64032e70068e10f87771234':
diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java
index 6fb4143..fbe4533 100644
--- a/src/com/android/gallery3d/app/AlbumPage.java
+++ b/src/com/android/gallery3d/app/AlbumPage.java
@@ -349,8 +349,10 @@
         if (mSyncTask != null) {
             mSyncTask.cancel();
             mSyncTask = null;
+            clearLoadingBit(BIT_LOADING_SYNC);
         }
         mActionModeHandler.pause();
+        GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
     }
 
     @Override
@@ -572,9 +574,8 @@
                 if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
                     mInitialSynced = true;
                 }
-                if (!mIsActive) return;
                 clearLoadingBit(BIT_LOADING_SYNC);
-                if (resultCode == MediaSet.SYNC_RESULT_ERROR) {
+                if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
                     Toast.makeText((Context) mActivity, R.string.sync_album_error,
                             Toast.LENGTH_LONG).show();
                 }
@@ -583,7 +584,7 @@
     }
 
     private void setLoadingBit(int loadTaskBit) {
-        if (mLoadingBits == 0) {
+        if (mLoadingBits == 0 && mIsActive) {
             GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
         }
         mLoadingBits |= loadTaskBit;
@@ -591,7 +592,7 @@
 
     private void clearLoadingBit(int loadTaskBit) {
         mLoadingBits &= ~loadTaskBit;
-        if (mLoadingBits == 0) {
+        if (mLoadingBits == 0 && mIsActive) {
             GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
 
             if (mAlbumDataAdapter.size() == 0) {
@@ -610,7 +611,6 @@
 
         @Override
         public void onLoadingFinished() {
-            if (!mIsActive) return;
             clearLoadingBit(BIT_LOADING_RELOAD);
         }
     }
diff --git a/src/com/android/gallery3d/app/AlbumSetPage.java b/src/com/android/gallery3d/app/AlbumSetPage.java
index e1dcade..5c39341 100644
--- a/src/com/android/gallery3d/app/AlbumSetPage.java
+++ b/src/com/android/gallery3d/app/AlbumSetPage.java
@@ -292,7 +292,7 @@
 
     private void clearLoadingBit(int loadingBit) {
         mLoadingBits &= ~loadingBit;
-        if (mLoadingBits == 0) {
+        if (mLoadingBits == 0 && mIsActive) {
             GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
 
             // Only show toast when there's no album and we are going to finish
@@ -308,7 +308,7 @@
     }
 
     private void setLoadingBit(int loadingBit) {
-        if (mLoadingBits == 0) {
+        if (mLoadingBits == 0 && mIsActive) {
             GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
         }
         mLoadingBits |= loadingBit;
@@ -330,6 +330,7 @@
             mSyncTask = null;
             clearLoadingBit(BIT_LOADING_SYNC);
         }
+        GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
     }
 
     @Override
@@ -614,9 +615,8 @@
                 if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
                     mInitialSynced = true;
                 }
-                if (!mIsActive) return;
                 clearLoadingBit(BIT_LOADING_SYNC);
-                if (resultCode == MediaSet.SYNC_RESULT_ERROR) {
+                if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
                     Toast.makeText((Context) mActivity, R.string.sync_album_set_error,
                             Toast.LENGTH_LONG).show();
                 }
@@ -630,7 +630,6 @@
         }
 
         public void onLoadingFinished() {
-            if (!mIsActive) return;
             clearLoadingBit(BIT_LOADING_RELOAD);
         }
     }
diff --git a/src/com/android/gallery3d/app/SlideshowDream.java b/src/com/android/gallery3d/app/SlideshowDream.java
index 80f7fe0..f4abe86 100644
--- a/src/com/android/gallery3d/app/SlideshowDream.java
+++ b/src/com/android/gallery3d/app/SlideshowDream.java
@@ -2,6 +2,7 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.support.v13.dreams.BasicDream;
 import android.graphics.Canvas;
 import android.net.Uri;
 import android.os.Bundle;
@@ -10,7 +11,7 @@
 import android.widget.ImageView;
 import android.widget.ViewFlipper;
 
-public class SlideshowDream extends Activity {
+public class SlideshowDream extends BasicDream {
     @Override
     public void onCreate(Bundle bndl) {
         super.onCreate(bndl);
diff --git a/src/com/android/gallery3d/app/StateManager.java b/src/com/android/gallery3d/app/StateManager.java
index 556a06a..f171689 100644
--- a/src/com/android/gallery3d/app/StateManager.java
+++ b/src/com/android/gallery3d/app/StateManager.java
@@ -159,7 +159,7 @@
     }
 
     void finishState(ActivityState state) {
-        Log.v(TAG, "finishState " + state.getClass());
+        Log.v(TAG, "finishState " + state);
         if (state != mStack.peek().activityState) {
             if (state.isDestroyed()) {
                 Log.d(TAG, "The state is already destroyed");
diff --git a/src/com/android/gallery3d/data/DataManager.java b/src/com/android/gallery3d/data/DataManager.java
index f7dac5e..51d4af9 100644
--- a/src/com/android/gallery3d/data/DataManager.java
+++ b/src/com/android/gallery3d/data/DataManager.java
@@ -159,11 +159,16 @@
             return null;
         }
 
-        MediaObject object = source.createMediaObject(path);
-        if (object == null) {
-            Log.w(TAG, "cannot create media object: " + path);
+        try {
+            MediaObject object = source.createMediaObject(path);
+            if (object == null) {
+                Log.w(TAG, "cannot create media object: " + path);
+            }
+            return object;
+        } catch (Throwable t) {
+            Log.w(TAG, "exception in creating media object: " + path, t);
+            return null;
         }
-        return object;
     }
 
     public MediaObject getMediaObject(String s) {
diff --git a/src/com/android/gallery3d/data/LocalAlbum.java b/src/com/android/gallery3d/data/LocalAlbum.java
index 5bd4398..28364cb 100644
--- a/src/com/android/gallery3d/data/LocalAlbum.java
+++ b/src/com/android/gallery3d/data/LocalAlbum.java
@@ -16,11 +16,14 @@
 
 package com.android.gallery3d.data;
 
+import com.android.gallery3d.R;
 import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.MediaSetUtils;
 
 import android.content.ContentResolver;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.MediaStore.Images;
@@ -45,7 +48,7 @@
     private final GalleryApp mApplication;
     private final ContentResolver mResolver;
     private final int mBucketId;
-    private final String mBucketName;
+    private final String mName;
     private final boolean mIsImage;
     private final ChangeNotifier mNotifier;
     private final Path mItemPath;
@@ -57,7 +60,7 @@
         mApplication = application;
         mResolver = application.getContentResolver();
         mBucketId = bucketId;
-        mBucketName = name;
+        mName = getLocalizedName(application.getResources(), bucketId, name);
         mIsImage = isImage;
 
         if (isImage) {
@@ -221,7 +224,7 @@
 
     @Override
     public String getName() {
-        return mBucketName;
+        return mName;
     }
 
     @Override
@@ -249,4 +252,19 @@
     public boolean isLeafAlbum() {
         return true;
     }
+
+    private static String getLocalizedName(Resources res, int bucketId,
+            String name) {
+        if (bucketId == MediaSetUtils.CAMERA_BUCKET_ID) {
+            return res.getString(R.string.folder_camera);
+        } else if (bucketId == MediaSetUtils.DOWNLOAD_BUCKET_ID) {
+            return res.getString(R.string.folder_download);
+        } else if (bucketId == MediaSetUtils.IMPORTED_BUCKET_ID) {
+            return res.getString(R.string.folder_imported);
+        } else if (bucketId == MediaSetUtils.SNAPSHOT_BUCKET_ID) {
+            return res.getString(R.string.folder_screenshot);
+        } else {
+            return name;
+        }
+    }
 }
diff --git a/src/com/android/gallery3d/photoeditor/BitmapUtils.java b/src/com/android/gallery3d/photoeditor/BitmapUtils.java
index a9fa758..a3a18d2 100644
--- a/src/com/android/gallery3d/photoeditor/BitmapUtils.java
+++ b/src/com/android/gallery3d/photoeditor/BitmapUtils.java
@@ -21,11 +21,8 @@
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
 import android.graphics.Matrix;
-import android.graphics.Paint;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.net.Uri;
 import android.provider.MediaStore.Images.ImageColumns;
 import android.util.Log;
@@ -57,40 +54,8 @@
         this.context = context;
     }
 
-    /**
-     * Creates a mutable bitmap from subset of source bitmap, transformed by the optional matrix.
-     */
-    private static Bitmap createBitmap(
-            Bitmap source, int x, int y, int width, int height, Matrix m) {
-        // Re-implement Bitmap createBitmap() to always return a mutable bitmap.
-        Canvas canvas = new Canvas();
-
-        Bitmap bitmap;
-        Paint paint;
-        if ((m == null) || m.isIdentity()) {
-            bitmap = Bitmap.createBitmap(width, height, source.getConfig());
-            paint = null;
-        } else {
-            RectF rect = new RectF(0, 0, width, height);
-            m.mapRect(rect);
-            bitmap = Bitmap.createBitmap(
-                    Math.round(rect.width()), Math.round(rect.height()), source.getConfig());
-
-            canvas.translate(-rect.left, -rect.top);
-            canvas.concat(m);
-
-            paint = new Paint(Paint.FILTER_BITMAP_FLAG);
-            if (!m.rectStaysRect()) {
-                paint.setAntiAlias(true);
-            }
-        }
-        bitmap.setDensity(source.getDensity());
-        canvas.setBitmap(bitmap);
-
-        Rect srcBounds = new Rect(x, y, x + width, y + height);
-        RectF dstBounds = new RectF(0, 0, width, height);
-        canvas.drawBitmap(source, srcBounds, dstBounds, paint);
-        return bitmap;
+    private static Bitmap createBitmap(Bitmap source, Matrix m) {
+        return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), m, true);
     }
 
     private void closeStream(Closeable stream) {
@@ -143,7 +108,7 @@
     }
 
     /**
-     * Decodes bitmap (maybe immutable) that keeps aspect-ratio and spans most within the bounds.
+     * Decodes bitmap that keeps aspect-ratio and spans most within the bounds.
      */
     private Bitmap decodeBitmap(Uri uri, int width, int height) {
         InputStream is = null;
@@ -184,8 +149,7 @@
             if (scale < 1) {
                 Matrix m = new Matrix();
                 m.setScale(scale, scale);
-                Bitmap transformed = createBitmap(
-                        bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m);
+                Bitmap transformed = createBitmap(bitmap, m);
                 bitmap.recycle();
                 return transformed;
             }
@@ -194,7 +158,7 @@
     }
 
     /**
-     * Gets decoded bitmap that keeps orientation as well.
+     * Gets decoded bitmap (maybe immutable) that keeps orientation as well.
      */
     public Bitmap getBitmap(Uri uri, int width, int height) {
         Bitmap bitmap = decodeBitmap(uri, width, height);
@@ -205,8 +169,7 @@
             if (orientation != 0) {
                 Matrix m = new Matrix();
                 m.setRotate(orientation);
-                Bitmap transformed = createBitmap(
-                        bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m);
+                Bitmap transformed = createBitmap(bitmap, m);
                 bitmap.recycle();
                 return transformed;
             }
diff --git a/src/com/android/gallery3d/photoeditor/EffectsBar.java b/src/com/android/gallery3d/photoeditor/EffectsBar.java
index 4075404..de47159 100644
--- a/src/com/android/gallery3d/photoeditor/EffectsBar.java
+++ b/src/com/android/gallery3d/photoeditor/EffectsBar.java
@@ -22,11 +22,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
-import android.widget.TextView;
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.photoeditor.actions.EffectAction;
-import com.android.gallery3d.photoeditor.actions.EffectToolFactory;
 
 /**
  * Effects bar that contains all effects and shows them in categorized views.
@@ -37,7 +35,6 @@
     private FilterStack filterStack;
     private EffectsMenu effectsMenu;
     private View effectsGallery;
-    private ViewGroup effectToolPanel;
     private EffectAction activeEffect;
 
     public EffectsBar(Context context, AttributeSet attrs) {
@@ -75,49 +72,40 @@
         ViewGroup scrollView = (ViewGroup) effectsGallery.findViewById(R.id.scroll_view);
         ViewGroup effects = (ViewGroup) inflater.inflate(effectsId, scrollView, false);
         for (int i = 0; i < effects.getChildCount(); i++) {
-            setupEffectListener((EffectAction) effects.getChildAt(i));
+            setupEffect((EffectAction) effects.getChildAt(i));
         }
         scrollView.addView(effects);
         scrollView.scrollTo(0, 0);
         addView(effectsGallery, 0);
     }
 
-    private void setupEffectListener(final EffectAction effect) {
-        effect.setListener(new EffectAction.Listener() {
+    private void setupEffect(final EffectAction effect) {
+        effect.setOnClickListener(new View.OnClickListener() {
 
             @Override
-            public void onClick() {
+            public void onClick(View v) {
                 if (isEnabled()) {
                     // Set the clicked effect active before exiting effects-gallery.
                     activeEffect = effect;
                     exitEffectsGallery();
-                    // Create effect tool panel first before the factory could create tools within.
-                    createEffectToolPanel();
-                    activeEffect.begin(filterStack,
-                            new EffectToolFactory(effectToolPanel, inflater));
+                    EffectAction.ActionListener listener = new EffectAction.ActionListener() {
+
+                        @Override
+                        public void onOk() {
+                            exit(null);
+                        }
+                    };
+                    activeEffect.begin(getRootView(), filterStack, listener);
                 }
             }
-
-            @Override
-            public void onDone() {
-                exit(null);
-            }
         });
     }
 
-    private void createEffectToolPanel() {
-        effectToolPanel = (ViewGroup) inflater.inflate(
-                R.layout.photoeditor_effect_tool_panel, this, false);
-        ((TextView) effectToolPanel.findViewById(R.id.effect_label)).setText(activeEffect.name());
-        addView(effectToolPanel, 0);
-    }
-
     private boolean exitEffectsGallery() {
         if (effectsGallery != null) {
             if (activeEffect != null) {
                 // Detach the active effect to prevent it stopping effects-gallery from gc.
-                ViewGroup scrollView = (ViewGroup) effectsGallery.findViewById(R.id.scroll_view);
-                ((ViewGroup) scrollView.getChildAt(0)).removeView(activeEffect);
+                ((ViewGroup) activeEffect.getParent()).removeView(activeEffect);
             }
             removeView(effectsGallery);
             effectsGallery = null;
@@ -134,12 +122,6 @@
                 @Override
                 public void run() {
                     SpinnerProgressDialog.dismissDialog();
-                    View fullscreenTool = getRootView().findViewById(R.id.fullscreen_effect_tool);
-                    if (fullscreenTool != null) {
-                        ((ViewGroup) fullscreenTool.getParent()).removeView(fullscreenTool);
-                    }
-                    removeView(effectToolPanel);
-                    effectToolPanel = null;
                     activeEffect = null;
                     if (runnableOnDone != null) {
                         runnableOnDone.run();
diff --git a/src/com/android/gallery3d/photoeditor/EffectsMenu.java b/src/com/android/gallery3d/photoeditor/EffectsMenu.java
index 71614b1..6578849 100644
--- a/src/com/android/gallery3d/photoeditor/EffectsMenu.java
+++ b/src/com/android/gallery3d/photoeditor/EffectsMenu.java
@@ -51,13 +51,13 @@
     }
 
     public void setOnToggleListener(OnToggleListener listener) {
-        setToggleRunnalbe(listener, R.id.exposure_button, R.layout.photoeditor_effects_exposure);
-        setToggleRunnalbe(listener, R.id.artistic_button, R.layout.photoeditor_effects_artistic);
-        setToggleRunnalbe(listener, R.id.color_button, R.layout.photoeditor_effects_color);
-        setToggleRunnalbe(listener, R.id.fix_button, R.layout.photoeditor_effects_fix);
+        setToggleRunnable(listener, R.id.exposure_button, R.layout.photoeditor_effects_exposure);
+        setToggleRunnable(listener, R.id.artistic_button, R.layout.photoeditor_effects_artistic);
+        setToggleRunnable(listener, R.id.color_button, R.layout.photoeditor_effects_color);
+        setToggleRunnable(listener, R.id.fix_button, R.layout.photoeditor_effects_fix);
     }
 
-    private void setToggleRunnalbe(final OnToggleListener listener, final int toggleId,
+    private void setToggleRunnable(final OnToggleListener listener, final int toggleId,
             final int effectsId) {
         setClickRunnable(toggleId, new Runnable() {
 
diff --git a/src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java b/src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java
index 26b5f51..a419840 100644
--- a/src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/AutoFixAction.java
@@ -33,14 +33,10 @@
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         AutoFixFilter filter = new AutoFixFilter();
         filter.setScale(DEFAULT_SCALE);
-        notifyFilterChanged(filter, true);
-        notifyDone();
-    }
-
-    @Override
-    public void doEnd() {
+        notifyChanged(filter);
+        notifyOk();
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java b/src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java
index 24978fa..76bc446 100644
--- a/src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/ColorTemperatureAction.java
@@ -28,32 +28,25 @@
 
     private static final float DEFAULT_SCALE = 0.5f;
 
-    private ScaleSeekBar scalePicker;
-
     public ColorTemperatureAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final ColorTemperatureFilter filter = new ColorTemperatureFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.COLOR);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.COLOR);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
     }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
-    }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/CropAction.java b/src/com/android/gallery3d/photoeditor/actions/CropAction.java
index 60a0179..e06c4e9 100644
--- a/src/com/android/gallery3d/photoeditor/actions/CropAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/CropAction.java
@@ -29,25 +29,24 @@
 
     private static final float DEFAULT_CROP = 0.2f;
 
-    private CropFilter filter;
-    private CropView cropView;
-
     public CropAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
-        filter = new CropFilter();
+    public void prepare() {
+        // Cropped results wouldn't be previewed for changed crop bounds.
+        final CropFilter filter = new CropFilter();
+        disableFilterOutput();
 
-        cropView = factory.createCropView();
+        CropView cropView = toolKit.addCropView();
         cropView.setOnCropChangeListener(new CropView.OnCropChangeListener() {
 
             @Override
             public void onCropChanged(RectF cropBounds, boolean fromUser) {
                 if (fromUser) {
                     filter.setCropBounds(cropBounds);
-                    notifyFilterChanged(filter, false);
+                    notifyChanged(filter);
                 }
             }
         });
@@ -55,12 +54,6 @@
         RectF bounds = new RectF(DEFAULT_CROP, DEFAULT_CROP, 1 - DEFAULT_CROP, 1 - DEFAULT_CROP);
         cropView.setCropBounds(bounds);
         filter.setCropBounds(bounds);
-        notifyFilterChanged(filter, false);
-    }
-
-    @Override
-    public void doEnd() {
-        cropView.setOnCropChangeListener(null);
-        notifyFilterChanged(filter, true);
+        notifyChanged(filter);
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java b/src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java
index 8be60d3..b61f6fa 100644
--- a/src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/CrossProcessAction.java
@@ -31,12 +31,8 @@
     }
 
     @Override
-    public void doBegin() {
-        notifyFilterChanged(new CrossProcessFilter(), true);
-        notifyDone();
-    }
-
-    @Override
-    public void doEnd() {
+    public void prepare() {
+        notifyChanged(new CrossProcessFilter());
+        notifyOk();
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java b/src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java
index 0ec4a02..b7f9a90 100644
--- a/src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/DocumentaryAction.java
@@ -31,12 +31,8 @@
     }
 
     @Override
-    public void doBegin() {
-        notifyFilterChanged(new DocumentaryFilter(), true);
-        notifyDone();
-    }
-
-    @Override
-    public void doEnd() {
+    public void prepare() {
+        notifyChanged(new DocumentaryFilter());
+        notifyOk();
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/Doodle.java b/src/com/android/gallery3d/photoeditor/actions/Doodle.java
index ea23e23..bd08983 100644
--- a/src/com/android/gallery3d/photoeditor/actions/Doodle.java
+++ b/src/com/android/gallery3d/photoeditor/actions/Doodle.java
@@ -55,20 +55,23 @@
     }
 
     /**
-     * Adds control points whose coordinates range from 0 to 1 to construct the doodle path.
-     *
-     * @return true if the constructed path is in (0, 0, 1, 1) bounds; otherwise, false.
+     * Checks if the constructed doodle path is in (0, 0, 1, 1) bounds.
      */
-    public boolean addControlPoint(PointF point) {
-        PointF last = points.lastElement();
-        normalizedPath.quadTo(last.x, last.y, (last.x + point.x) / 2, (last.y + point.y) / 2);
-        points.add(point);
-
+    public boolean inBounds() {
         RectF r = new RectF();
         normalizedPath.computeBounds(r, false);
         return r.intersects(0, 0, 1, 1);
     }
 
+    /**
+     * Adds control points whose coordinates range from 0 to 1 to construct the doodle path.
+     */
+    public void addControlPoint(PointF point) {
+        PointF last = points.lastElement();
+        normalizedPath.quadTo(last.x, last.y, (last.x + point.x) / 2, (last.y + point.y) / 2);
+        points.add(point);
+    }
+
     public int getColor() {
         return color;
     }
diff --git a/src/com/android/gallery3d/photoeditor/actions/DoodleAction.java b/src/com/android/gallery3d/photoeditor/actions/DoodleAction.java
index 4ad2cfb..f148287 100644
--- a/src/com/android/gallery3d/photoeditor/actions/DoodleAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/DoodleAction.java
@@ -28,19 +28,37 @@
 
     private static final int DEFAULT_COLOR_INDEX = 4;
 
-    private DoodleFilter filter;
-    private ColorSeekBar colorPicker;
-    private DoodleView doodleView;
-
     public DoodleAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
-        filter = new DoodleFilter();
+    public void prepare() {
+        // Directly draw on doodle-view because running the doodle filter isn't fast enough.
+        final DoodleFilter filter = new DoodleFilter();
+        disableFilterOutput();
 
-        colorPicker = factory.createColorPicker();
+        final DoodleView doodleView = toolKit.addDoodleView();
+        doodleView.setOnDoodleChangeListener(new DoodleView.OnDoodleChangeListener() {
+
+            @Override
+            public void onDoodleChanged(Doodle doodle) {
+                // Check if the user draws within photo bounds and makes visible changes on photo.
+                if (doodle.inBounds()) {
+                    notifyChanged(filter);
+                }
+            }
+
+            @Override
+            public void onDoodleFinished(Doodle doodle) {
+                if (doodle.inBounds()) {
+                    filter.addDoodle(doodle);
+                    notifyChanged(filter);
+                }
+            }
+        });
+
+        ColorSeekBar colorPicker = toolKit.addColorPicker();
         colorPicker.setOnColorChangeListener(new ColorSeekBar.OnColorChangeListener() {
 
             @Override
@@ -51,30 +69,6 @@
             }
         });
         colorPicker.setColorIndex(DEFAULT_COLOR_INDEX);
-
-        doodleView = factory.createDoodleView();
-        doodleView.setOnDoodleChangeListener(new DoodleView.OnDoodleChangeListener() {
-
-            @Override
-            public void onDoodleInPhotoBounds() {
-                // Notify the user has drawn within photo bounds and made visible changes on photo.
-                filter.setDoodledInPhotoBounds();
-                notifyFilterChanged(filter, false);
-            }
-
-            @Override
-            public void onDoodleFinished(Doodle doodle) {
-                filter.addDoodle(doodle);
-                notifyFilterChanged(filter, false);
-            }
-        });
         doodleView.setColor(colorPicker.getColor());
     }
-
-    @Override
-    public void doEnd() {
-        colorPicker.setOnColorChangeListener(null);
-        doodleView.setOnDoodleChangeListener(null);
-        notifyFilterChanged(filter, true);
-    }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/DoodleView.java b/src/com/android/gallery3d/photoeditor/actions/DoodleView.java
index b596861..d396049 100644
--- a/src/com/android/gallery3d/photoeditor/actions/DoodleView.java
+++ b/src/com/android/gallery3d/photoeditor/actions/DoodleView.java
@@ -37,7 +37,7 @@
      */
     public interface OnDoodleChangeListener {
 
-        void onDoodleInPhotoBounds();
+        void onDoodleChanged(Doodle doodle);
 
         void onDoodleFinished(Doodle doodle);
     }
@@ -111,9 +111,10 @@
     }
 
     private void addLastPointIntoDoodle() {
-        if ((doodle != null) && doodle.addControlPoint(new PointF(lastPoint.x, lastPoint.y))) {
+        if (doodle != null) {
+            doodle.addControlPoint(new PointF(lastPoint.x, lastPoint.y));
             if (listener != null) {
-                listener.onDoodleInPhotoBounds();
+                listener.onDoodleChanged(doodle);
             }
             invalidate();
         }
diff --git a/src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java b/src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java
index b8da71e..3ba8aa5 100644
--- a/src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/DuotoneAction.java
@@ -34,15 +34,11 @@
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         // TODO: Add several sets of duo-tone colors to select from.
         DuotoneFilter filter = new DuotoneFilter();
         filter.setDuotone(DEFAULT_FIRST_COLOR, DEFAULT_SECOND_COLOR);
-        notifyFilterChanged(filter, true);
-        notifyDone();
-    }
-
-    @Override
-    public void doEnd() {
+        notifyChanged(filter);
+        notifyOk();
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/EffectAction.java b/src/com/android/gallery3d/photoeditor/actions/EffectAction.java
index 6c6a893..92bcee4 100644
--- a/src/com/android/gallery3d/photoeditor/actions/EffectAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/EffectAction.java
@@ -37,46 +37,32 @@
     /**
      * Listener of effect action.
      */
-    public interface Listener {
+    public interface ActionListener {
 
-        void onClick();
-
-        void onDone();
+        /**
+         * Invoked when the action is okayed (effect is applied and completed).
+         */
+        void onOk();
     }
 
-    protected EffectToolFactory factory;
-
-    private Listener listener;
+    protected EffectToolKit toolKit;
     private Toast tooltip;
     private FilterStack filterStack;
     private boolean pushedFilter;
+    private boolean disableFilterOutput;
     private FilterChangedCallback lastFilterChangedCallback;
+    private ActionListener listener;
 
     public EffectAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
-    public void setListener(Listener l) {
-        listener = l;
-        findViewById(R.id.effect_button).setOnClickListener(
-                (listener == null) ? null : new View.OnClickListener() {
-
-            @Override
-            public void onClick(View v) {
-                listener.onClick();
-            }
-        });
-    }
-
-    public CharSequence name() {
-        return ((TextView) findViewById(R.id.effect_label)).getText();
-    }
-
-    public void begin(FilterStack filterStack, EffectToolFactory factory) {
+    public void begin(View root, FilterStack filterStack, ActionListener listener) {
         // This view is already detached from UI view hierarchy by reaching here; findViewById()
         // could only access its own child views from here.
+        toolKit = new EffectToolKit(root, ((TextView) findViewById(R.id.effect_label)).getText());
         this.filterStack = filterStack;
-        this.factory = factory;
+        this.listener = listener;
 
         // Shows the tooltip if it's available.
         if (getTag() != null) {
@@ -84,14 +70,30 @@
             tooltip.setGravity(Gravity.CENTER, 0, 0);
             tooltip.show();
         }
-        doBegin();
+        prepare();
     }
 
     /**
+     * Subclasses should create a specific filter and bind the filter to necessary UI controls here
+     * when the action is about to begin.
+     */
+    protected abstract void prepare();
+
+    /**
      * Ends the effect and then executes the runnable after the effect is finished.
      */
     public void end(final Runnable runnableOnODone) {
-        doEnd();
+        // Cancel the tooltip if it's still showing.
+        if ((tooltip != null) && (tooltip.getView().getParent() != null)) {
+            tooltip.cancel();
+            tooltip = null;
+        }
+        // End tool editing by canceling unfinished touch events.
+        toolKit.cancel();
+        // Output the pushed filter if it wasn't outputted.
+        if (pushedFilter && disableFilterOutput) {
+            outputFilter();
+        }
 
         // Wait till last output callback is done before finishing.
         if ((lastFilterChangedCallback == null) || lastFilterChangedCallback.done) {
@@ -108,45 +110,38 @@
     }
 
     private void finish(Runnable runnableOnDone) {
-        // Close the tooltip if it's still showing.
-        if ((tooltip != null) && (tooltip.getView().getParent() != null)) {
-            tooltip.cancel();
-            tooltip = null;
-        }
+        toolKit.close();
         pushedFilter = false;
+        disableFilterOutput = false;
         lastFilterChangedCallback = null;
 
         runnableOnDone.run();
     }
 
-    protected void notifyDone() {
-        if (listener != null) {
-            listener.onDone();
-        }
+    protected void disableFilterOutput() {
+        // Filter output won't be outputted until this effect has done editing its filter.
+        disableFilterOutput = true;
     }
 
-    protected void notifyFilterChanged(Filter filter, boolean output) {
-        if (!pushedFilter && filter.isValid()) {
+    protected void outputFilter() {
+        // Notify the stack to execute the changed top filter and output the results.
+        lastFilterChangedCallback = new FilterChangedCallback();
+        filterStack.topFilterChanged(lastFilterChangedCallback);
+    }
+
+    protected void notifyChanged(Filter filter) {
+        if (!pushedFilter) {
             filterStack.pushFilter(filter);
             pushedFilter = true;
         }
-        if (pushedFilter && output) {
-            // Notify the stack to execute the changed top filter and output the results.
-            lastFilterChangedCallback = new FilterChangedCallback();
-            filterStack.topFilterChanged(lastFilterChangedCallback);
+        if (pushedFilter && !disableFilterOutput) {
+            outputFilter();
         }
     }
 
-    /**
-     * Subclasses should creates a specific filter and binds the filter to necessary UI controls
-     * here when the action is about to begin.
-     */
-    protected abstract void doBegin();
-
-    /**
-     * Subclasses could do specific ending operations here when the action is about to end.
-     */
-    protected abstract void doEnd();
+    protected void notifyOk() {
+        listener.onOk();
+    }
 
     /**
      * Done callback for executing top filter changes.
diff --git a/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java b/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java
deleted file mode 100644
index 3641828..0000000
--- a/src/com/android/gallery3d/photoeditor/actions/EffectToolFactory.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2010 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.photoeditor.actions;
-
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.gallery3d.R;
-import com.android.gallery3d.photoeditor.PhotoView;
-
-/**
- * Factory to create tools that will be used by effect actions.
- */
-public class EffectToolFactory {
-
-    public enum ScalePickerType {
-        LIGHT, SHADOW, COLOR, GENERIC
-    }
-
-    private final ViewGroup effectToolPanel;
-    private final LayoutInflater inflater;
-
-    public EffectToolFactory(ViewGroup effectToolPanel, LayoutInflater inflater) {
-        this.effectToolPanel = effectToolPanel;
-        this.inflater = inflater;
-    }
-
-    private View createFullscreenTool(int toolId) {
-        // Create full screen effect tool on top of photo-view and place it within the same
-        // view group that contains photo-view.
-        View photoView = effectToolPanel.getRootView().findViewById(R.id.photo_view);
-        ViewGroup parent = (ViewGroup) photoView.getParent();
-        FullscreenToolView view = (FullscreenToolView) inflater.inflate(toolId, parent, false);
-        view.setPhotoBounds(((PhotoView) photoView).getPhotoBounds());
-        parent.addView(view, parent.indexOfChild(photoView) + 1);
-        return view;
-    }
-
-    private View createPanelTool(int toolId) {
-        View view = inflater.inflate(toolId, effectToolPanel, false);
-        effectToolPanel.addView(view, 0);
-        return view;
-    }
-
-    private int getScalePickerBackground(ScalePickerType type) {
-        switch (type) {
-            case LIGHT:
-                return R.drawable.photoeditor_scale_seekbar_light;
-
-            case SHADOW:
-                return R.drawable.photoeditor_scale_seekbar_shadow;
-
-            case COLOR:
-                return R.drawable.photoeditor_scale_seekbar_color;
-        }
-        return R.drawable.photoeditor_scale_seekbar_generic;
-    }
-
-    public ScaleSeekBar createScalePicker(ScalePickerType type) {
-        ScaleSeekBar scalePicker = (ScaleSeekBar) createPanelTool(
-                R.layout.photoeditor_scale_seekbar);
-        scalePicker.setBackgroundResource(getScalePickerBackground(type));
-        return scalePicker;
-    }
-
-    public ColorSeekBar createColorPicker() {
-        return (ColorSeekBar) createPanelTool(R.layout.photoeditor_color_seekbar);
-    }
-
-    public DoodleView createDoodleView() {
-        return (DoodleView) createFullscreenTool(R.layout.photoeditor_doodle_view);
-    }
-
-    public TouchView createTouchView() {
-        return (TouchView) createFullscreenTool(R.layout.photoeditor_touch_view);
-    }
-
-    public FlipView createFlipView() {
-        return (FlipView) createFullscreenTool(R.layout.photoeditor_flip_view);
-    }
-
-    public RotateView createRotateView() {
-        return (RotateView) createFullscreenTool(R.layout.photoeditor_rotate_view);
-    }
-
-    public CropView createCropView() {
-        return (CropView) createFullscreenTool(R.layout.photoeditor_crop_view);
-    }
-}
diff --git a/src/com/android/gallery3d/photoeditor/actions/EffectToolKit.java b/src/com/android/gallery3d/photoeditor/actions/EffectToolKit.java
new file mode 100644
index 0000000..fbb54d9
--- /dev/null
+++ b/src/com/android/gallery3d/photoeditor/actions/EffectToolKit.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2010 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.photoeditor.actions;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.photoeditor.PhotoView;
+
+/**
+ * Tool kit used by effect actions to retrieve tools, including managing tool creation/removal.
+ */
+public class EffectToolKit {
+
+    public enum ScaleType {
+        LIGHT, SHADOW, COLOR, GENERIC
+    }
+
+    private final LayoutInflater inflater;
+    private final PhotoView photoView;
+    private final ViewGroup toolPanel;
+    private final ViewGroup toolFullscreen;
+
+    public EffectToolKit(View root, CharSequence label) {
+        inflater = (LayoutInflater) root.getContext().getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        // Create effect tool panel as the first child of effects-bar.
+        ViewGroup effectsBar = (ViewGroup) root.findViewById(R.id.effects_bar);
+        toolPanel = (ViewGroup) inflater.inflate(
+                R.layout.photoeditor_effect_tool_panel, effectsBar, false);
+        ((TextView) toolPanel.findViewById(R.id.effect_label)).setText(label);
+        effectsBar.addView(toolPanel, 0);
+
+        // Create effect tool full-screen on top of photo-view and place it within the same
+        // view group that contains photo-view.
+        photoView = (PhotoView) root.findViewById(R.id.photo_view);
+        ViewGroup parent = (ViewGroup) photoView.getParent();
+        toolFullscreen = (ViewGroup) inflater.inflate(
+                R.layout.photoeditor_effect_tool_fullscreen, parent, false);
+        parent.addView(toolFullscreen, parent.indexOfChild(photoView) + 1);
+    }
+
+    public PhotoView getPhotoView() {
+        return photoView;
+    }
+
+    /**
+     * Cancel pending touch events and stop dispatching further touch events to tools.
+     */
+    public void cancel() {
+        long now = SystemClock.uptimeMillis();
+        MotionEvent cancelEvent = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+        toolFullscreen.dispatchTouchEvent(cancelEvent);
+        toolPanel.dispatchTouchEvent(cancelEvent);
+        cancelEvent.recycle();
+        View.OnTouchListener listener = new View.OnTouchListener() {
+
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                // Consume all further touch events and don't dispatch them.
+                return true;
+            }
+        };
+        toolFullscreen.setOnTouchListener(listener);
+        toolPanel.setOnTouchListener(listener);
+    }
+
+    /**
+     * Close to remove all created tools.
+     */
+    public void close() {
+        ((ViewGroup) toolFullscreen.getParent()).removeView(toolFullscreen);
+        ((ViewGroup) toolPanel.getParent()).removeView(toolPanel);
+    }
+
+    private View addFullscreenTool(int toolId) {
+        FullscreenToolView tool = (FullscreenToolView) inflater.inflate(
+                toolId, toolFullscreen, false);
+        tool.setPhotoBounds(getPhotoView().getPhotoBounds());
+        toolFullscreen.addView(tool);
+        return tool;
+    }
+
+    private View addPanelTool(int toolId) {
+        // Add the tool right above the effect-label in the panel.
+        View tool = inflater.inflate(toolId, toolPanel, false);
+        toolPanel.addView(tool, toolPanel.indexOfChild(toolPanel.findViewById(R.id.effect_label)));
+        return tool;
+    }
+
+    private int getScalePickerBackground(ScaleType type) {
+        switch (type) {
+            case LIGHT:
+                return R.drawable.photoeditor_scale_seekbar_light;
+
+            case SHADOW:
+                return R.drawable.photoeditor_scale_seekbar_shadow;
+
+            case COLOR:
+                return R.drawable.photoeditor_scale_seekbar_color;
+        }
+        return R.drawable.photoeditor_scale_seekbar_generic;
+    }
+
+    public ScaleSeekBar addScalePicker(ScaleType type) {
+        ScaleSeekBar scalePicker = (ScaleSeekBar) addPanelTool(
+                R.layout.photoeditor_scale_seekbar);
+        scalePicker.setBackgroundResource(getScalePickerBackground(type));
+        return scalePicker;
+    }
+
+    public ColorSeekBar addColorPicker() {
+        return (ColorSeekBar) addPanelTool(R.layout.photoeditor_color_seekbar);
+    }
+
+    public DoodleView addDoodleView() {
+        return (DoodleView) addFullscreenTool(R.layout.photoeditor_doodle_view);
+    }
+
+    public TouchView addTouchView() {
+        return (TouchView) addFullscreenTool(R.layout.photoeditor_touch_view);
+    }
+
+    public FlipView addFlipView() {
+        return (FlipView) addFullscreenTool(R.layout.photoeditor_flip_view);
+    }
+
+    public RotateView addRotateView() {
+        return (RotateView) addFullscreenTool(R.layout.photoeditor_rotate_view);
+    }
+
+    public CropView addCropView() {
+        return (CropView) addFullscreenTool(R.layout.photoeditor_crop_view);
+    }
+}
diff --git a/src/com/android/gallery3d/photoeditor/actions/FaceTanAction.java b/src/com/android/gallery3d/photoeditor/actions/FaceTanAction.java
index a82f330..6b8f1d1 100644
--- a/src/com/android/gallery3d/photoeditor/actions/FaceTanAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/FaceTanAction.java
@@ -28,35 +28,28 @@
 
     private static final float DEFAULT_SCALE = 0.5f;
 
-    private ScaleSeekBar scalePicker;
-
     public FaceTanAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final FaceTanFilter filter = new FaceTanFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
 
         filter.setScale(DEFAULT_SCALE);
-        notifyFilterChanged(filter, true);
-    }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
+        notifyChanged(filter);
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/FaceliftAction.java b/src/com/android/gallery3d/photoeditor/actions/FaceliftAction.java
index 90d4e0c..4c1a918 100644
--- a/src/com/android/gallery3d/photoeditor/actions/FaceliftAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/FaceliftAction.java
@@ -28,35 +28,28 @@
 
     private static final float DEFAULT_SCALE = 0.5f;
 
-    private ScaleSeekBar scalePicker;
-
     public FaceliftAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final FaceliftFilter filter = new FaceliftFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
 
         filter.setScale(DEFAULT_SCALE);
-        notifyFilterChanged(filter, true);
-    }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
+        notifyChanged(filter);
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/FillLightAction.java b/src/com/android/gallery3d/photoeditor/actions/FillLightAction.java
index 73cf3d8..323bfd6 100644
--- a/src/com/android/gallery3d/photoeditor/actions/FillLightAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/FillLightAction.java
@@ -28,32 +28,25 @@
 
     private static final float DEFAULT_SCALE = 0f;
 
-    private ScaleSeekBar scalePicker;
-
     public FillLightAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final FillLightFilter filter = new FillLightFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.LIGHT);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.LIGHT);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
     }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
-    }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java b/src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java
index 348f004..df7ed78 100644
--- a/src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/FisheyeAction.java
@@ -28,35 +28,28 @@
 
     private static final float DEFAULT_SCALE = 0.5f;
 
-    private ScaleSeekBar scalePicker;
-
     public FisheyeAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final FisheyeFilter filter = new FisheyeFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
 
         filter.setScale(DEFAULT_SCALE);
-        notifyFilterChanged(filter, true);
-    }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
+        notifyChanged(filter);
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/FlipAction.java b/src/com/android/gallery3d/photoeditor/actions/FlipAction.java
index da238ba..71e5a90 100644
--- a/src/com/android/gallery3d/photoeditor/actions/FlipAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/FlipAction.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.util.AttributeSet;
 
-import com.android.gallery3d.R;
 import com.android.gallery3d.photoeditor.PhotoView;
 import com.android.gallery3d.photoeditor.filters.FlipFilter;
 
@@ -31,35 +30,32 @@
     private static final float DEFAULT_ANGLE = 0.0f;
     private static final float DEFAULT_FLIP_SPAN = 180.0f;
 
-    private FlipFilter filter;
-    private float horizontalFlipDegrees;
-    private float verticalFlipDegrees;
-    private Runnable queuedFlipChange;
-    private FlipView flipView;
-
     public FlipAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
-        filter = new FlipFilter();
+    public void prepare() {
+        // Disable outputting flipped results and directly flip photo-view for animations.
+        final FlipFilter filter = new FlipFilter();
+        disableFilterOutput();
 
-        flipView = factory.createFlipView();
+        final FlipView flipView = toolKit.addFlipView();
         flipView.setOnFlipChangeListener(new FlipView.OnFlipChangeListener() {
 
-            // Directly transform photo-view because running the flip filter isn't fast enough.
-            PhotoView photoView = (PhotoView) flipView.getRootView().findViewById(
-                    R.id.photo_view);
+            float horizontalDegrees;
+            float verticalDegrees;
+            Runnable queuedTransform;
+            PhotoView photoView = toolKit.getPhotoView();
 
             @Override
             public void onAngleChanged(float horizontalDegrees, float verticalDegrees,
                     boolean fromUser) {
                 if (fromUser) {
-                    horizontalFlipDegrees = horizontalDegrees;
-                    verticalFlipDegrees = verticalDegrees;
-                    updateFlipFilter(false);
+                    this.horizontalDegrees = horizontalDegrees;
+                    this.verticalDegrees = verticalDegrees;
                     transformPhotoView(horizontalDegrees, verticalDegrees);
+                    notifyChanged(filter);
                 }
             }
 
@@ -70,59 +66,42 @@
 
             @Override
             public void onStopTrackingTouch() {
-                roundFlipDegrees();
-                updateFlipFilter(false);
-                transformPhotoView(horizontalFlipDegrees, verticalFlipDegrees);
-                flipView.setFlippedAngles(horizontalFlipDegrees, verticalFlipDegrees);
+                // Round flip degrees to multiples of 180 degrees.
+                horizontalDegrees = roundTo180(horizontalDegrees);
+                verticalDegrees = roundTo180(verticalDegrees);
+                transformPhotoView(horizontalDegrees, verticalDegrees);
+                flipView.setFlippedAngles(horizontalDegrees, verticalDegrees);
+
+                // Flip the filter according to the flipped directions of flip-view.
+                filter.setFlip(((int) horizontalDegrees / 180) % 2 != 0,
+                        ((int) verticalDegrees / 180) % 2 != 0);
+                notifyChanged(filter);
+            }
+
+            private float roundTo180(float degrees) {
+                if (degrees % 180 != 0) {
+                    degrees = Math.round(degrees / 180) * 180;
+                }
+                return degrees;
             }
 
             private void transformPhotoView(final float horizontalDegrees,
                     final float verticalDegrees) {
                 // Remove the outdated flip change before queuing a new one.
-                if (queuedFlipChange != null) {
-                    photoView.remove(queuedFlipChange);
+                if (queuedTransform != null) {
+                    photoView.remove(queuedTransform);
                 }
-                queuedFlipChange = new Runnable() {
+                queuedTransform = new Runnable() {
 
                     @Override
                     public void run() {
                         photoView.flipPhoto(horizontalDegrees, verticalDegrees);
                     }
                 };
-                photoView.queue(queuedFlipChange);
+                photoView.queue(queuedTransform);
             }
         });
         flipView.setFlippedAngles(DEFAULT_ANGLE, DEFAULT_ANGLE);
         flipView.setFlipSpan(DEFAULT_FLIP_SPAN);
-        horizontalFlipDegrees = 0;
-        verticalFlipDegrees = 0;
-        queuedFlipChange = null;
-    }
-
-    @Override
-    public void doEnd() {
-        flipView.setOnFlipChangeListener(null);
-        // Round the current flip degrees in case flip tracking has not stopped yet.
-        roundFlipDegrees();
-        updateFlipFilter(true);
-    }
-
-    /**
-     * Rounds flip degrees to multiples of 180 degrees.
-     */
-    private void roundFlipDegrees() {
-        if (horizontalFlipDegrees % 180 != 0) {
-            horizontalFlipDegrees = Math.round(horizontalFlipDegrees / 180) * 180;
-        }
-        if (verticalFlipDegrees % 180 != 0) {
-            verticalFlipDegrees = Math.round(verticalFlipDegrees / 180) * 180;
-        }
-    }
-
-    private void updateFlipFilter(boolean outputFilter) {
-        // Flip the filter if the flipped degrees are at the opposite directions.
-        filter.setFlip(((int) horizontalFlipDegrees / 180) % 2 != 0,
-                ((int) verticalFlipDegrees / 180) % 2 != 0);
-        notifyFilterChanged(filter, outputFilter);
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/GrainAction.java b/src/com/android/gallery3d/photoeditor/actions/GrainAction.java
index 258eb8a..5e99129 100644
--- a/src/com/android/gallery3d/photoeditor/actions/GrainAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/GrainAction.java
@@ -28,35 +28,28 @@
 
     private static final float DEFAULT_SCALE = 0.5f;
 
-    private ScaleSeekBar scalePicker;
-
     public GrainAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final GrainFilter filter = new GrainFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
 
         filter.setScale(DEFAULT_SCALE);
-        notifyFilterChanged(filter, true);
-    }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
+        notifyChanged(filter);
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java b/src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java
index ac89cd1..f534ca8 100644
--- a/src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/GrayscaleAction.java
@@ -31,12 +31,8 @@
     }
 
     @Override
-    public void doBegin() {
-        notifyFilterChanged(new GrayscaleFilter(), true);
-        notifyDone();
-    }
-
-    @Override
-    public void doEnd() {
+    public void prepare() {
+        notifyChanged(new GrayscaleFilter());
+        notifyOk();
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/HighlightAction.java b/src/com/android/gallery3d/photoeditor/actions/HighlightAction.java
index a3d62d2..18d7add 100644
--- a/src/com/android/gallery3d/photoeditor/actions/HighlightAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/HighlightAction.java
@@ -28,32 +28,25 @@
 
     private static final float DEFAULT_SCALE = 0f;
 
-    private ScaleSeekBar scalePicker;
-
     public HighlightAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final HighlightFilter filter = new HighlightFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.LIGHT);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.LIGHT);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
     }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
-    }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/LomoishAction.java b/src/com/android/gallery3d/photoeditor/actions/LomoishAction.java
index 44ffc52..17af429 100644
--- a/src/com/android/gallery3d/photoeditor/actions/LomoishAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/LomoishAction.java
@@ -31,12 +31,8 @@
     }
 
     @Override
-    public void doBegin() {
-        notifyFilterChanged(new LomoishFilter(), true);
-        notifyDone();
-    }
-
-    @Override
-    public void doEnd() {
+    public void prepare() {
+        notifyChanged(new LomoishFilter());
+        notifyOk();
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/NegativeAction.java b/src/com/android/gallery3d/photoeditor/actions/NegativeAction.java
index 5276421..8893154 100644
--- a/src/com/android/gallery3d/photoeditor/actions/NegativeAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/NegativeAction.java
@@ -31,12 +31,8 @@
     }
 
     @Override
-    public void doBegin() {
-        notifyFilterChanged(new NegativeFilter(), true);
-        notifyDone();
-    }
-
-    @Override
-    public void doEnd() {
+    public void prepare() {
+        notifyChanged(new NegativeFilter());
+        notifyOk();
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java b/src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java
index 760539d..9a9f874 100644
--- a/src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/PosterizeAction.java
@@ -31,12 +31,8 @@
     }
 
     @Override
-    public void doBegin() {
-        notifyFilterChanged(new PosterizeFilter(), true);
-        notifyDone();
-    }
-
-    @Override
-    public void doEnd() {
+    public void prepare() {
+        notifyChanged(new PosterizeFilter());
+        notifyOk();
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java b/src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java
index a472ad9..b957715 100644
--- a/src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/RedEyeAction.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.PointF;
+import android.graphics.RectF;
 import android.util.AttributeSet;
 
 import com.android.gallery3d.photoeditor.filters.RedEyeFilter;
@@ -27,29 +28,27 @@
  */
 public class RedEyeAction extends EffectAction {
 
-    private TouchView touchView;
-
     public RedEyeAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final RedEyeFilter filter = new RedEyeFilter();
 
-        touchView = factory.createTouchView();
+        TouchView touchView = toolKit.addTouchView();
         touchView.setSingleTapListener(new TouchView.SingleTapListener() {
 
+            final RectF bounds = new RectF(0, 0, 1, 1);
+
             @Override
             public void onSingleTap(PointF point) {
-                filter.addRedEyePosition(point);
-                notifyFilterChanged(filter, true);
+                // Check if the user taps within photo bounds to remove red eye on photo.
+                if (bounds.contains(point.x, point.y)) {
+                    filter.addRedEyePosition(point);
+                    notifyChanged(filter);
+                }
             }
         });
     }
-
-    @Override
-    public void doEnd() {
-        touchView.setSingleTapListener(null);
-    }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/RotateAction.java b/src/com/android/gallery3d/photoeditor/actions/RotateAction.java
index 36a09d9..98d6555 100644
--- a/src/com/android/gallery3d/photoeditor/actions/RotateAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/RotateAction.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.util.AttributeSet;
 
-import com.android.gallery3d.R;
 import com.android.gallery3d.photoeditor.PhotoView;
 import com.android.gallery3d.photoeditor.filters.RotateFilter;
 
@@ -31,32 +30,29 @@
     private static final float DEFAULT_ANGLE = 0.0f;
     private static final float DEFAULT_ROTATE_SPAN = 360.0f;
 
-    private RotateFilter filter;
-    private float rotateDegrees;
-    private Runnable queuedRotationChange;
-    private RotateView rotateView;
-
     public RotateAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
-        filter = new RotateFilter();
+    public void prepare() {
+        // Disable outputting rotated results and directly rotate photo-view for animations.
+        final RotateFilter filter = new RotateFilter();
+        disableFilterOutput();
 
-        rotateView = factory.createRotateView();
+        final RotateView rotateView = toolKit.addRotateView();
         rotateView.setOnRotateChangeListener(new RotateView.OnRotateChangeListener() {
 
-            // Directly transform photo-view because running the rotate filter isn't fast enough.
-            PhotoView photoView = (PhotoView) rotateView.getRootView().findViewById(
-                    R.id.photo_view);
+            float rotateDegrees;
+            Runnable queuedTransform;
+            PhotoView photoView = toolKit.getPhotoView();
 
             @Override
-            public void onAngleChanged(float degrees, boolean fromUser){
+            public void onAngleChanged(float degrees, boolean fromUser) {
                 if (fromUser) {
                     rotateDegrees = degrees;
-                    updateRotateFilter(false);
-                    transformPhotoView(degrees);
+                    transformPhotoView(rotateDegrees);
+                    notifyChanged(filter);
                 }
             }
 
@@ -67,52 +63,32 @@
 
             @Override
             public void onStopTrackingTouch() {
-                roundRotateDegrees();
-                updateRotateFilter(false);
+                // Round rotate degrees to multiples of 90 degrees.
+                if (rotateDegrees % 90 != 0) {
+                    rotateDegrees = Math.round(rotateDegrees / 90) * 90;
+                }
                 transformPhotoView(rotateDegrees);
                 rotateView.setRotatedAngle(rotateDegrees);
+                filter.setAngle(rotateDegrees);
+                notifyChanged(filter);
             }
 
             private void transformPhotoView(final float degrees) {
                 // Remove the outdated rotation change before queuing a new one.
-                if (queuedRotationChange != null) {
-                    photoView.remove(queuedRotationChange);
+                if (queuedTransform != null) {
+                    photoView.remove(queuedTransform);
                 }
-                queuedRotationChange = new Runnable() {
+                queuedTransform = new Runnable() {
 
                     @Override
                     public void run() {
                         photoView.rotatePhoto(degrees);
                     }
                 };
-                photoView.queue(queuedRotationChange);
+                photoView.queue(queuedTransform);
             }
         });
         rotateView.setRotatedAngle(DEFAULT_ANGLE);
         rotateView.setRotateSpan(DEFAULT_ROTATE_SPAN);
-        rotateDegrees = 0;
-        queuedRotationChange = null;
-    }
-
-    @Override
-    public void doEnd() {
-        rotateView.setOnRotateChangeListener(null);
-        // Round the current rotation degrees in case rotation tracking has not stopped yet.
-        roundRotateDegrees();
-        updateRotateFilter(true);
-    }
-
-    /**
-     * Rounds rotate degrees to multiples of 90 degrees.
-     */
-    private void roundRotateDegrees() {
-        if (rotateDegrees % 90 != 0) {
-            rotateDegrees = Math.round(rotateDegrees / 90) * 90;
-        }
-    }
-
-    private void updateRotateFilter(boolean outputFilter) {
-        filter.setAngle(rotateDegrees);
-        notifyFilterChanged(filter, outputFilter);
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/SaturationAction.java b/src/com/android/gallery3d/photoeditor/actions/SaturationAction.java
index 2f67e0a..6afd7ba 100644
--- a/src/com/android/gallery3d/photoeditor/actions/SaturationAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/SaturationAction.java
@@ -28,32 +28,25 @@
 
     private static final float DEFAULT_SCALE = 0.5f;
 
-    private ScaleSeekBar scalePicker;
-
     public SaturationAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final SaturationFilter filter = new SaturationFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.COLOR);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.COLOR);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
     }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
-    }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/SepiaAction.java b/src/com/android/gallery3d/photoeditor/actions/SepiaAction.java
index c431115..5f551c8 100644
--- a/src/com/android/gallery3d/photoeditor/actions/SepiaAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/SepiaAction.java
@@ -31,12 +31,8 @@
     }
 
     @Override
-    public void doBegin() {
-        notifyFilterChanged(new SepiaFilter(), true);
-        notifyDone();
-    }
-
-    @Override
-    public void doEnd() {
+    public void prepare() {
+        notifyChanged(new SepiaFilter());
+        notifyOk();
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/ShadowAction.java b/src/com/android/gallery3d/photoeditor/actions/ShadowAction.java
index 15ba850..cfd0538 100644
--- a/src/com/android/gallery3d/photoeditor/actions/ShadowAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/ShadowAction.java
@@ -28,32 +28,25 @@
 
     private static final float DEFAULT_SCALE = 0f;
 
-    private ScaleSeekBar scalePicker;
-
     public ShadowAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final ShadowFilter filter = new ShadowFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.SHADOW);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.SHADOW);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
     }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
-    }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/SharpenAction.java b/src/com/android/gallery3d/photoeditor/actions/SharpenAction.java
index c6b240b..7c00b21 100644
--- a/src/com/android/gallery3d/photoeditor/actions/SharpenAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/SharpenAction.java
@@ -28,35 +28,28 @@
 
     private static final float DEFAULT_SCALE = 0.5f;
 
-    private ScaleSeekBar scalePicker;
-
     public SharpenAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final SharpenFilter filter = new SharpenFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
 
         filter.setScale(DEFAULT_SCALE);
-        notifyFilterChanged(filter, true);
-    }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
+        notifyChanged(filter);
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java b/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java
index 42b384d..55eb8fd 100644
--- a/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/StraightenAction.java
@@ -29,24 +29,22 @@
     private static final float DEFAULT_ANGLE = 0.0f;
     private static final float DEFAULT_ROTATE_SPAN = StraightenFilter.MAX_DEGREES * 2;
 
-    private RotateView rotateView;
-
     public StraightenAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final StraightenFilter filter = new StraightenFilter();
 
-        rotateView = factory.createRotateView();
+        RotateView rotateView = toolKit.addRotateView();
         rotateView.setOnRotateChangeListener(new RotateView.OnRotateChangeListener() {
 
             @Override
             public void onAngleChanged(float degrees, boolean fromUser){
                 if (fromUser) {
                     filter.setAngle(degrees);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
 
@@ -64,9 +62,4 @@
         rotateView.setRotatedAngle(DEFAULT_ANGLE);
         rotateView.setRotateSpan(DEFAULT_ROTATE_SPAN);
     }
-
-    @Override
-    public void doEnd() {
-        rotateView.setOnRotateChangeListener(null);
-    }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/TintAction.java b/src/com/android/gallery3d/photoeditor/actions/TintAction.java
index defd2a3..417c8f5 100644
--- a/src/com/android/gallery3d/photoeditor/actions/TintAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/TintAction.java
@@ -28,35 +28,28 @@
 
     private static final int DEFAULT_COLOR_INDEX = 13;
 
-    private ColorSeekBar colorPicker;
-
     public TintAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final TintFilter filter = new TintFilter();
 
-        colorPicker = factory.createColorPicker();
+        ColorSeekBar colorPicker = toolKit.addColorPicker();
         colorPicker.setOnColorChangeListener(new ColorSeekBar.OnColorChangeListener() {
 
             @Override
             public void onColorChanged(int color, boolean fromUser) {
                 if (fromUser) {
                     filter.setTint(color);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
-        // Tint photo with the default color.
         colorPicker.setColorIndex(DEFAULT_COLOR_INDEX);
-        filter.setTint(colorPicker.getColor());
-        notifyFilterChanged(filter, true);
-    }
 
-    @Override
-    public void doEnd() {
-        colorPicker.setOnColorChangeListener(null);
+        filter.setTint(colorPicker.getColor());
+        notifyChanged(filter);
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/actions/TouchView.java b/src/com/android/gallery3d/photoeditor/actions/TouchView.java
index 0548cc4..d5f311c 100644
--- a/src/com/android/gallery3d/photoeditor/actions/TouchView.java
+++ b/src/com/android/gallery3d/photoeditor/actions/TouchView.java
@@ -28,20 +28,6 @@
 class TouchView extends FullscreenToolView {
 
     /**
-     * Listener of swipes.
-     */
-    public interface SwipeListener {
-
-        void onSwipeLeft();
-
-        void onSwipeRight();
-
-        void onSwipeUp();
-
-        void onSwipeDown();
-    }
-
-    /**
      * Listener of single tap on a point (relative to photo coordinates).
      */
     public interface SingleTapListener {
@@ -50,21 +36,18 @@
     }
 
     private final GestureDetector gestureDetector;
-
-    private SwipeListener swipeListener;
     private SingleTapListener singleTapListener;
 
     public TouchView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        final int swipeThreshold = (int) (500 * getResources().getDisplayMetrics().density);
         gestureDetector = new GestureDetector(
                 context, new GestureDetector.SimpleOnGestureListener() {
 
             @Override
             public boolean onDown(MotionEvent e) {
-                // GestureDetector onTouchEvent returns true for fling events only when their
-                // preceding down events are consumed.
+                // GestureDetector onTouchEvent returns true only for events whose preceding
+                // down-events have been consumed.
                 return true;
             }
 
@@ -77,37 +60,10 @@
                 }
                 return true;
             }
-
-            @Override
-            public boolean onFling(
-                    MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) {
-                if (swipeListener != null) {
-                    float absX = Math.abs(velocityX);
-                    float absY = Math.abs(velocityY);
-                    float deltaX = me2.getX() - me1.getX();
-                    float deltaY = me2.getY() - me1.getY();
-                    int travelX = getWidth() / 4;
-                    int travelY = getHeight() / 4;
-                    if (velocityX > swipeThreshold && absY < absX && deltaX > travelX) {
-                        swipeListener.onSwipeRight();
-                    } else if (velocityX < -swipeThreshold && absY < absX && deltaX < -travelX) {
-                        swipeListener.onSwipeLeft();
-                    } else if (velocityY < -swipeThreshold && absX < absY && deltaY < -travelY) {
-                        swipeListener.onSwipeUp();
-                    } else if (velocityY > swipeThreshold && absX < absY / 2 && deltaY > travelY) {
-                        swipeListener.onSwipeDown();
-                    }
-                }
-                return true;
-            }
         });
         gestureDetector.setIsLongpressEnabled(false);
     }
 
-    public void setSwipeListener(SwipeListener listener) {
-        swipeListener = listener;
-    }
-
     public void setSingleTapListener(SingleTapListener listener) {
         singleTapListener = listener;
     }
diff --git a/src/com/android/gallery3d/photoeditor/actions/VignetteAction.java b/src/com/android/gallery3d/photoeditor/actions/VignetteAction.java
index f59c636..9f6bcc7 100644
--- a/src/com/android/gallery3d/photoeditor/actions/VignetteAction.java
+++ b/src/com/android/gallery3d/photoeditor/actions/VignetteAction.java
@@ -28,35 +28,28 @@
 
     private static final float DEFAULT_SCALE = 0.5f;
 
-    private ScaleSeekBar scalePicker;
-
     public VignetteAction(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void doBegin() {
+    public void prepare() {
         final VignetteFilter filter = new VignetteFilter();
 
-        scalePicker = factory.createScalePicker(EffectToolFactory.ScalePickerType.GENERIC);
+        ScaleSeekBar scalePicker = toolKit.addScalePicker(EffectToolKit.ScaleType.GENERIC);
         scalePicker.setOnScaleChangeListener(new ScaleSeekBar.OnScaleChangeListener() {
 
             @Override
             public void onProgressChanged(float progress, boolean fromUser) {
                 if (fromUser) {
                     filter.setScale(progress);
-                    notifyFilterChanged(filter, true);
+                    notifyChanged(filter);
                 }
             }
         });
         scalePicker.setProgress(DEFAULT_SCALE);
 
         filter.setScale(DEFAULT_SCALE);
-        notifyFilterChanged(filter, true);
-    }
-
-    @Override
-    public void doEnd() {
-        scalePicker.setOnScaleChangeListener(null);
+        notifyChanged(filter);
     }
 }
diff --git a/src/com/android/gallery3d/photoeditor/filters/AbstractScaleFilter.java b/src/com/android/gallery3d/photoeditor/filters/AbstractScaleFilter.java
index 727a98c..6376d33 100644
--- a/src/com/android/gallery3d/photoeditor/filters/AbstractScaleFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/AbstractScaleFilter.java
@@ -30,7 +30,6 @@
      */
     public void setScale(float scale) {
         this.scale = scale;
-        validate();
     }
 
     @Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/CropFilter.java b/src/com/android/gallery3d/photoeditor/filters/CropFilter.java
index ccca813..00a6c42 100644
--- a/src/com/android/gallery3d/photoeditor/filters/CropFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/CropFilter.java
@@ -37,7 +37,6 @@
      */
     public void setCropBounds(RectF bounds) {
         this.bounds = bounds;
-        validate();
     }
 
     @Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java b/src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java
index e82a667..bc233da 100644
--- a/src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/CrossProcessFilter.java
@@ -27,10 +27,6 @@
 
     public static final Creator<CrossProcessFilter> CREATOR = creatorOf(CrossProcessFilter.class);
 
-    public CrossProcessFilter() {
-        validate();
-    }
-
     @Override
     public void process(Photo src, Photo dst) {
         getEffect(EffectFactory.EFFECT_CROSSPROCESS).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java b/src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java
index d6f347b..d2e4c7c 100644
--- a/src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/DocumentaryFilter.java
@@ -27,10 +27,6 @@
 
     public static final Creator<DocumentaryFilter> CREATOR = creatorOf(DocumentaryFilter.class);
 
-    public DocumentaryFilter() {
-        validate();
-    }
-
     @Override
     public void process(Photo src, Photo dst) {
         getEffect(EffectFactory.EFFECT_DOCUMENTARY).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java b/src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java
index 277e06d..61920d3 100644
--- a/src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/DoodleFilter.java
@@ -40,14 +40,6 @@
 
     private final Vector<Doodle> doodles = new Vector<Doodle>();
 
-    /**
-     * Signals once at least a doodle drawn within photo bounds; this filter is regarded as invalid
-     * (no-op on the photo) until not all its doodling is out of bounds.
-     */
-    public void setDoodledInPhotoBounds() {
-        validate();
-    }
-
     public void addDoodle(Doodle doodle) {
         doodles.add(doodle);
     }
diff --git a/src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java b/src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java
index b94f95e..b2c5525 100644
--- a/src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/DuotoneFilter.java
@@ -35,7 +35,6 @@
     public void setDuotone(int firstColor, int secondColor) {
         this.firstColor = firstColor;
         this.secondColor = secondColor;
-        validate();
     }
 
     @Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/Filter.java b/src/com/android/gallery3d/photoeditor/filters/Filter.java
index baa3747..5d1ac22 100644
--- a/src/com/android/gallery3d/photoeditor/filters/Filter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/Filter.java
@@ -26,8 +26,7 @@
 import java.util.HashMap;
 
 /**
- * Image filter for photo editing; most of its methods must be called from a single GL thread except
- * validate()/isValid() that are called from UI thread.
+ * Image filter for photo editing; all of its methods must be called from a single GL thread.
  */
 public abstract class Filter implements Parcelable {
 
@@ -37,8 +36,6 @@
     private static final HashMap<Filter, Effect> effects = new HashMap<Filter, Effect>();
     private static EffectContext context;
 
-    private boolean isValid;
-
     /**
      * Filter context should be released before the current GL context is lost.
      */
@@ -74,18 +71,6 @@
         return effect;
     }
 
-    protected void validate() {
-        isValid = true;
-    }
-
-    /**
-     * Some filters, e.g. lighting filters, are initially invalid until set up with parameters while
-     * others, e.g. Sepia or Posterize filters, are initially valid without parameters.
-     */
-    public boolean isValid() {
-        return isValid;
-    }
-
     /**
      * Processes the source bitmap and matrix and output the destination bitmap and matrix.
      *
diff --git a/src/com/android/gallery3d/photoeditor/filters/FlipFilter.java b/src/com/android/gallery3d/photoeditor/filters/FlipFilter.java
index 816aad8..9c325c1 100644
--- a/src/com/android/gallery3d/photoeditor/filters/FlipFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/FlipFilter.java
@@ -34,7 +34,6 @@
     public void setFlip(boolean flipHorizontal, boolean flipVertical) {
         flips[0] = flipHorizontal;
         flips[1] = flipVertical;
-        validate();
     }
 
     @Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java b/src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java
index 38dfb52..b0e94ef 100644
--- a/src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/GrayscaleFilter.java
@@ -27,10 +27,6 @@
 
     public static final Creator<GrayscaleFilter> CREATOR = creatorOf(GrayscaleFilter.class);
 
-    public GrayscaleFilter() {
-        validate();
-    }
-
     @Override
     public void process(Photo src, Photo dst) {
         getEffect(EffectFactory.EFFECT_GRAYSCALE).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java b/src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java
index f8c5173..16a1d61 100644
--- a/src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/LomoishFilter.java
@@ -27,10 +27,6 @@
 
     public static final Creator<LomoishFilter> CREATOR = creatorOf(LomoishFilter.class);
 
-    public LomoishFilter() {
-        validate();
-    }
-
     @Override
     public void process(Photo src, Photo dst) {
         getEffect(EffectFactory.EFFECT_LOMOISH).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java b/src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java
index 88bbd58..db702d7 100644
--- a/src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/NegativeFilter.java
@@ -27,10 +27,6 @@
 
     public static final Creator<NegativeFilter> CREATOR = creatorOf(NegativeFilter.class);
 
-    public NegativeFilter() {
-        validate();
-    }
-
     @Override
     public void process(Photo src, Photo dst) {
         getEffect(EffectFactory.EFFECT_NEGATIVE).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java b/src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java
index 186baa9..23e78bf 100644
--- a/src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/PosterizeFilter.java
@@ -27,10 +27,6 @@
 
     public static final Creator<PosterizeFilter> CREATOR = creatorOf(PosterizeFilter.class);
 
-    public PosterizeFilter() {
-        validate();
-    }
-
     @Override
     public void process(Photo src, Photo dst) {
         getEffect(EffectFactory.EFFECT_POSTERIZE).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java b/src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java
index 257d322..32e8f7c 100644
--- a/src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/RedEyeFilter.java
@@ -39,7 +39,6 @@
      */
     public void addRedEyePosition(PointF point) {
         redeyes.add(point);
-        validate();
     }
 
     @Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/RotateFilter.java b/src/com/android/gallery3d/photoeditor/filters/RotateFilter.java
index d377f96..d820bda 100644
--- a/src/com/android/gallery3d/photoeditor/filters/RotateFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/RotateFilter.java
@@ -31,9 +31,11 @@
 
     private float degrees;
 
+    /**
+     * Sets rotation angle which must be multiples of 90 degrees.
+     */
     public void setAngle(float degrees) {
         this.degrees = degrees;
-        validate();
     }
 
     @Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java b/src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java
index 6c1a70e..d95c0d8 100644
--- a/src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/SepiaFilter.java
@@ -27,10 +27,6 @@
 
     public static final Creator<SepiaFilter> CREATOR = creatorOf(SepiaFilter.class);
 
-    public SepiaFilter() {
-        validate();
-    }
-
     @Override
     public void process(Photo src, Photo dst) {
         getEffect(EffectFactory.EFFECT_SEPIA).apply(
diff --git a/src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java b/src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java
index 90738f0..dffdf50 100644
--- a/src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/StraightenFilter.java
@@ -34,7 +34,6 @@
 
     public void setAngle(float degrees) {
         this.degrees = degrees;
-        validate();
     }
 
     @Override
diff --git a/src/com/android/gallery3d/photoeditor/filters/TintFilter.java b/src/com/android/gallery3d/photoeditor/filters/TintFilter.java
index af3d777..7a7463e 100644
--- a/src/com/android/gallery3d/photoeditor/filters/TintFilter.java
+++ b/src/com/android/gallery3d/photoeditor/filters/TintFilter.java
@@ -33,7 +33,6 @@
 
     public void setTint(int color) {
         this.color = color;
-        validate();
     }
 
     @Override
diff --git a/src/com/android/gallery3d/provider/GalleryProvider.java b/src/com/android/gallery3d/provider/GalleryProvider.java
index 4068d46..7cc4d5c 100644
--- a/src/com/android/gallery3d/provider/GalleryProvider.java
+++ b/src/com/android/gallery3d/provider/GalleryProvider.java
@@ -16,17 +16,6 @@
 
 package com.android.gallery3d.provider;
 
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.DownloadCache;
-import com.android.gallery3d.data.MediaItem;
-import com.android.gallery3d.data.MediaObject;
-import com.android.gallery3d.data.MtpImage;
-import com.android.gallery3d.data.Path;
-import com.android.gallery3d.picasasource.PicasaSource;
-import com.android.gallery3d.util.GalleryUtils;
-
 import android.content.ContentProvider;
 import android.content.ContentValues;
 import android.content.Context;
@@ -39,6 +28,17 @@
 import android.provider.MediaStore.Images.ImageColumns;
 import android.util.Log;
 
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.data.DownloadCache;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.MtpImage;
+import com.android.gallery3d.data.Path;
+import com.android.gallery3d.picasasource.PicasaSource;
+import com.android.gallery3d.util.GalleryUtils;
+
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -48,7 +48,15 @@
 
     public static final String AUTHORITY = "com.android.gallery3d.provider";
     public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);
+
+    public static interface PicasaColumns {
+        public static final String USER_ACCOUNT = "user_account";
+        public static final String PICASA_ID = "picasa_id";
+    }
+
     private static final String[] SUPPORTED_PICASA_COLUMNS = {
+            PicasaColumns.USER_ACCOUNT,
+            PicasaColumns.PICASA_ID,
             ImageColumns.DISPLAY_NAME,
             ImageColumns.SIZE,
             ImageColumns.MIME_TYPE,
@@ -170,7 +178,11 @@
 
         for (int i = 0, n = projection.length; i < n; ++i) {
             String column = projection[i];
-            if (ImageColumns.DISPLAY_NAME.equals(column)) {
+            if (PicasaColumns.USER_ACCOUNT.equals(column)) {
+                columnValues[i] = PicasaSource.getUserAccount(getContext(), image);
+            } else if (PicasaColumns.PICASA_ID.equals(column)) {
+                columnValues[i] = PicasaSource.getPicasaId(image);
+            } else if (ImageColumns.DISPLAY_NAME.equals(column)) {
                 columnValues[i] = PicasaSource.getImageTitle(image);
             } else if (ImageColumns.SIZE.equals(column)){
                 columnValues[i] = PicasaSource.getImageSize(image);
diff --git a/src/com/android/gallery3d/ui/ScrollerHelper.java b/src/com/android/gallery3d/ui/ScrollerHelper.java
index 8423518..0fa85ee 100644
--- a/src/com/android/gallery3d/ui/ScrollerHelper.java
+++ b/src/com/android/gallery3d/ui/ScrollerHelper.java
@@ -84,7 +84,8 @@
     // Returns the distance that over the scroll limit.
     public int startScroll(int distance, int min, int max) {
         int currPosition = mScroller.getCurrX();
-        int finalPosition = mScroller.getFinalX();
+        int finalPosition = mScroller.isFinished() ? currPosition :
+                mScroller.getFinalX();
         int newPosition = Utils.clamp(finalPosition + distance, min, max);
         if (newPosition != currPosition) {
             mScroller.startScroll(
diff --git a/src/com/android/gallery3d/util/MediaSetUtils.java b/src/com/android/gallery3d/util/MediaSetUtils.java
index 817ffed..8a556aa 100644
--- a/src/com/android/gallery3d/util/MediaSetUtils.java
+++ b/src/com/android/gallery3d/util/MediaSetUtils.java
@@ -34,6 +34,9 @@
     public static final int IMPORTED_BUCKET_ID = GalleryUtils.getBucketId(
             Environment.getExternalStorageDirectory().toString() + "/"
             + MtpContext.NAME_IMPORTED_FOLDER);
+    public static final int SNAPSHOT_BUCKET_ID = GalleryUtils.getBucketId(
+            Environment.getExternalStorageDirectory().toString() +
+            "/Pictures/Screenshots");
 
     private static final Path[] CAMERA_PATHS = {
             Path.fromString("/local/all/" + CAMERA_BUCKET_ID),
diff --git a/src_pd/com/android/gallery3d/picasasource/PicasaSource.java b/src_pd/com/android/gallery3d/picasasource/PicasaSource.java
index 8cfdac3..f849067 100644
--- a/src_pd/com/android/gallery3d/picasasource/PicasaSource.java
+++ b/src_pd/com/android/gallery3d/picasasource/PicasaSource.java
@@ -118,6 +118,14 @@
         throw new UnsupportedOperationException();
     }
 
+    public static long getPicasaId(MediaObject image) {
+        throw new UnsupportedOperationException();
+    }
+
+    public static String getUserAccount(Context context, MediaObject image) {
+        throw new UnsupportedOperationException();
+    }
+
     public static ParcelFileDescriptor openFile(Context context, MediaObject image, String mode)
             throws FileNotFoundException {
         throw new UnsupportedOperationException();