Merge "Update Uri instead of removing" into gb-ub-photos-carlsbad
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 07cb6ce..2ef71ed 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -68,6 +68,7 @@
     public static final String SECURE_CAMERA_EXTRA = "secure_camera";
 
     private CameraDataAdapter mDataAdapter;
+    private PanoramaStitchingManager mPanoramaManager;
     private int mCurrentModuleIndex;
     private CameraModule mCurrentModule;
     private View mRootView;
@@ -149,6 +150,22 @@
             }
         };
 
+    private ImageTaskManager.TaskListener mStitchingListener =
+            new ImageTaskManager.TaskListener() {
+                @Override
+                public void onTaskQueued(String filePath, Uri imageUri) {
+                }
+
+                @Override
+                public void onTaskDone(String filePath, Uri imageUri) {
+                }
+
+                @Override
+                public void onTaskProgress(
+                        String filePath, Uri imageUri, int progress) {
+                }
+            };
+
     public MediaSaveService getMediaSaveService() {
         return mMediaSaveService;
     }
@@ -219,6 +236,8 @@
             IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
             registerReceiver(mScreenOffReceiver, filter);
         }
+        mPanoramaManager = new PanoramaStitchingManager(CameraActivity.this);
+        mPanoramaManager.addTaskListener(mStitchingListener);
         LayoutInflater inflater = getLayoutInflater();
         View rootLayout = inflater.inflate(R.layout.camera, null, false);
         mRootView = rootLayout.findViewById(R.id.camera_app_root);
@@ -277,6 +296,13 @@
         super.onResume();
         mCurrentModule.onResumeAfterSuper();
 
+        setSwipingEnabled(true);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
         // The loading is done in background and will update the filmstrip later.
         if (!mSecureCamera) {
             mDataAdapter.requestLoad(getContentResolver());
@@ -293,7 +319,6 @@
                             v.getDrawable().getIntrinsicHeight(),
                             0, 0));
         }
-        setSwipingEnabled(true);
     }
 
     @Override
diff --git a/src/com/android/camera/ImageTaskManager.java b/src/com/android/camera/ImageTaskManager.java
new file mode 100644
index 0000000..1324942
--- /dev/null
+++ b/src/com/android/camera/ImageTaskManager.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.net.Uri;
+
+/**
+ * The interface for background image processing task manager.
+ */
+interface ImageTaskManager {
+
+    /**
+     * Callback interface for task events.
+     */
+    public interface TaskListener {
+        public void onTaskQueued(String filePath, Uri imageUri);
+        public void onTaskDone(String filePath, Uri imageUri);
+        public void onTaskProgress(
+                String filePath, Uri imageUri, int progress);
+    }
+
+    public void addTaskListener(TaskListener l);
+
+    public void removeTaskListener(TaskListener l);
+
+    /**
+     * Get task progress by Uri.
+     *
+     * @param uri         The Uri of the final image file to identify the task.
+     * @return            Integer from 0 to 100, or -1. The percentage of the task done
+     *                    so far. -1 means not found.
+     */
+    public int getTaskProgress(Uri uri);
+}
diff --git a/src/com/android/camera/MediaSaveService.java b/src/com/android/camera/MediaSaveService.java
index e37b45c..40675b8 100644
--- a/src/com/android/camera/MediaSaveService.java
+++ b/src/com/android/camera/MediaSaveService.java
@@ -36,15 +36,16 @@
  * Service for saving images in the background thread.
  */
 public class MediaSaveService extends Service {
-    private static final int SAVE_TASK_LIMIT = 3;
-    private static final String TAG = MediaSaveService.class.getSimpleName();
+    // The memory limit for unsaved image is 20MB.
+    private static final int SAVE_TASK_MEMORY_LIMIT = 20 * 1024 * 1024;
+    private static final String TAG = "CAM_" + MediaSaveService.class.getSimpleName();
 
     private final IBinder mBinder = new LocalBinder();
-    private int mTaskNumber;
     private Listener mListener;
+    // Memory used by the total queued save request, in bytes.
+    private long mMemoryUse;
 
     interface Listener {
-
         public void onQueueStatus(boolean full);
     }
 
@@ -74,14 +75,13 @@
 
     @Override
     public void onCreate() {
-        mTaskNumber = 0;
+        mMemoryUse = 0;
     }
 
     public boolean isQueueFull() {
-        return (mTaskNumber >= SAVE_TASK_LIMIT);
+        return (mMemoryUse >= SAVE_TASK_MEMORY_LIMIT);
     }
 
-    // Runs in main thread
     public void addImage(final byte[] data, String title, long date, Location loc,
             int width, int height, int orientation, ExifInterface exif,
             OnMediaSavedListener l, ContentResolver resolver) {
@@ -93,13 +93,20 @@
                 (loc == null) ? null : new Location(loc),
                 width, height, orientation, exif, resolver, l);
 
-        mTaskNumber++;
+        mMemoryUse += data.length;
         if (isQueueFull()) {
             onQueueFull();
         }
         t.execute();
     }
 
+    public void addImage(final byte[] data, String title, Location loc,
+            int width, int height, int orientation, ExifInterface exif,
+            OnMediaSavedListener l, ContentResolver resolver) {
+        addImage(data, title, System.currentTimeMillis(), loc, width, height,
+                orientation, exif, l, resolver);
+    }
+
     public void addVideo(String path, long duration, ContentValues values,
             OnMediaSavedListener l, ContentResolver resolver) {
         // We don't set a queue limit for video saving because the file
@@ -161,8 +168,9 @@
         @Override
         protected void onPostExecute(Uri uri) {
             if (listener != null) listener.onMediaSaved(uri);
-            mTaskNumber--;
-            if (mTaskNumber == SAVE_TASK_LIMIT - 1) onQueueAvailable();
+            boolean previouslyFull = isQueueFull();
+            mMemoryUse -= data.length;
+            if (isQueueFull() != previouslyFull) onQueueAvailable();
         }
     }
 
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java
new file mode 100644
index 0000000..345c2f1
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterCropRepresentation.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.graphics.RectF;
+import android.util.JsonReader;
+import android.util.JsonWriter;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorCrop;
+
+import java.io.IOException;
+
+public class FilterCropRepresentation extends FilterRepresentation {
+    public static final String SERIALIZATION_NAME = "CROP";
+    public static final String[] BOUNDS = {
+            "C0", "C1", "C2", "C3", "I0", "I1", "I2", "I3"
+    };
+    private static final String TAG = FilterCropRepresentation.class.getSimpleName();
+
+    RectF mCrop = new RectF();
+    RectF mImage = new RectF();
+
+    public FilterCropRepresentation(RectF crop, RectF image) {
+        super(FilterCropRepresentation.class.getSimpleName());
+        setSerializationName(SERIALIZATION_NAME);
+        setShowParameterValue(true);
+        setFilterClass(FilterCropRepresentation.class);
+        setFilterType(FilterRepresentation.TYPE_GEOMETRY);
+        setTextId(R.string.crop);
+        setEditorId(EditorCrop.ID);
+        setCrop(crop);
+        setImage(image);
+    }
+
+    public FilterCropRepresentation(FilterCropRepresentation m) {
+        this(m.getCrop(), m.getImage());
+    }
+
+    public FilterCropRepresentation() {
+        this(new RectF(), new RectF());
+    }
+
+    public void set(FilterCropRepresentation r) {
+        mCrop.set(r.mCrop);
+        mImage.set(r.mImage);
+    }
+
+    public RectF getCrop() {
+        return new RectF(mCrop);
+    }
+
+    public void getCrop(RectF r) {
+        r.set(mCrop);
+    }
+
+    public void setCrop(RectF crop) {
+        if (crop == null) {
+            throw new IllegalArgumentException("Argument to setCrop is null");
+        }
+        mCrop.set(crop);
+    }
+
+    public RectF getImage() {
+        return new RectF(mImage);
+    }
+
+    public void getImage(RectF r) {
+        r.set(mImage);
+    }
+
+    public void setImage(RectF image) {
+        if (image == null) {
+            throw new IllegalArgumentException("Argument to setImage is null");
+        }
+        mImage.set(image);
+    }
+
+    @Override
+    public boolean allowsSingleInstanceOnly() {
+        return true;
+    }
+
+    @Override
+    public FilterRepresentation copy(){
+        return new FilterCropRepresentation(this);
+    }
+
+    @Override
+    protected void copyAllParameters(FilterRepresentation representation) {
+        if (!(representation instanceof FilterCropRepresentation)) {
+            throw new IllegalArgumentException("calling copyAllParameters with incompatible types!");
+        }
+        super.copyAllParameters(representation);
+        representation.useParametersFrom(this);
+    }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        if (!(a instanceof FilterCropRepresentation)) {
+            throw new IllegalArgumentException("calling useParametersFrom with incompatible types!");
+        }
+        setCrop(((FilterCropRepresentation) a).mCrop);
+        setImage(((FilterCropRepresentation) a).mImage);
+    }
+
+    @Override
+    public boolean isNil() {
+        return mCrop.equals(mImage);
+    }
+
+    @Override
+    public void serializeRepresentation(JsonWriter writer) throws IOException {
+        writer.beginObject();
+        writer.name(BOUNDS[0]).value(mCrop.left);
+        writer.name(BOUNDS[1]).value(mCrop.top);
+        writer.name(BOUNDS[2]).value(mCrop.right);
+        writer.name(BOUNDS[3]).value(mCrop.bottom);
+        writer.name(BOUNDS[4]).value(mImage.left);
+        writer.name(BOUNDS[5]).value(mImage.top);
+        writer.name(BOUNDS[6]).value(mImage.right);
+        writer.name(BOUNDS[7]).value(mImage.bottom);
+        writer.endObject();
+    }
+
+    @Override
+    public void deSerializeRepresentation(JsonReader reader) throws IOException {
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String name = reader.nextName();
+            if (BOUNDS[0].equals(name)) {
+                mCrop.left = (float) reader.nextDouble();
+            } else if (BOUNDS[1].equals(name)) {
+                mCrop.top = (float) reader.nextDouble();
+            } else if (BOUNDS[2].equals(name)) {
+                mCrop.right = (float) reader.nextDouble();
+            } else if (BOUNDS[3].equals(name)) {
+                mCrop.bottom = (float) reader.nextDouble();
+            } else if (BOUNDS[4].equals(name)) {
+                mImage.left = (float) reader.nextDouble();
+            } else if (BOUNDS[5].equals(name)) {
+                mImage.top = (float) reader.nextDouble();
+            } else if (BOUNDS[6].equals(name)) {
+                mImage.right = (float) reader.nextDouble();
+            } else if (BOUNDS[7].equals(name)) {
+                mImage.bottom = (float) reader.nextDouble();
+            } else {
+                reader.skipValue();
+            }
+        }
+        reader.endObject();
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterMirrorRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterMirrorRepresentation.java
new file mode 100644
index 0000000..0acf70e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterMirrorRepresentation.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorFlip;
+
+import java.io.IOException;
+
+public class FilterMirrorRepresentation extends FilterRepresentation {
+    public static final String SERIALIZATION_NAME = "MIRROR";
+    private static final String SERIALIZATION_MIRROR_VALUE = "value";
+    private static final String TAG = FilterMirrorRepresentation.class.getSimpleName();
+
+    Mirror mMirror = Mirror.NONE;
+
+    public enum Mirror {
+        NONE('N'), VERTICAL('V'), HORIZONTAL('H'), BOTH('B');
+        char mValue;
+
+        private Mirror(char value) {
+            mValue = value;
+        }
+
+        public char value() {
+            return mValue;
+        }
+
+        public static Mirror fromValue(char value) {
+            switch (value) {
+                case 'N':
+                    return NONE;
+                case 'V':
+                    return VERTICAL;
+                case 'H':
+                    return HORIZONTAL;
+                case 'B':
+                    return BOTH;
+                default:
+                    return null;
+            }
+        }
+    }
+
+    public FilterMirrorRepresentation(Mirror mirror) {
+        super(FilterMirrorRepresentation.class.getSimpleName());
+        setSerializationName(SERIALIZATION_NAME);
+        setShowParameterValue(true);
+        setFilterClass(FilterMirrorRepresentation.class);
+        setFilterType(FilterRepresentation.TYPE_GEOMETRY);
+        setTextId(R.string.mirror);
+        setEditorId(EditorFlip.ID);
+        setMirror(mirror);
+    }
+
+    public FilterMirrorRepresentation(FilterMirrorRepresentation m) {
+        this(m.getMirror());
+    }
+
+    public FilterMirrorRepresentation() {
+        this(Mirror.NONE);
+    }
+
+    public Mirror getMirror() {
+        return mMirror;
+    }
+
+    public void set(FilterMirrorRepresentation r) {
+        mMirror = r.mMirror;
+    }
+
+    public void setMirror(Mirror mirror) {
+        if (mirror == null) {
+            throw new IllegalArgumentException("Argument to setMirror is null");
+        }
+        mMirror = mirror;
+    }
+
+    @Override
+    public boolean allowsSingleInstanceOnly() {
+        return true;
+    }
+
+    @Override
+    public FilterRepresentation copy() {
+        return new FilterMirrorRepresentation(this);
+    }
+
+    @Override
+    protected void copyAllParameters(FilterRepresentation representation) {
+        if (!(representation instanceof FilterMirrorRepresentation)) {
+            throw new IllegalArgumentException("calling copyAllParameters with incompatible types!");
+        }
+        super.copyAllParameters(representation);
+        representation.useParametersFrom(this);
+    }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        if (!(a instanceof FilterMirrorRepresentation)) {
+            throw new IllegalArgumentException("calling useParametersFrom with incompatible types!");
+        }
+        setMirror(((FilterMirrorRepresentation) a).getMirror());
+    }
+
+    @Override
+    public boolean isNil() {
+        return mMirror == Mirror.NONE;
+    }
+
+    @Override
+    public void serializeRepresentation(JsonWriter writer) throws IOException {
+        writer.beginObject();
+        writer.name(SERIALIZATION_MIRROR_VALUE).value(mMirror.value());
+        writer.endObject();
+    }
+
+    @Override
+    public void deSerializeRepresentation(JsonReader reader) throws IOException {
+        boolean unset = true;
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String name = reader.nextName();
+            if (SERIALIZATION_MIRROR_VALUE.equals(name)) {
+                Mirror r = Mirror.fromValue((char) reader.nextInt());
+                if (r != null) {
+                    setMirror(r);
+                    unset = false;
+                }
+            } else {
+                reader.skipValue();
+            }
+        }
+        if (unset) {
+            Log.w(TAG, "WARNING: bad value when deserializing " + SERIALIZATION_NAME);
+        }
+        reader.endObject();
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
index ca9587c..5b33ffb 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
@@ -45,6 +45,7 @@
     public static final byte TYPE_VIGNETTE = 4;
     public static final byte TYPE_NORMAL = 5;
     public static final byte TYPE_TINYPLANET = 6;
+    public static final byte TYPE_GEOMETRY = 7;
     protected static final String NAME_TAG = "Name";
 
     public FilterRepresentation(String name) {
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRotateRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRotateRepresentation.java
new file mode 100644
index 0000000..8f7d8eb
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRotateRepresentation.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorRotate;
+
+import java.io.IOException;
+
+public class FilterRotateRepresentation extends FilterRepresentation {
+    public static final String SERIALIZATION_NAME = "ROTATION";
+    public static final String SERIALIZATION_ROTATE_VALUE = "value";
+    private static final String TAG = FilterRotateRepresentation.class.getSimpleName();
+
+    Rotation mRotation = Rotation.ZERO;
+
+    public enum Rotation {
+        ZERO(0), NINETY(90), ONE_EIGHTY(180), TWO_SEVENTY(270);
+        private final int mValue;
+
+        private Rotation(int value) {
+            mValue = value;
+        }
+
+        public int value() {
+            return mValue;
+        }
+
+        public static Rotation fromValue(int value) {
+            switch (value) {
+                case 0:
+                    return ZERO;
+                case 90:
+                    return NINETY;
+                case 180:
+                    return ONE_EIGHTY;
+                case 270:
+                    return TWO_SEVENTY;
+                default:
+                    return null;
+            }
+        }
+    }
+
+    public FilterRotateRepresentation(Rotation rotation) {
+        super(FilterRotateRepresentation.class.getSimpleName());
+        setSerializationName(SERIALIZATION_NAME);
+        setShowParameterValue(true);
+        setFilterClass(FilterRotateRepresentation.class);
+        setFilterType(FilterRepresentation.TYPE_GEOMETRY);
+        setTextId(R.string.rotate);
+        setEditorId(EditorRotate.ID);
+        setRotation(rotation);
+    }
+
+    public FilterRotateRepresentation(FilterRotateRepresentation r) {
+        this(r.getRotation());
+    }
+
+    public FilterRotateRepresentation() {
+        this(Rotation.ZERO);
+    }
+
+    public Rotation getRotation() {
+        return mRotation;
+    }
+
+    public void set(FilterRotateRepresentation r) {
+        mRotation = r.mRotation;
+    }
+
+    public void setRotation(Rotation rotation) {
+        if (rotation == null) {
+            throw new IllegalArgumentException("Argument to setRotation is null");
+        }
+        mRotation = rotation;
+    }
+
+    @Override
+    public boolean allowsSingleInstanceOnly() {
+        return true;
+    }
+
+    @Override
+    public FilterRepresentation copy() {
+        return new FilterRotateRepresentation(this);
+    }
+
+    @Override
+    protected void copyAllParameters(FilterRepresentation representation) {
+        if (!(representation instanceof FilterRotateRepresentation)) {
+            throw new IllegalArgumentException("calling copyAllParameters with incompatible types!");
+        }
+        super.copyAllParameters(representation);
+        representation.useParametersFrom(this);
+    }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        if (!(a instanceof FilterRotateRepresentation)) {
+            throw new IllegalArgumentException("calling useParametersFrom with incompatible types!");
+        }
+        setRotation(((FilterRotateRepresentation) a).getRotation());
+    }
+
+    @Override
+    public boolean isNil() {
+        return mRotation == Rotation.ZERO;
+    }
+
+    @Override
+    public void serializeRepresentation(JsonWriter writer) throws IOException {
+        writer.beginObject();
+        writer.name(SERIALIZATION_ROTATE_VALUE).value(mRotation.value());
+        writer.endObject();
+    }
+
+    @Override
+    public void deSerializeRepresentation(JsonReader reader) throws IOException {
+        boolean unset = true;
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String name = reader.nextName();
+            if (SERIALIZATION_ROTATE_VALUE.equals(name)) {
+                Rotation r = Rotation.fromValue(reader.nextInt());
+                if (r != null) {
+                    setRotation(r);
+                    unset = false;
+                }
+            } else {
+                reader.skipValue();
+            }
+        }
+        if (unset) {
+            Log.w(TAG, "WARNING: bad value when deserializing " + SERIALIZATION_NAME);
+        }
+        reader.endObject();
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterStraightenRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterStraightenRepresentation.java
new file mode 100644
index 0000000..a06216e
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FilterStraightenRepresentation.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.gallery3d.filtershow.filters;
+
+import android.util.JsonReader;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.editors.EditorStraighten;
+
+import java.io.IOException;
+
+public class FilterStraightenRepresentation extends FilterRepresentation {
+    public static final String SERIALIZATION_NAME = "STRAIGHTEN";
+    public static final String SERIALIZATION_STRAIGHTEN_VALUE = "value";
+    private static final String TAG = FilterStraightenRepresentation.class.getSimpleName();
+
+    float mStraighten;
+
+    public FilterStraightenRepresentation(float straighten) {
+        super(FilterStraightenRepresentation.class.getSimpleName());
+        setSerializationName(SERIALIZATION_NAME);
+        setShowParameterValue(true);
+        setFilterClass(FilterStraightenRepresentation.class);
+        setFilterType(FilterRepresentation.TYPE_GEOMETRY);
+        setTextId(R.string.straighten);
+        setEditorId(EditorStraighten.ID);
+        setStraighten(straighten);
+    }
+
+    public FilterStraightenRepresentation(FilterStraightenRepresentation s) {
+        this(s.getStraighten());
+    }
+
+    public FilterStraightenRepresentation() {
+        this(0);
+    }
+
+    public void set(FilterStraightenRepresentation r) {
+        mStraighten = r.mStraighten;
+    }
+
+    public float getStraighten() {
+        return mStraighten;
+    }
+
+    public void setStraighten(float straighten) {
+        if (!rangeCheck(straighten)) {
+            straighten = Math.min(Math.max(straighten, -45), 45);
+        }
+        mStraighten = straighten;
+    }
+
+    @Override
+    public boolean allowsSingleInstanceOnly() {
+        return true;
+    }
+
+    @Override
+    public FilterRepresentation copy() {
+        return new FilterStraightenRepresentation(this);
+    }
+
+    @Override
+    protected void copyAllParameters(FilterRepresentation representation) {
+        if (!(representation instanceof FilterStraightenRepresentation)) {
+            throw new IllegalArgumentException("calling copyAllParameters with incompatible types!");
+        }
+        super.copyAllParameters(representation);
+        representation.useParametersFrom(this);
+    }
+
+    @Override
+    public void useParametersFrom(FilterRepresentation a) {
+        if (!(a instanceof FilterStraightenRepresentation)) {
+            throw new IllegalArgumentException("calling useParametersFrom with incompatible types!");
+        }
+        setStraighten(((FilterStraightenRepresentation) a).getStraighten());
+    }
+
+    @Override
+    public boolean isNil() {
+        return mStraighten == 0;
+    }
+
+    @Override
+    public void serializeRepresentation(JsonWriter writer) throws IOException {
+        writer.beginObject();
+        writer.name(SERIALIZATION_STRAIGHTEN_VALUE).value(mStraighten);
+        writer.endObject();
+    }
+
+    @Override
+    public void deSerializeRepresentation(JsonReader reader) throws IOException {
+        boolean unset = true;
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String name = reader.nextName();
+            if (SERIALIZATION_STRAIGHTEN_VALUE.equals(name)) {
+                int s = reader.nextInt();
+                if (rangeCheck(s)) {
+                    setStraighten(s);
+                    unset = false;
+                }
+            } else {
+                reader.skipValue();
+            }
+        }
+        if (unset) {
+            Log.w(TAG, "WARNING: bad value when deserializing " + SERIALIZATION_NAME);
+        }
+        reader.endObject();
+    }
+
+    private boolean rangeCheck(float s) {
+        if (s < -45 || s > 45) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
index 4f46eed..3c323e1 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java
@@ -79,17 +79,6 @@
             Log.w(LOGTAG, "Cannot apply geometry: geometry metadata has not been initialized");
             return bitmap;
         }
-        CropExtras extras = mGeometry.getCropExtras();
-        boolean useExtras = mGeometry.getUseCropExtrasFlag();
-        int outputX = 0;
-        int outputY = 0;
-        boolean s = false;
-        if (extras != null && useExtras){
-            outputX = extras.getOutputX();
-            outputY = extras.getOutputY();
-            s = extras.getScaleUp();
-        }
-
 
         Rect cropBounds = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
         RectF crop = mGeometry.getCropBounds(bitmap);
@@ -105,20 +94,8 @@
             height = temp;
         }
 
-        if(outputX <= 0 || outputY <= 0){
-            outputX = width;
-            outputY = height;
-        }
-
-        float scaleX = 1;
-        float scaleY = 1;
-        if (s){
-                scaleX = (float) outputX / width;
-                scaleY = (float) outputY / height;
-        }
-
         Bitmap temp = null;
-        temp = Bitmap.createBitmap(outputX, outputY, mConfig);
+        temp = Bitmap.createBitmap(width, height, mConfig);
 
         float[] displayCenter = {
                 temp.getWidth() / 2f, temp.getHeight() / 2f
@@ -126,7 +103,7 @@
 
         Matrix m1 = mGeometry.buildTotalXform(bitmap.getWidth(), bitmap.getHeight(), displayCenter);
 
-        m1.postScale(scaleX, scaleY, displayCenter[0], displayCenter[1]);
+        m1.postScale(1, 1, displayCenter[0], displayCenter[1]);
 
         Canvas canvas = new Canvas(temp);
         Paint paint = new Paint();
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
index 64ced7e..42474df 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
@@ -18,71 +18,37 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
-import android.graphics.Rect;
 import android.graphics.RectF;
+import android.util.JsonReader;
+import android.util.JsonWriter;
 
 import com.android.gallery3d.filtershow.cache.ImageLoader;
-import com.android.gallery3d.filtershow.crop.CropExtras;
 import com.android.gallery3d.filtershow.editors.EditorCrop;
 import com.android.gallery3d.filtershow.editors.EditorFlip;
 import com.android.gallery3d.filtershow.editors.EditorRotate;
 import com.android.gallery3d.filtershow.editors.EditorStraighten;
+import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation.Mirror;
 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation.Rotation;
+import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
 import com.android.gallery3d.filtershow.filters.ImageFilterGeometry;
 
 import java.util.HashMap;
+import java.io.IOException;
 
 public class GeometryMetadata extends FilterRepresentation {
     public static final String SERIALIZATION_NAME = "GEOM";
+    public static final String SERIALIZATION_VALUE_SCALE = "scalevalue";
     private static final String LOGTAG = "GeometryMetadata";
     private float mScaleFactor = 1.0f;
-    private float mRotation = 0;
-    private float mStraightenRotation = 0;
-    private final RectF mCropBounds = new RectF();
-    private final RectF mPhotoBounds = new RectF();
-    private FLIP mFlip = FLIP.NONE;
 
-    public enum FLIP {
-        NONE("N"), VERTICAL("V"), HORIZONTAL("H"), BOTH("B");
-        String mValue;
-
-        FLIP(String name) {
-            mValue = name;
-        }
-
-        public static FLIP parse(String name){
-            switch (name.charAt(0)) {
-                case 'N':
-                    return NONE;
-                case 'V':
-                    return VERTICAL;
-                case 'H':
-                    return HORIZONTAL;
-                case 'B':
-                    return BOTH;
-            };
-            return NONE;
-        }
-    }
-
-    // Output format data from intent extras
-    private boolean mUseCropExtras = false;
-    private CropExtras mCropExtras = null;
-    public void setUseCropExtrasFlag(boolean f){
-        mUseCropExtras = f;
-    }
-
-    public boolean getUseCropExtrasFlag(){
-        return mUseCropExtras;
-    }
-
-    public void setCropExtras(CropExtras e){
-        mCropExtras = e;
-    }
-
-    public CropExtras getCropExtras(){
-        return mCropExtras;
-    }
+    private FilterRotateRepresentation mRotationRep = new FilterRotateRepresentation();
+    private FilterStraightenRepresentation mStraightenRep = new FilterStraightenRepresentation();
+    private FilterCropRepresentation mCropRep = new FilterCropRepresentation();
+    private FilterMirrorRepresentation mMirrorRep = new FilterMirrorRepresentation();
 
     public GeometryMetadata() {
         super("GeometryMetadata");
@@ -112,18 +78,16 @@
         if (mScaleFactor != 1.0f) {
             return true;
         }
-        if (mRotation != 0) {
+        if (!mRotationRep.isNil()) {
             return true;
         }
-        if (mStraightenRotation != 0) {
+        if (!mStraightenRep.isNil()) {
             return true;
         }
-        Rect cropBounds = GeometryMath.roundNearest(mCropBounds);
-        Rect photoBounds = GeometryMath.roundNearest(mPhotoBounds);
-        if (!cropBounds.equals(photoBounds)) {
+        if (!mCropRep.isNil()) {
             return true;
         }
-        if (!mFlip.equals(FLIP.NONE)) {
+        if (!mMirrorRep.isNil()) {
             return true;
         }
         return false;
@@ -131,132 +95,82 @@
 
     public void set(GeometryMetadata g) {
         mScaleFactor = g.mScaleFactor;
-        mRotation = g.mRotation;
-        mStraightenRotation = g.mStraightenRotation;
-        mCropBounds.set(g.mCropBounds);
-        mPhotoBounds.set(g.mPhotoBounds);
-        mFlip = g.mFlip;
-
-        mUseCropExtras = g.mUseCropExtras;
-        if (g.mCropExtras != null){
-            mCropExtras = new CropExtras(g.mCropExtras);
-        }
+        mRotationRep.set(g.mRotationRep);
+        mStraightenRep.set(g.mStraightenRep);
+        mCropRep.set(g.mCropRep);
+        mMirrorRep.set(g.mMirrorRep);
     }
 
     public float getScaleFactor() {
         return mScaleFactor;
     }
 
-    public float getRotation() {
-        return mRotation;
+    public int getRotation() {
+        return mRotationRep.getRotation().value();
     }
 
     public float getStraightenRotation() {
-        return mStraightenRotation;
+        return mStraightenRep.getStraighten();
     }
 
     public RectF getPreviewCropBounds() {
-        return new RectF(mCropBounds);
+        return mCropRep.getCrop();
     }
 
     public RectF getCropBounds(Bitmap bitmap) {
         float scale = 1.0f;
-        scale = GeometryMath.scale(mPhotoBounds.width(), mPhotoBounds.height(), bitmap.getWidth(),
+        RectF photoBounds = mCropRep.getImage();
+        RectF cropBounds = mCropRep.getCrop();
+        scale = GeometryMath.scale(photoBounds.width(), photoBounds.height(), bitmap.getWidth(),
                 bitmap.getHeight());
-        RectF croppedRegion = new RectF(mCropBounds.left * scale, mCropBounds.top * scale,
-                mCropBounds.right * scale, mCropBounds.bottom * scale);
+        RectF croppedRegion = new RectF(cropBounds.left * scale, cropBounds.top * scale,
+                cropBounds.right * scale, cropBounds.bottom * scale);
 
         // If no crop has been applied, make sure to use the exact size values.
         // Multiplying using scale will introduce rounding errors that modify
         // even un-cropped images.
-        if (mCropBounds.left == 0 && mCropBounds.right == mPhotoBounds.right) {
+        if (cropBounds.left == 0 && cropBounds.right == photoBounds.right) {
             croppedRegion.left = 0;
             croppedRegion.right = bitmap.getWidth();
         }
-        if (mCropBounds.top == 0 && mCropBounds.bottom == mPhotoBounds.bottom) {
+        if (cropBounds.top == 0 && cropBounds.bottom == photoBounds.bottom) {
             croppedRegion.top = 0;
             croppedRegion.bottom = bitmap.getHeight();
         }
         return croppedRegion;
     }
 
-    public FLIP getFlipType() {
-        return mFlip;
+    public Mirror getMirrorType() {
+        return mMirrorRep.getMirror();
+    }
+
+    public void setMirrorType(Mirror m) {
+        mMirrorRep.setMirror(m);
     }
 
     public RectF getPhotoBounds() {
-        return new RectF(mPhotoBounds);
+        return mCropRep.getImage();
     }
 
     public void setScaleFactor(float scale) {
         mScaleFactor = scale;
     }
 
-    public void setFlipType(FLIP flip) {
-        mFlip = flip;
-    }
-
-    public void setRotation(float rotation) {
-        mRotation = rotation;
+    public void setRotation(int rotation) {
+        Rotation r = Rotation.fromValue(rotation % 360);
+        mRotationRep.setRotation((r == null) ? Rotation.ZERO : r);
     }
 
     public void setStraightenRotation(float straighten) {
-        mStraightenRotation = straighten;
+        mStraightenRep.setStraighten(straighten);
     }
 
     public void setCropBounds(RectF newCropBounds) {
-        mCropBounds.set(newCropBounds);
+        mCropRep.setCrop(newCropBounds);
     }
 
     public void setPhotoBounds(RectF newPhotoBounds) {
-        mPhotoBounds.set(newPhotoBounds);
-    }
-
-    public boolean cropFitsInPhoto(RectF cropBounds) {
-        return mPhotoBounds.contains(cropBounds);
-    }
-
-    private boolean compareRectF(RectF a, RectF b) {
-        return ((int) a.left == (int) b.left)
-                && ((int) a.right == (int) b.right)
-                && ((int) a.top == (int) b.top)
-                && ((int) a.bottom == (int) b.bottom);
-    }
-
-    @Override
-    public boolean equals(FilterRepresentation o) {
-        if (this == o)
-            return true;
-        if (o == null || !(o instanceof GeometryMetadata))
-            return false;
-
-        GeometryMetadata d = (GeometryMetadata) o;
-        return (mScaleFactor == d.mScaleFactor
-                && mRotation == d.mRotation
-                && mStraightenRotation == d.mStraightenRotation
-                && mFlip == d.mFlip
-                && compareRectF(mCropBounds, d.mCropBounds)
-                && compareRectF(mPhotoBounds, d.mPhotoBounds));
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 23;
-        result = 31 * result + Float.floatToIntBits(mRotation);
-        result = 31 * result + Float.floatToIntBits(mStraightenRotation);
-        result = 31 * result + Float.floatToIntBits(mScaleFactor);
-        result = 31 * result + mFlip.hashCode();
-        result = 31 * result + mCropBounds.hashCode();
-        result = 31 * result + mPhotoBounds.hashCode();
-        return result;
-    }
-
-    @Override
-    public String toString() {
-        return getClass().getName() + "[" + "scale=" + mScaleFactor
-                + ",rotation=" + mRotation + ",flip=" + mFlip + ",straighten="
-                + mStraightenRotation + ",cropRect=" + mCropBounds.toShortString()
-                + ",photoRect=" + mPhotoBounds.toShortString() + "]";
+        mCropRep.setImage(newPhotoBounds);
     }
 
     protected static void concatHorizontalMatrix(Matrix m, float width) {
@@ -269,19 +183,18 @@
         m.postTranslate(0, height);
     }
 
-
-    public static void concatMirrorMatrix(Matrix m, float width, float height, FLIP type) {
-        if (type == FLIP.HORIZONTAL) {
+    public static void concatMirrorMatrix(Matrix m, float width, float height, Mirror type) {
+        if (type == Mirror.HORIZONTAL) {
             concatHorizontalMatrix(m, width);
-        } else if (type == FLIP.VERTICAL) {
+        } else if (type == Mirror.VERTICAL) {
             concatVerticalMatrix(m, height);
-        } else if (type == FLIP.BOTH) {
+        } else if (type == Mirror.BOTH) {
             concatVerticalMatrix(m, height);
             concatHorizontalMatrix(m, width);
         }
     }
 
-    public Matrix getMatrixOriginalOrientation(int orientation, float originalWidth,
+    public static Matrix getMatrixOriginalOrientation(int orientation, float originalWidth,
             float originalHeight) {
         Matrix imageRotation = new Matrix();
         switch (orientation) {
@@ -360,7 +273,7 @@
                 viewWidth / 2f, viewHeight / 2f
         };
         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
-                getRotation(), getStraightenRotation(), getFlipType(), displayCenter);
+                getRotation(), getStraightenRotation(), getMirrorType(), displayCenter);
         float[] cropCenter = {
                 scaledCrop.centerX(), scaledCrop.centerY()
         };
@@ -375,11 +288,11 @@
     }
 
     public boolean hasSwitchedWidthHeight() {
-        return (((int) (mRotation / 90)) % 2) != 0;
+        return (((int) (mRotationRep.getRotation().value() / 90)) % 2) != 0;
     }
 
     public static Matrix buildPhotoMatrix(RectF photo, RectF crop, float rotation,
-            float straighten, FLIP type) {
+            float straighten, Mirror type) {
         Matrix m = new Matrix();
         m.setRotate(straighten, photo.centerX(), photo.centerY());
         concatMirrorMatrix(m, photo.right, photo.bottom, type);
@@ -429,7 +342,7 @@
 
         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
                 getRotation(), getStraightenRotation(),
-                getFlipType(), displayCenter);
+                getMirrorType(), displayCenter);
         float[] cropCenter = {
                 scaledCrop.centerX(), scaledCrop.centerY()
         };
@@ -455,7 +368,7 @@
      * @return
      */
     public static Matrix buildCenteredPhotoMatrix(RectF photo, RectF crop, float rotation,
-            float straighten, FLIP type, float[] newCenter) {
+            float straighten, Mirror type, float[] newCenter) {
         Matrix m = buildPhotoMatrix(photo, crop, rotation, straighten, type);
         float[] center = {
                 photo.centerX(), photo.centerY()
@@ -497,7 +410,7 @@
      * @return
      */
     public static Matrix buildWanderingCropMatrix(RectF photo, RectF crop, float rotation,
-            float straighten, FLIP type, float[] newCenter) {
+            float straighten, Mirror type, float[] newCenter) {
         Matrix m = buildCenteredPhotoMatrix(photo, crop, rotation, straighten, type, newCenter);
         m.preRotate(-straighten, photo.centerX(), photo.centerY());
         return m;
@@ -522,86 +435,40 @@
         representation.useParametersFrom(this);
     }
 
-    private static final String[] sParams = {
-            "Name", "ScaleFactor", "Rotation", "StraightenRotation", "CropBoundsLeft",
-            "CropBoundsTop", "CropBoundsRight", "CropBoundsBottom", "PhotoBoundsLeft",
-            "PhotoBoundsTop", "PhotoBoundsRight", "PhotoBoundsBottom", "Flip"
-    };
-
     @Override
-    public String[][] serializeRepresentation() {
-        String[][] ret = {
-                { "Name", getName() },
-                { "ScaleFactor", Float.toString(mScaleFactor) },
-                { "Rotation", Float.toString(mRotation) },
-                { "StraightenRotation", Float.toString(mStraightenRotation) },
-                { "CropBoundsLeft", Float.toString(mCropBounds.left) },
-                { "CropBoundsTop", Float.toString(mCropBounds.top) },
-                { "CropBoundsRight", Float.toString(mCropBounds.right) },
-                { "CropBoundsBottom", Float.toString(mCropBounds.bottom) },
-                { "PhotoBoundsLeft", Float.toString(mPhotoBounds.left) },
-                { "PhotoBoundsTop", Float.toString(mPhotoBounds.top) },
-                { "PhotoBoundsRight", Float.toString(mPhotoBounds.right) },
-                { "PhotoBoundsBottom", Float.toString(mPhotoBounds.bottom) },
-                { "Flip", mFlip.mValue } };
-        return ret;
+    public void serializeRepresentation(JsonWriter writer) throws IOException {
+        writer.beginObject();
+        writer.name(FilterRotateRepresentation.SERIALIZATION_NAME);
+        mRotationRep.serializeRepresentation(writer);
+        writer.name(FilterMirrorRepresentation.SERIALIZATION_NAME);
+        mMirrorRep.serializeRepresentation(writer);
+        writer.name(FilterStraightenRepresentation.SERIALIZATION_NAME);
+        mStraightenRep.serializeRepresentation(writer);
+        writer.name(FilterCropRepresentation.SERIALIZATION_NAME);
+        mCropRep.serializeRepresentation(writer);
+        writer.name(SERIALIZATION_VALUE_SCALE).value(mScaleFactor);
+        writer.endObject();
     }
 
     @Override
-    public void deSerializeRepresentation(String[][] rep) {
-        HashMap<String, Integer> map = new HashMap<String, Integer>();
-        for (int i = 0; i < sParams.length; i++) {
-            map.put(sParams[i], i);
-        }
-        for (int i = 0; i < rep.length; i++) {
-            String key = rep[i][0];
-            String value = rep[i][1];
-
-            switch (map.get(key)) {
-                case -1: // Unknown
-                    break;
-                case 0:
-                    if (!getName().equals(value)) {
-                        throw new IllegalArgumentException("Not a "+getName());
-                    }
-                    break;
-                case 1: // "ScaleFactor", Float
-                    mScaleFactor = Float.parseFloat(value);
-                    break;
-                case 2: // "Rotation", Float
-                    mRotation = Float.parseFloat(value);
-                    break;
-                case 3: // "StraightenRotation", Float
-                    mStraightenRotation = Float.parseFloat(value);
-                    break;
-                case 4: // "mCropBoundsLeft", Float
-                    mCropBounds.left = Float.parseFloat(value);
-                    break;
-                case 5: // "mCropBoundsTop", Float
-                    mCropBounds.top = Float.parseFloat(value);
-                    break;
-                case 6: // "mCropBoundsRight", Float
-                    mCropBounds.right = Float.parseFloat(value);
-                    break;
-                case 7: // "mCropBoundsBottom", Float
-                    mCropBounds.bottom = Float.parseFloat(value);
-                    break;
-                case 8: // "mPhotoBoundsLeft", Float
-                    mPhotoBounds.left = Float.parseFloat(value);
-                    break;
-                case 9: // "mPhotoBoundsTop", Float
-                    mPhotoBounds.top = Float.parseFloat(value);
-                    break;
-                case 10: // "mPhotoBoundsRight", Float
-                    mPhotoBounds.right = Float.parseFloat(value);
-                    break;
-                case 11: // "mPhotoBoundsBottom", Float
-                    mPhotoBounds.bottom = Float.parseFloat(value);
-                    break;
-                case 12: // "Flip", enum
-                    mFlip = FLIP.parse(value);
-                    break;
+    public void deSerializeRepresentation(JsonReader reader) throws IOException {
+        reader.beginObject();
+        while (reader.hasNext()) {
+            String name = reader.nextName();
+            if (FilterRotateRepresentation.SERIALIZATION_NAME.equals(name)) {
+                mRotationRep.deSerializeRepresentation(reader);
+            } else if (FilterMirrorRepresentation.SERIALIZATION_NAME.equals(name)) {
+                mMirrorRep.deSerializeRepresentation(reader);
+            } else if (FilterStraightenRepresentation.SERIALIZATION_NAME.equals(name)) {
+                mStraightenRep.deSerializeRepresentation(reader);
+            } else if (FilterCropRepresentation.SERIALIZATION_NAME.equals(name)) {
+                mCropRep.deSerializeRepresentation(reader);
+            } else if (SERIALIZATION_VALUE_SCALE.equals(name)) {
+                mScaleFactor = (float) reader.nextDouble();
+            } else {
+                reader.skipValue();
             }
         }
+        reader.endObject();
     }
 }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
index 5094563..0c44065 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java
@@ -197,7 +197,7 @@
         RectF scaledPhoto = new RectF();
         float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter);
         Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
-                getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
+                getLocalRotation(), getLocalStraighten(), getLocalMirror(), displayCenter);
         m.preScale(scale, scale);
         return m;
     }
@@ -208,7 +208,7 @@
         RectF scaledPhoto = new RectF();
         float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter);
         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
-                getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
+                getLocalRotation(), getLocalStraighten(), getLocalMirror(), displayCenter);
         m1.preScale(scale, scale);
         return m1;
     }
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
index 0054af3..fb39a9a 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java
@@ -25,14 +25,14 @@
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.filtershow.editors.EditorFlip;
-import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP;
+import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation.Mirror;
 
 public class ImageFlip extends ImageGeometry {
 
     private static final Paint gPaint = new Paint();
-    private static final float MIN_FLICK_DIST_FOR_FLIP = 0.1f;
+    private static final float MIN_FLICK_DIST_FOR_FLIP= 0.1f;
     private static final String LOGTAG = "ImageFlip";
-    private FLIP mNextFlip = FLIP.NONE;
+    private Mirror mNextFlip = Mirror.NONE;
     private EditorFlip mEditorFlip;
 
     public ImageFlip(Context context, AttributeSet attrs) {
@@ -54,10 +54,10 @@
     }
 
     public void flip() {
-        FLIP flip = getLocalFlip();
+        Mirror flip = getLocalMirror();
         boolean next = true;
-        // Picks next flip in order from enum FLIP (wrapping)
-        for (FLIP f : FLIP.values()) {
+        // Picks next flip in order from enum Mirror (wrapping)
+        for (Mirror f : Mirror.values()) {
             if (next) {
                 mNextFlip = f;
                 next = false;
@@ -66,7 +66,7 @@
                 next = true;
             }
         }
-        setLocalFlip(mNextFlip);
+        setLocalMirror(mNextFlip);
     }
 
     @Override
@@ -83,44 +83,44 @@
         }
         if (Math.abs(diffx) >= flick) {
             // flick moving left/right
-            FLIP flip = getLocalFlip();
+            Mirror flip = getLocalMirror();
             switch (flip) {
                 case NONE:
-                    flip = FLIP.HORIZONTAL;
+                    flip = Mirror.HORIZONTAL;
                     break;
                 case HORIZONTAL:
-                    flip = FLIP.NONE;
+                    flip = Mirror.NONE;
                     break;
                 case VERTICAL:
-                    flip = FLIP.BOTH;
+                    flip = Mirror.BOTH;
                     break;
                 case BOTH:
-                    flip = FLIP.VERTICAL;
+                    flip = Mirror.VERTICAL;
                     break;
                 default:
-                    flip = FLIP.NONE;
+                    flip = Mirror.NONE;
                     break;
             }
             mNextFlip = flip;
         }
         if (Math.abs(diffy) >= flick) {
             // flick moving up/down
-            FLIP flip = getLocalFlip();
+            Mirror flip = getLocalMirror();
             switch (flip) {
                 case NONE:
-                    flip = FLIP.VERTICAL;
+                    flip = Mirror.VERTICAL;
                     break;
                 case VERTICAL:
-                    flip = FLIP.NONE;
+                    flip = Mirror.NONE;
                     break;
                 case HORIZONTAL:
-                    flip = FLIP.BOTH;
+                    flip = Mirror.BOTH;
                     break;
                 case BOTH:
-                    flip = FLIP.HORIZONTAL;
+                    flip = Mirror.HORIZONTAL;
                     break;
                 default:
-                    flip = FLIP.NONE;
+                    flip = Mirror.NONE;
                     break;
             }
             mNextFlip = flip;
@@ -130,13 +130,13 @@
     @Override
     protected void setActionUp() {
         super.setActionUp();
-        setLocalFlip(mNextFlip);
+        setLocalMirror(mNextFlip);
     }
 
     @Override
     public void resetParameter() {
         super.resetParameter();
-        mNextFlip = FLIP.NONE;
+        mNextFlip = Mirror.NONE;
     }
 
     private float getScaledMinFlick() {
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java
index 58225c8..7d6c787 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java
@@ -29,9 +29,9 @@
 import android.view.MotionEvent;
 import android.view.View;
 
-import com.android.gallery3d.filtershow.history.HistoryItem;
+import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation.Mirror;
 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
-import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP;
+import com.android.gallery3d.filtershow.history.HistoryItem;
 import com.android.gallery3d.filtershow.pipeline.ImagePreset;
 
 public abstract class ImageGeometry extends ImageShow {
@@ -133,7 +133,7 @@
         setLocalRotation(0);
         setLocalStraighten(0);
         setLocalCropBounds(getLocalPhotoBounds());
-        setLocalFlip(FLIP.NONE);
+        setLocalMirror(Mirror.NONE);
         saveAndSetPreset();
         invalidate();
     }
@@ -179,7 +179,7 @@
         setLocalScale(zoom);
     }
 
-    protected void setLocalRotation(float r) {
+    protected void setLocalRotation(int r) {
         mLocalGeometry.setRotation(r);
         updateScale();
     }
@@ -207,12 +207,12 @@
         updateScale();
     }
 
-    protected FLIP getLocalFlip() {
-        return mLocalGeometry.getFlipType();
+    protected Mirror getLocalMirror() {
+        return mLocalGeometry.getMirrorType();
     }
 
-    protected void setLocalFlip(FLIP flip) {
-        mLocalGeometry.setFlipType(flip);
+    protected void setLocalMirror(Mirror flip) {
+        mLocalGeometry.setMirrorType(flip);
     }
 
     protected float getTotalLocalRotation() {
@@ -458,10 +458,10 @@
         RectF scaledPhoto = new RectF();
         float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter);
         Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop,
-                getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
+                getLocalRotation(), getLocalStraighten(), getLocalMirror(), displayCenter);
 
         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
-                getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
+                getLocalRotation(), getLocalStraighten(), getLocalMirror(), displayCenter);
         m1.mapRect(scaledCrop);
         Path path = new Path();
         scaledCrop.offset(-offset[0], -offset[1]);
@@ -502,7 +502,7 @@
                 getWidth() / 2f, getHeight() / 2f
         };
         Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop,
-                getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter);
+                getLocalRotation(), getLocalStraighten(), getLocalMirror(), displayCenter);
         float[] cropCenter = {
                 scaledCrop.centerX(), scaledCrop.centerY()
         };
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
index b3c23cd..87b5d33 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java
@@ -53,7 +53,7 @@
         mAngle += 90;
         mAngle = snappedAngle(mAngle);
         mAngle %= 360;
-        setLocalRotation(mAngle);
+        setLocalRotation((int) mAngle);
     }
 
     @Override
@@ -66,7 +66,7 @@
     protected void setActionMove(float x, float y) {
         super.setActionMove(x, y);
         computeValue();
-        setLocalRotation(mAngle % 360);
+        setLocalRotation((int) mAngle % 360);
     }
 
     @Override
diff --git a/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java b/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java
index cc4938c..26dce37 100644
--- a/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java
+++ b/src/com/android/gallery3d/filtershow/pipeline/ImagePreset.java
@@ -26,9 +26,13 @@
 import com.android.gallery3d.R;
 import com.android.gallery3d.filtershow.cache.ImageLoader;
 import com.android.gallery3d.filtershow.filters.BaseFiltersManager;
+import com.android.gallery3d.filtershow.filters.FilterCropRepresentation;
 import com.android.gallery3d.filtershow.filters.FilterFxRepresentation;
 import com.android.gallery3d.filtershow.filters.FilterImageBorderRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterMirrorRepresentation;
 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRotateRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterStraightenRepresentation;
 import com.android.gallery3d.filtershow.filters.FiltersManager;
 import com.android.gallery3d.filtershow.filters.ImageFilter;
 import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
@@ -659,8 +663,17 @@
     }
 
     FilterRepresentation creatFilterFromName(String name) {
+        // TODO: move these to FiltersManager pattern.
         if (GeometryMetadata.SERIALIZATION_NAME.equalsIgnoreCase(name)) {
             return new GeometryMetadata();
+        } else if (FilterRotateRepresentation.SERIALIZATION_NAME.equals(name)) {
+            return new FilterRotateRepresentation();
+        } else if (FilterMirrorRepresentation.SERIALIZATION_NAME.equals(name)) {
+            return new FilterMirrorRepresentation();
+        } else if (FilterStraightenRepresentation.SERIALIZATION_NAME.equals(name)) {
+            return new FilterStraightenRepresentation();
+        } else if (FilterCropRepresentation.SERIALIZATION_NAME.equals(name)) {
+            return new FilterCropRepresentation();
         }
         FiltersManager filtersManager = FiltersManager.getManager();
         return filtersManager.createFilterFromName(name);
diff --git a/src_pd/com/android/camera/PanoramaStitchingManager.java b/src_pd/com/android/camera/PanoramaStitchingManager.java
new file mode 100644
index 0000000..5ba16b8
--- /dev/null
+++ b/src_pd/com/android/camera/PanoramaStitchingManager.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.content.Context;
+import android.net.Uri;
+
+class PanoramaStitchingManager implements ImageTaskManager {
+
+    public PanoramaStitchingManager(Context ctx) {
+    }
+
+    @Override
+    public void addTaskListener(TaskListener l) {
+        // do nothing.
+    }
+
+    @Override
+    public void removeTaskListener(TaskListener l) {
+        // do nothing.
+    }
+
+    @Override
+    public int getTaskProgress(Uri uri) {
+        return -1;
+    }
+}