Merge "Dejank camera roll scrolling in grid view." into gb-ub-photos-arches
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ef16d66..31bcd49 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
-<manifest android:versionCode="40000"
-        android:versionName="1.1.40000"
+<manifest android:versionCode="40001"
+        android:versionName="1.1.40001"
         xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.gallery3d">
 
diff --git a/src/com/android/gallery3d/app/GalleryActionBar.java b/src/com/android/gallery3d/app/GalleryActionBar.java
index 620add6..4b9ef53 100644
--- a/src/com/android/gallery3d/app/GalleryActionBar.java
+++ b/src/com/android/gallery3d/app/GalleryActionBar.java
@@ -380,8 +380,6 @@
     }
 
     private Menu mActionBarMenu;
-    private MenuItem mSharePanoramaMenuItem;
-    private MenuItem mShareMenuItem;
     private ShareActionProvider mSharePanoramaActionProvider;
     private ShareActionProvider mShareActionProvider;
 
@@ -389,15 +387,21 @@
         mActivity.getMenuInflater().inflate(menuRes, menu);
         mActionBarMenu = menu;
 
-        mSharePanoramaMenuItem = menu.findItem(R.id.action_share_panorama);
-        mSharePanoramaActionProvider = (ShareActionProvider)
-            mSharePanoramaMenuItem.getActionProvider();
-        mSharePanoramaActionProvider.setShareHistoryFileName("panorama_share_history.xml");
+        MenuItem item = menu.findItem(R.id.action_share_panorama);
+        if (item != null) {
+            mSharePanoramaActionProvider = (ShareActionProvider)
+                item.getActionProvider();
+            mSharePanoramaActionProvider
+                .setShareHistoryFileName("panorama_share_history.xml");
+        }
 
-        mShareMenuItem = menu.findItem(R.id.action_share);
-        mShareActionProvider = (ShareActionProvider)
-            mShareMenuItem.getActionProvider();
-        mShareActionProvider.setShareHistoryFileName("share_history.xml");
+        item = menu.findItem(R.id.action_share);
+        if (item != null) {
+            mShareActionProvider = (ShareActionProvider)
+                item.getActionProvider();
+            mShareActionProvider
+                .setShareHistoryFileName("share_history.xml");
+        }
     }
 
     public Menu getMenu() {
@@ -405,23 +409,10 @@
     }
 
     public void setShareIntents(Intent sharePanoramaIntent, Intent shareIntent) {
-        // if panorama sharing is enabled, rename share to share as photo,
-        // and move it to overflow
-        if (mSharePanoramaMenuItem != null) {
-            if (sharePanoramaIntent != null) {
-                mActivity.invalidateOptionsMenu();
-                mShareMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
-                mShareMenuItem.setTitle(
-                        mContext.getResources().getString(R.string.share_as_photo));
-            } else {
-                mSharePanoramaMenuItem.setVisible(false);
-                mShareMenuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
-                mShareMenuItem.setTitle(
-                        mContext.getResources().getString(R.string.share));
-            }
+        if (mSharePanoramaActionProvider != null) {
             mSharePanoramaActionProvider.setShareIntent(sharePanoramaIntent);
         }
-        if (mShareMenuItem != null) {
+        if (mShareActionProvider != null) {
             mShareActionProvider.setShareIntent(shareIntent);
         }
     }
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index 5c9c598..7f8414c 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -347,27 +347,29 @@
                         .getMediaObject(screenNailItemPath);
                 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
 
-                // Check if the path is a secure album.
-                if (SecureSource.isSecurePath(mSetPathString)) {
-                    mSecureAlbum = (SecureAlbum) mActivity.getDataManager()
-                            .getMediaSet(mSetPathString);
-                    mShowSpinner = false;
-                }
                 if (data.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) {
                     // Set the flag to be on top of the lock screen.
                     mFlags |= FLAG_SHOW_WHEN_LOCKED;
                 }
 
-                // Don't display "empty album" action item for capture intents
-                if(!mSetPathString.equals("/local/all/0")) {
+                // Don't display "empty album" action item or panorama
+                // progress for capture intents.
+                if (!mSetPathString.equals("/local/all/0")) {
+                    // Check if the path is a secure album.
+                    if (SecureSource.isSecurePath(mSetPathString)) {
+                        mSecureAlbum = (SecureAlbum) mActivity.getDataManager()
+                                .getMediaSet(mSetPathString);
+                        mShowSpinner = false;
+                    } else {
+                        // Use lightcycle album to handle panorama progress if
+                        // the path is not a secure album.
+                        if (LightCycleHelper.hasLightCycleCapture(mActivity.getAndroidContext())) {
+                            mSetPathString = LightCycleHelper.wrapGalleryPath(mSetPathString);
+                        }
+                    }
                     mSetPathString = "/filter/empty/{"+mSetPathString+"}";
                 }
 
-                // Add support for showing panorama progress.
-                if (LightCycleHelper.hasLightCycleCapture(mActivity.getAndroidContext())) {
-                    mSetPathString = LightCycleHelper.wrapGalleryPath(mSetPathString);
-                }
-
                 // Combine the original MediaSet with the one for ScreenNail
                 // from AppBridge.
                 mSetPathString = "/combo/item/{" + screenNailSetPath +
@@ -645,6 +647,18 @@
             supportedOperations &= ~MediaObject.SUPPORT_EDIT;
         }
         MenuExecutor.updateMenuOperation(menu, supportedOperations);
+        if ((supportedOperations & MediaObject.SUPPORT_PANORAMA360) != 0) {
+            mActivity.invalidateOptionsMenu();
+            item = menu.findItem(R.id.action_share);
+            item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+            item.setTitle(
+                    mActivity.getResources().getString(R.string.share_as_photo));
+        } else if ((supportedOperations & MediaObject.SUPPORT_SHARE) != 0) {
+            item = menu.findItem(R.id.action_share);
+            item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+            item.setTitle(
+                    mActivity.getResources().getString(R.string.share));
+        }
     }
 
     private boolean canDoSlideShow() {
diff --git a/src/com/android/gallery3d/app/StitchingChangeListener.java b/src/com/android/gallery3d/app/StitchingChangeListener.java
index 980f145..40f59a5 100644
--- a/src/com/android/gallery3d/app/StitchingChangeListener.java
+++ b/src/com/android/gallery3d/app/StitchingChangeListener.java
@@ -16,12 +16,14 @@
 
 package com.android.gallery3d.app;
 
+import com.android.gallery3d.data.Path;
+
 import android.net.Uri;
 
 public interface StitchingChangeListener {
-    public void onStitchingQueued(String filePath);
+    public void onStitchingQueued(Path path);
 
-    public void onStitchingResult(String filePath, Uri uri);
+    public void onStitchingResult(Path path, Uri uri);
 
-    public void onStitchingProgress(String filePath, int progress);
+    public void onStitchingProgress(Path path, int progress);
 }
diff --git a/src/com/android/gallery3d/data/SecureAlbum.java b/src/com/android/gallery3d/data/SecureAlbum.java
index 382de5b..4e33cda 100644
--- a/src/com/android/gallery3d/data/SecureAlbum.java
+++ b/src/com/android/gallery3d/data/SecureAlbum.java
@@ -25,6 +25,7 @@
 
 import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.app.StitchingChangeListener;
+import com.android.gallery3d.app.StitchingProgressManager;
 import com.android.gallery3d.util.MediaSetUtils;
 
 import java.util.ArrayList;
@@ -43,9 +44,10 @@
     // The types of items in mAllItems. True is video and false is image.
     private ArrayList<Boolean> mAllItemTypes = new ArrayList<Boolean>();
     private ArrayList<Path> mExistingItems = new ArrayList<Path>();
-    private ArrayList<String> mStitchingFilePaths = new ArrayList<String>();
+    private ArrayList<Path> mStitchingItems = new ArrayList<Path>();
     private Context mContext;
     private DataManager mDataManager;
+    private StitchingProgressManager mStitchingProgressManager;
     private static final Uri[] mWatchUris =
         {Images.Media.EXTERNAL_CONTENT_URI, Video.Media.EXTERNAL_CONTENT_URI};
     private final ChangeNotifier mNotifier;
@@ -62,7 +64,8 @@
         mUnlockItem = unlock;
         mShowUnlockItem = (!isCameraBucketEmpty(Images.Media.EXTERNAL_CONTENT_URI)
                 || !isCameraBucketEmpty(Video.Media.EXTERNAL_CONTENT_URI));
-        application.getStitchingProgressManager().addChangeListener(this);
+        mStitchingProgressManager = application.getStitchingProgressManager();
+        mStitchingProgressManager.addChangeListener(this);
     }
 
     public void addMediaItem(boolean isVideo, int id) {
@@ -79,14 +82,31 @@
         mNotifier.fakeChange();
     }
 
+    // The sequence is stitching items, local media items, and unlock image.
     @Override
     public ArrayList<MediaItem> getMediaItem(int start, int count) {
-        if (start >= mExistingItems.size() + 1) {
+        int stitchingCount = mStitchingItems.size();
+        int existingCount = mExistingItems.size();
+        if (start >= stitchingCount + existingCount + 1) {
             return new ArrayList<MediaItem>();
         }
-        int end = Math.min(start + count, mExistingItems.size());
-        ArrayList<Path> subset = new ArrayList<Path>(
-                mExistingItems.subList(start, end));
+
+        // Add paths of requested stitching items.
+        int end = Math.min(start + count, stitchingCount + existingCount);
+        ArrayList<Path> subset = new ArrayList<Path>();
+        if (start < stitchingCount) {
+            subset.addAll(mStitchingItems.subList(
+                    start, Math.min(stitchingCount, end)));
+        }
+
+        // Add paths of requested local media items.
+        if (end >= stitchingCount) {
+            int existingStart = Math.max(0, start - stitchingCount);
+            int existingEnd = end - stitchingCount;
+            subset.addAll(mExistingItems.subList(existingStart, existingEnd));
+        }
+
+        // Convert paths to media items.
         final MediaItem[] buf = new MediaItem[end - start];
         ItemConsumer consumer = new ItemConsumer() {
             @Override
@@ -105,7 +125,8 @@
 
     @Override
     public int getMediaItemCount() {
-        return mExistingItems.size() + (mShowUnlockItem ? 1 : 0);
+        return (mStitchingItems.size() + mExistingItems.size()
+                + (mShowUnlockItem ? 1 : 0));
     }
 
     @Override
@@ -183,19 +204,24 @@
     }
 
     @Override
-    public void onStitchingQueued(String filePath) {
-        mStitchingFilePaths.add(filePath);
+    public void onStitchingQueued(Path path) {
+        mStitchingItems.add(path);
+        notifyContentChanged();
     }
 
     @Override
-    public void onStitchingResult(String filePath, Uri uri) {
-        if (mStitchingFilePaths.remove(filePath)) {
+    public void onStitchingResult(Path path, Uri uri) {
+        if (mStitchingItems.remove(path)) {
             int id = Integer.parseInt(uri.getLastPathSegment());
             addMediaItem(false, id);
+            notifyContentChanged();
         }
     }
 
     @Override
-    public void onStitchingProgress(String filePath, int progress) {
+    public void onStitchingProgress(Path path, int progress) {
+        if (mStitchingItems.contains(path)) {
+            notifyContentChanged();
+        }
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index 02ffe75..f2fb3da 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -8,6 +8,7 @@
 import android.content.ContentValues;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.graphics.Color;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
@@ -32,6 +33,7 @@
 import com.android.gallery3d.filtershow.cache.ImageLoader;
 import com.android.gallery3d.filtershow.filters.ImageFilter;
 import com.android.gallery3d.filtershow.filters.ImageFilterBorder;
+import com.android.gallery3d.filtershow.filters.ImageFilterParametricBorder;
 import com.android.gallery3d.filtershow.filters.ImageFilterRS;
 import com.android.gallery3d.filtershow.imageshow.ImageBorder;
 import com.android.gallery3d.filtershow.imageshow.ImageCrop;
@@ -329,6 +331,22 @@
     }
 
     @Override
+    public void onPause() {
+        super.onPause();
+        if (mShareActionProvider != null) {
+            mShareActionProvider.setOnShareTargetSelectedListener(null);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        if (mShareActionProvider != null) {
+            mShareActionProvider.setOnShareTargetSelectedListener(this);
+        }
+    }
+
+    @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.undoButton: {
@@ -400,29 +418,24 @@
         // TODO: use listview
         // TODO: load the borders straight from the filesystem
         int p = 0;
-        ImageFilter[] borders = new ImageFilter[8];
+        ImageFilter[] borders = new ImageFilter[7];
         borders[p++] = new ImageFilterBorder(null);
 
+        borders[p++] = new ImageFilterParametricBorder(Color.WHITE, 100, 0);
+        borders[p++] = new ImageFilterParametricBorder(Color.BLACK, 100, 0);
+        borders[p++] = new ImageFilterParametricBorder(Color.WHITE, 100, 100);
+        borders[p++] = new ImageFilterParametricBorder(Color.BLACK, 100, 100);
         Drawable npd3 = getResources().getDrawable(R.drawable.filtershow_border_film3);
         borders[p++] = new ImageFilterBorder(npd3);
         Drawable npd = getResources().getDrawable(
                 R.drawable.filtershow_border_scratch3);
         borders[p++] = new ImageFilterBorder(npd);
-        Drawable npd2 = getResources().getDrawable(R.drawable.filtershow_border_black);
-        borders[p++] = new ImageFilterBorder(npd2);
-        Drawable npd6 = getResources().getDrawable(
-                R.drawable.filtershow_border_rounded_black);
-        borders[p++] = new ImageFilterBorder(npd6);
-        Drawable npd4 = getResources().getDrawable(R.drawable.filtershow_border_white);
-        borders[p++] = new ImageFilterBorder(npd4);
-        Drawable npd5 = getResources().getDrawable(
-                R.drawable.filtershow_border_rounded_white);
-        borders[p++] = new ImageFilterBorder(npd5);
 
         for (int i = 0; i < p; i++) {
             ImageSmallFilter filter = new ImageSmallFilter(getBaseContext());
             filter.setImageFilter(borders[i]);
             filter.setController(this);
+            filter.setBorder(true);
             filter.setImageLoader(mImageLoader);
             filter.setShowTitle(false);
             listBorders.addView(filter);
@@ -595,18 +608,19 @@
         invalidateViews();
     }
 
-    public void useImageFilter(ImageFilter imageFilter) {
+    public void useImageFilter(ImageFilter imageFilter, boolean setBorder) {
         if (imageFilter == null) {
             return;
         }
         ImagePreset oldPreset = mImageShow.getImagePreset();
         ImagePreset copy = new ImagePreset(oldPreset);
         // TODO: use a numerical constant instead.
-        if (imageFilter.getName().equalsIgnoreCase("Border")) {
-            copy.remove("Border");
+        if (setBorder) {
             copy.setHistoryName("Border");
+            copy.setBorder(imageFilter);
+        } else {
+            copy.add(imageFilter);
         }
-        copy.add(imageFilter);
         mImageShow.setImagePreset(copy);
         invalidateViews();
     }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java
new file mode 100644
index 0000000..2ead047
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterParametricBorder.java
@@ -0,0 +1,79 @@
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.RectF;
+
+public class ImageFilterParametricBorder extends ImageFilter {
+    private int mBorderColor = Color.WHITE;
+    private int mBorderSize = 10;
+    private int mBorderCornerRadius = 10;
+
+    public ImageFilterParametricBorder() {
+        mName = "Border";
+    }
+
+    public ImageFilterParametricBorder(int color, int size, int radius) {
+        setBorder(color, size, radius);
+    }
+
+    @Override
+    public ImageFilter clone() throws CloneNotSupportedException {
+        ImageFilterParametricBorder filter = (ImageFilterParametricBorder) super.clone();
+        filter.setBorder(mBorderColor, mBorderSize, mBorderCornerRadius);
+        return filter;
+    }
+
+    @Override
+    public boolean same(ImageFilter filter) {
+        boolean isBorderFilter = super.same(filter);
+        if (!isBorderFilter) {
+            return false;
+        }
+        ImageFilterParametricBorder borderFilter = (ImageFilterParametricBorder) filter;
+        if (borderFilter.mBorderColor != mBorderColor) {
+            return false;
+        }
+        if (borderFilter.mBorderSize != mBorderSize) {
+            return false;
+        }
+        if (borderFilter.mBorderCornerRadius != mBorderCornerRadius) {
+            return false;
+        }
+        return true;
+    }
+
+    public void setBorder(int color, int size, int radius) {
+        mBorderColor = color;
+        mBorderSize = size;
+        mBorderCornerRadius = radius;
+    }
+
+    @Override
+    public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) {
+        Canvas canvas = new Canvas(bitmap);
+        Path border = new Path();
+        border.moveTo(0, 0);
+        int w = bitmap.getWidth();
+        int h = bitmap.getHeight();
+        float bs = mBorderSize * scaleFactor;
+        float r = mBorderCornerRadius * scaleFactor;
+        border.lineTo(0, h);
+        border.lineTo(w, h);
+        border.lineTo(w, 0);
+        border.lineTo(0, 0);
+        border.addRoundRect(new RectF(bs, bs, w - bs, h - bs),
+                r, r, Path.Direction.CW);
+
+        Paint paint = new Paint();
+        paint.setAntiAlias(true);
+        paint.setColor(mBorderColor);
+        canvas.drawPath(border, paint);
+        return bitmap;
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java
index 401d96f..162d3ab 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java
@@ -1,25 +1,22 @@
 
 package com.android.gallery3d.filtershow.imageshow;
 
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.util.AttributeSet;
+import android.view.View;
+
 import com.android.gallery3d.filtershow.FilterShowActivity;
 import com.android.gallery3d.filtershow.filters.ImageFilter;
 import com.android.gallery3d.filtershow.presets.ImagePreset;
 
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.View.MeasureSpec;
-
 public class ImageSmallFilter extends ImageShow implements View.OnClickListener {
 
     private static final String LOGTAG = "ImageSmallFilter";
     private FilterShowActivity mController = null;
     private ImageFilter mImageFilter = null;
     private boolean mShowTitle = true;
+    private boolean mSetBorder = false;
 
     public ImageSmallFilter(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -37,26 +34,33 @@
         mImagePreset.add(mImageFilter);
     }
 
+    public void setBorder(boolean value) {
+        mSetBorder = value;
+    }
+
     public void setController(FilterShowActivity activity) {
         mController = activity;
     }
 
+    @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
         int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
         setMeasuredDimension(parentHeight, parentHeight);
     }
 
+    @Override
     public void onClick(View v) {
         if (mController != null) {
             if (mImageFilter != null) {
-                mController.useImageFilter(mImageFilter);
+                mController.useImageFilter(mImageFilter, mSetBorder);
             } else if (mImagePreset != null) {
                 mController.useImagePreset(mImagePreset);
             }
         }
     }
 
+    @Override
     public Bitmap getOriginalFrontBitmap() {
         if (mImageLoader == null) {
             return null;
@@ -69,14 +73,17 @@
         invalidate();
     }
 
+    @Override
     public boolean showTitle() {
         return mShowTitle;
     }
 
+    @Override
     public boolean showControls() {
         return false;
     }
 
+    @Override
     public boolean showHires() {
         return false;
     }
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
index 830f3cb..a7b4b40 100644
--- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
@@ -2,12 +2,10 @@
 package com.android.gallery3d.filtershow.presets;
 
 import android.graphics.Bitmap;
-import android.graphics.RectF;
 import android.util.Log;
 
 import com.android.gallery3d.filtershow.ImageStateAdapter;
 import com.android.gallery3d.filtershow.filters.ImageFilter;
-import com.android.gallery3d.filtershow.filters.ImageFilterStraighten;
 import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
 import com.android.gallery3d.filtershow.imageshow.ImageShow;
 
@@ -16,32 +14,32 @@
 public class ImagePreset {
 
     private static final String LOGTAG = "ImagePreset";
-    ImageShow mEndPoint = null;
+
+    private ImageShow mEndPoint = null;
+    private ImageFilter mImageBorder = null;
+    private float mScaleFactor = 1.0f;
+    private boolean mIsHighQuality = false;
+
     protected Vector<ImageFilter> mFilters = new Vector<ImageFilter>();
     protected String mName = "Original";
     protected String mHistoryName = "Original";
     protected boolean mIsFxPreset = false;
 
+    public final GeometryMetadata mGeoData = new GeometryMetadata();
+
     enum FullRotate {
         ZERO, NINETY, HUNDRED_EIGHTY, TWO_HUNDRED_SEVENTY
     }
 
-    // This is where the geometry metadata lives now.
-    public final GeometryMetadata mGeoData = new GeometryMetadata();
-
-    public void setGeometry(GeometryMetadata m) {
-        mGeoData.set(m);
-    }
-
-    private float mScaleFactor = 1.0f;
-    private boolean mIsHighQuality = false;
-
     public ImagePreset() {
         setup();
     }
 
     public ImagePreset(ImagePreset source) {
         try {
+            if (source.mImageBorder != null) {
+                mImageBorder = source.mImageBorder.clone();
+            }
             for (int i = 0; i < source.mFilters.size(); i++) {
                 add(source.mFilters.elementAt(i).clone());
             }
@@ -55,6 +53,14 @@
         mGeoData.set(source.mGeoData);
     }
 
+    public void setGeometry(GeometryMetadata m) {
+        mGeoData.set(m);
+    }
+
+    public void setBorder(ImageFilter filter) {
+        mImageBorder = filter;
+    }
+
     public boolean isFx() {
         return mIsFxPreset;
     }
@@ -83,6 +89,15 @@
         if (!mGeoData.equals(preset.mGeoData)) {
             return false;
         }
+
+        if (mImageBorder != preset.mImageBorder) {
+            return false;
+        }
+
+        if (mImageBorder != null && !mImageBorder.same(preset.mImageBorder)) {
+            return false;
+        }
+
         for (int i = 0; i < preset.mFilters.size(); i++) {
             ImageFilter a = preset.mFilters.elementAt(i);
             ImageFilter b = mFilters.elementAt(i);
@@ -138,20 +153,15 @@
         // First we apply any transform -- 90 rotate, flip, straighten, crop
         Bitmap bitmap = mGeoData.apply(original, mScaleFactor, mIsHighQuality);
 
-        // TODO -- apply borders separately
-        ImageFilter borderFilter = null;
+        if (mImageBorder != null) {
+            bitmap = mImageBorder.apply(bitmap, mScaleFactor, mIsHighQuality);
+        }
+
         for (int i = 0; i < mFilters.size(); i++) {
             ImageFilter filter = mFilters.elementAt(i);
-            if (filter.getName().equalsIgnoreCase("Border")) {
-                // TODO don't use the name as an id
-                borderFilter = filter;
-            } else {
-                bitmap = filter.apply(bitmap, mScaleFactor, mIsHighQuality);
-            }
+            bitmap = filter.apply(bitmap, mScaleFactor, mIsHighQuality);
         }
-        if (borderFilter != null) {
-            bitmap = borderFilter.apply(bitmap, mScaleFactor, mIsHighQuality);
-        }
+
         if (mEndPoint != null) {
             mEndPoint.updateFilteredImage(bitmap);
         }