Merge "Remove redudant setName" into gb-ub-photos-bryce
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java b/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java
index 2fef9ed..a1cf0fc 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java
@@ -20,6 +20,7 @@
 import android.graphics.BitmapFactory;
 import android.util.SparseIntArray;
 
+import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
@@ -758,7 +759,7 @@
         }
         InputStream is = null;
         try {
-            is = (InputStream) new FileInputStream(inFileName);
+            is = (InputStream) new BufferedInputStream(new FileInputStream(inFileName));
             readExif(is);
         } catch (IOException e) {
             closeSilently(is);
@@ -800,6 +801,7 @@
         }
         OutputStream s = getExifWriterStream(exifOutStream);
         s.write(jpeg, 0, jpeg.length);
+        s.flush();
     }
 
     /**
@@ -817,6 +819,7 @@
         }
         OutputStream s = getExifWriterStream(exifOutStream);
         bmap.compress(Bitmap.CompressFormat.JPEG, 90, s);
+        s.flush();
     }
 
     /**
@@ -834,6 +837,7 @@
         }
         OutputStream s = getExifWriterStream(exifOutStream);
         doExifStreamIO(jpegStream, s);
+        s.flush();
     }
 
     /**
@@ -1010,7 +1014,7 @@
         boolean ret;
         try {
             File temp = new File(filename);
-            is = new FileInputStream(temp);
+            is = new BufferedInputStream(new FileInputStream(temp));
 
             // Parse beginning of APP1 in exif to find size of exif header.
             ExifParser parser = null;
diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
index e5a5bf0..ae501a5 100644
--- a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
+++ b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java
@@ -18,6 +18,7 @@
 
 import android.util.Log;
 
+import java.io.BufferedOutputStream;
 import java.io.FilterOutputStream;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -58,6 +59,7 @@
 class ExifOutputStream extends FilterOutputStream {
     private static final String TAG = "ExifOutputStream";
     private static final boolean DEBUG = false;
+    private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
 
     private static final int STATE_SOI = 0;
     private static final int STATE_FRAME_HEADER = 1;
@@ -75,11 +77,12 @@
     private int mState = STATE_SOI;
     private int mByteToSkip;
     private int mByteToCopy;
+    private byte[] mSingleByteArray = new byte[1];
     private ByteBuffer mBuffer = ByteBuffer.allocate(4);
     private final ExifInterface mInterface;
 
     protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
-        super(ou);
+        super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
         mInterface = iRef;
     }
 
@@ -190,10 +193,8 @@
      */
     @Override
     public void write(int oneByte) throws IOException {
-        byte[] buf = new byte[] {
-            (byte) (0xff & oneByte)
-        };
-        write(buf);
+        mSingleByteArray[0] = (byte) (0xff & oneByte);
+        write(mSingleByteArray);
     }
 
     /**
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 3813cce..1215cec 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -118,7 +118,6 @@
 
     public void init() {
         boolean landscape = Util.getDisplayRotation(this) % 180 == 90;
-        setMargins(landscape);
         mControlsBackground = findViewById(R.id.blocker);
         mCameraControls = findViewById(R.id.camera_controls);
         mShutter = (ShutterButton) findViewById(R.id.shutter_button);
@@ -318,24 +317,9 @@
     @Override
     public void onConfigurationChanged(Configuration config) {
         super.onConfigurationChanged(config);
-        boolean landscape = (config.orientation == Configuration.ORIENTATION_LANDSCAPE);
-        setMargins(landscape);
         mCurrentModule.onConfigurationChanged(config);
     }
 
-    private void setMargins(boolean landscape) {
-        ViewGroup appRoot = (ViewGroup) findViewById(R.id.content);
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) appRoot.getLayoutParams();
-        int navBarWidth = getResources().getDimensionPixelSize(R.dimen.navigation_bar_width);
-        int navBarHeight = getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
-        if (landscape) {
-            lp.setMargins(navBarHeight, 0, navBarHeight - navBarWidth, 0);
-        } else {
-            lp.setMargins(0, navBarHeight, 0, 0);
-        }
-        appRoot.setLayoutParams(lp);
-    }
-
     @Override
     public void onPause() {
         mPaused = true;
diff --git a/src/com/android/camera/CameraManager.java b/src/com/android/camera/CameraManager.java
index be82ea6..b354654 100644
--- a/src/com/android/camera/CameraManager.java
+++ b/src/com/android/camera/CameraManager.java
@@ -74,7 +74,6 @@
     private static final int ENABLE_SHUTTER_SOUND = 24;
 
     private Handler mCameraHandler;
-    private CameraProxy mCameraProxy;
     private android.hardware.Camera mCamera;
 
     // This holder is used when we need to pass the exception
@@ -139,7 +138,6 @@
                     case RELEASE:
                         mCamera.release();
                         mCamera = null;
-                        mCameraProxy = null;
                         return;
 
                     case RECONNECT:
@@ -226,11 +224,15 @@
                         return;
 
                     case SET_PARAMETERS:
+                        mParametersIsDirty = true;
                         mCamera.setParameters((Parameters) msg.obj);
                         return;
 
                     case GET_PARAMETERS:
-                        mParameters = mCamera.getParameters();
+                        if (mParametersIsDirty) {
+                            mParameters = mCamera.getParameters();
+                            mParametersIsDirty = false;
+                        }
                         return;
 
                     case SET_PARAMETERS_ASYNC:
@@ -256,7 +258,6 @@
                         Log.e(TAG, "Fail to release the camera.");
                     }
                     mCamera = null;
-                    mCameraProxy = null;
                 }
                 throw e;
             }
@@ -280,9 +281,8 @@
         // a view hierarchy can touch its views.
         mCamera = android.hardware.Camera.open(cameraId);
         if (mCamera != null) {
-            mCameraProxy = new CameraProxy();
             mParametersIsDirty = true;
-            return mCameraProxy;
+            return new CameraProxy();
         } else {
             return null;
         }
@@ -334,8 +334,11 @@
             mCameraHandler.sendEmptyMessage(START_PREVIEW_ASYNC);
         }
 
+        // stopPreview() is synchronous because many resources should be released after
+        // the preview is stopped.
         public void stopPreview() {
             mCameraHandler.sendEmptyMessage(STOP_PREVIEW);
+            waitDone();
         }
 
         public void setPreviewCallback(final PreviewCallback cb) {
@@ -424,7 +427,6 @@
                 Log.v(TAG, "null parameters in setParameters()");
                 return;
             }
-            mParametersIsDirty = true;
             mCameraHandler.obtainMessage(SET_PARAMETERS, params).sendToTarget();
         }
 
@@ -434,16 +436,13 @@
                 Log.v(TAG, "null parameters in setParameters()");
                 return;
             }
-            mParametersIsDirty = true;
             mCameraHandler.removeMessages(SET_PARAMETERS_ASYNC);
             mCameraHandler.obtainMessage(SET_PARAMETERS_ASYNC, params).sendToTarget();
         }
 
         public Parameters getParameters() {
-            if (mParametersIsDirty || mParameters == null) {
-                mCameraHandler.sendEmptyMessage(GET_PARAMETERS);
-                if (waitDone()) mParametersIsDirty = false;
-            }
+            mCameraHandler.sendEmptyMessage(GET_PARAMETERS);
+            waitDone();
             return mParameters;
         }
 
diff --git a/src/com/android/camera/PhotoMenu.java b/src/com/android/camera/PhotoMenu.java
index 280ad44..92396c7 100644
--- a/src/com/android/camera/PhotoMenu.java
+++ b/src/com/android/camera/PhotoMenu.java
@@ -34,6 +34,15 @@
         TimerSettingPopup.Listener,
         ListPrefSettingPopup.Listener {
     private static String TAG = "CAM_photomenu";
+
+    private static final int POS_HDR = 0;
+    private static final int POS_EXP = 1;
+    private static final int POS_MORE = 2;
+    private static final int POS_FLASH = 3;
+    private static final int POS_SWITCH = 4;
+    private static final int POS_WB = 1;
+    private static final int POS_SET = 2;
+
     private final String mSettingOff;
 
     private PhotoUI mUI;
@@ -53,20 +62,19 @@
         super.initialize(group);
         mPopup = null;
         mSecondPopup = null;
-        float sweep = (float) (SWEEP * Math.PI);
         PieItem item = null;
         // flash
         if (group.findPreference(CameraSettings.KEY_FLASH_MODE) != null) {
-            item = makeItem(CameraSettings.KEY_FLASH_MODE, CENTER - sweep, sweep);
+            item = makeItem(CameraSettings.KEY_FLASH_MODE, POS_FLASH, 5);
             mRenderer.addItem(item);
         }
         // exposure compensation
-        item = makeItem(CameraSettings.KEY_EXPOSURE, CENTER + sweep, sweep);
+        item = makeItem(CameraSettings.KEY_EXPOSURE, POS_EXP, 5);
         mRenderer.addItem(item);
         // camera switcher
         if (group.findPreference(CameraSettings.KEY_CAMERA_ID) != null) {
             item = makeItem(R.drawable.ic_switch_photo_facing_holo_light);
-            item.setFixedSlice(CENTER - 2 * sweep, sweep);
+            item.setPosition(POS_SWITCH, 5);
             item.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(PieItem item) {
@@ -88,7 +96,7 @@
         // hdr
         if (group.findPreference(CameraSettings.KEY_CAMERA_HDR) != null) {
             item = makeItem(R.drawable.ic_hdr);
-            item.setFixedSlice(CENTER + 2 * sweep, sweep);
+            item.setPosition(POS_HDR, 5);
             item.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(PieItem item) {
@@ -108,11 +116,10 @@
 
         // more settings
         PieItem more = makeItem(R.drawable.ic_settings_holo_light);
-        more.setFixedSlice(CENTER, sweep);
+        more.setPosition(POS_MORE, 5);
         mRenderer.addItem(more);
         // white balance
-        item = makeItem(CameraSettings.KEY_WHITE_BALANCE,
-                CENTER + sweep, sweep);
+        item = makeItem(CameraSettings.KEY_WHITE_BALANCE, POS_WB, 5);
         more.addItem(item);
         // settings popup
         mOtherKeys = new String[] {
@@ -124,7 +131,7 @@
                 CameraSettings.KEY_TIMER_SOUND_EFFECTS,
                 };
         item = makeItem(R.drawable.ic_settings_holo_light);
-        item.setFixedSlice(CENTER, sweep);
+        item.setPosition(POS_SET, 5);
         item.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(PieItem item) {
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index f4bd4ce..22bd650 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -1013,6 +1013,8 @@
 
         mFaceDetectionStarted = false;
         setCameraState(SNAPSHOT_IN_PROGRESS);
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                UsageStatistics.ACTION_CAPTURE_DONE, "Photo");
         return true;
     }
 
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index 1af870a..29ebd6a 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -115,6 +115,8 @@
                 h = width;
             }
             if (mPreviewWidth != w || mPreviewHeight != h) {
+                mPreviewWidth = w;
+                mPreviewHeight = h;
                 mController.onScreenSizeChanged(width, height, w, h);
             }
         }
diff --git a/src/com/android/camera/PieController.java b/src/com/android/camera/PieController.java
index 2145fd8..c5d1b8b 100644
--- a/src/com/android/camera/PieController.java
+++ b/src/com/android/camera/PieController.java
@@ -37,10 +37,6 @@
     protected static final int MODE_PHOTO = 0;
     protected static final int MODE_VIDEO = 1;
 
-    protected static float CENTER = (float) Math.PI / 2;
-    protected static final float SWEEP = 0.06f;
-
-
     protected CameraActivity mActivity;
     protected PreferenceGroup mPreferenceGroup;
     protected OnPreferenceChangedListener mListener;
@@ -88,7 +84,7 @@
         return new PieItem(drawable, 0);
     }
 
-    public PieItem makeItem(String prefKey, float center, float sweep) {
+    public PieItem makeItem(String prefKey, int position, int count) {
         final IconListPreference pref =
                 (IconListPreference) mPreferenceGroup.findPreference(prefKey);
         if (pref == null) return null;
@@ -103,8 +99,7 @@
             resid = pref.getSingleIcon();
         }
         PieItem item = makeItem(resid);
-        // use center and sweep to determine layout
-        item.setFixedSlice(center, sweep);
+        item.setPosition(position, count);
         mPreferences.add(pref);
         mPreferenceMap.put(pref, item);
         int nOfEntries = pref.getEntries().length;
@@ -116,7 +111,7 @@
                 } else {
                     inner = makeItem(pref.getEntries()[i]);
                 }
-                layoutInner(inner, i, nOfEntries);
+                inner.setPosition(i, nOfEntries);
                 item.addItem(inner);
                 final int index = i;
                 inner.setOnClickListener(new OnClickListener() {
@@ -137,14 +132,8 @@
         return item;
     }
 
-    protected void layoutInner(PieItem item, int ix, int n) {
-        float sweep = (float) (SWEEP * Math.PI);//FLOAT_PI_DIVIDED_BY_TWO / Math.max(n, 5);
-        float start = CENTER + (n - 1) * (sweep / 2f);
-        item.setFixedSlice(start - ix * sweep, sweep);
-    }
-
-    public void addItem(String prefKey, float center, float sweep) {
-        PieItem item = makeItem(prefKey, center, sweep);
+    public void addItem(String prefKey, int position, int count) {
+        PieItem item = makeItem(prefKey, position, count);
         mRenderer.addItem(item);
     }
 
diff --git a/src/com/android/camera/VideoMenu.java b/src/com/android/camera/VideoMenu.java
index 0f987aa..d9629d3 100644
--- a/src/com/android/camera/VideoMenu.java
+++ b/src/com/android/camera/VideoMenu.java
@@ -34,6 +34,10 @@
         TimeIntervalPopup.Listener {
 
     private static String TAG = "CAM_VideoMenu";
+    private static final int POS_WB = 1;
+    private static final int POS_SET = 2;
+    private static final int POS_FLASH = 3;
+    private static final int POS_SWITCH = 4;
 
     private VideoUI mUI;
     private String[] mOtherKeys;
@@ -53,13 +57,14 @@
         super.initialize(group);
         mPopup = null;
         mPopupStatus = POPUP_NONE;
-        float sweep = (float)(SWEEP * Math.PI);
 
-        addItem(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, CENTER - sweep, sweep);
-        addItem(CameraSettings.KEY_WHITE_BALANCE, CENTER + sweep, sweep);
+        PieItem item = makeItem(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, POS_FLASH, 5);
+        mRenderer.addItem(item);
+        item = makeItem(CameraSettings.KEY_WHITE_BALANCE, POS_WB, 5);
+        mRenderer.addItem(item);
         // camera switcher
-        PieItem item = makeItem(R.drawable.ic_switch_video_facing_holo_light);
-        item.setFixedSlice(CENTER - 2 * sweep, sweep);
+        item = makeItem(R.drawable.ic_switch_video_facing_holo_light);
+        item.setPosition(POS_SWITCH, 5);
         item.setOnClickListener(new OnClickListener() {
 
             @Override
@@ -84,7 +89,7 @@
                 CameraSettings.KEY_RECORD_LOCATION
         };
         item = makeItem(R.drawable.ic_settings_holo_light);
-        item.setFixedSlice(CENTER, sweep);
+        item.setPosition(POS_SET, 5);
         item.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(PieItem item) {
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 1f31778..09a406c 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -450,6 +450,8 @@
         mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc));
         showVideoSnapshotUI(true);
         mSnapshotInProgress = true;
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
     }
 
     @Override
@@ -1547,6 +1549,8 @@
 
         updateRecordingTime();
         keepScreenOn();
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                UsageStatistics.ACTION_CAPTURE_START, "Video");
     }
 
     private void showCaptureResult() {
@@ -1662,6 +1666,10 @@
         // Update the parameters here because the parameters might have been altered
         // by MediaRecorder.
         if (!mPaused) mParameters = mActivity.mCameraDevice.getParameters();
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
+                    UsageStatistics.ACTION_CAPTURE_DONE, "Video",
+                    SystemClock.uptimeMillis() - mRecordingStartTime);
         return fail;
     }
 
diff --git a/src/com/android/camera/ui/CameraControls.java b/src/com/android/camera/ui/CameraControls.java
new file mode 100644
index 0000000..a7d1f21
--- /dev/null
+++ b/src/com/android/camera/ui/CameraControls.java
@@ -0,0 +1,83 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout.LayoutParams;
+
+import com.android.camera.Util;
+import com.android.gallery3d.R;
+
+public class CameraControls extends RotatableLayout
+{
+    private View mBackgroundView;
+    public CameraControls(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CameraControls(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration config) {
+        super.onConfigurationChanged(config);
+        adjustBackground();
+    }
+
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+        mBackgroundView = findViewById(R.id.blocker);
+    }
+
+    // In reverse landscape and reverse portrait, camera controls will be laid out
+    // on the wrong side of the screen. We need to make adjustment to move the controls
+    // to the USB side
+    public void adjustControlsToRightPosition() {
+        Configuration config = getResources().getConfiguration();
+        int orientation = Util.getDisplayRotation((Activity) getContext());
+        if (orientation == 270 && config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            flipChildren();
+        }
+        if (orientation == 180 && config.orientation == Configuration.ORIENTATION_PORTRAIT) {
+            flipChildren();
+        }
+        adjustBackground();
+    }
+
+    private void adjustBackground() {
+        // remove current drawable and reset rotation
+        mBackgroundView.setBackgroundDrawable(null);
+        mBackgroundView.setRotationX(0);
+        mBackgroundView.setRotationY(0);
+        // if the switcher background is top aligned we need to flip the background
+        // drawable vertically; if left aligned, flip horizontally
+        int gravity = ((LayoutParams) mBackgroundView.getLayoutParams()).gravity;
+        if ((gravity & Gravity.TOP) == Gravity.TOP) {
+            mBackgroundView.setRotationX(180);
+        } else if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
+            mBackgroundView.setRotationY(180);
+        }
+        mBackgroundView.setBackgroundResource(R.drawable.switcher_bg);
+    }
+}
diff --git a/src/com/android/camera/ui/CameraRootView.java b/src/com/android/camera/ui/CameraRootView.java
new file mode 100644
index 0000000..cce6495
--- /dev/null
+++ b/src/com/android/camera/ui/CameraRootView.java
@@ -0,0 +1,85 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
+
+import com.android.camera.Util;
+import com.android.gallery3d.R;
+
+public class CameraRootView extends RelativeLayout
+    implements RotatableLayout.RotationListener {
+
+    private int mOffset = 0;
+    public CameraRootView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        // Layout the window as if we did not need navigation bar
+        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+    }
+
+    @Override
+    protected boolean fitSystemWindows(Rect insets) {
+        super.fitSystemWindows(insets);
+        // insets include status bar, navigation bar, etc
+        // In this case, we are only concerned with the size of nav bar
+        if (mOffset > 0) return true;
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        if (insets.bottom > 0) {
+            mOffset = insets.bottom;
+        } else if (insets.right > 0) {
+            mOffset = insets.right;
+        }
+        Configuration config = getResources().getConfiguration();
+        if (config.orientation == Configuration.ORIENTATION_PORTRAIT) {
+            lp.setMargins(0, 0, 0, mOffset);
+        } else if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            lp.setMargins(0, 0, mOffset, 0);
+        }
+        CameraControls controls = (CameraControls) findViewById(R.id.camera_controls);
+        if (controls != null) {
+            controls.setRotationListener(this);
+            controls.adjustControlsToRightPosition();
+        }
+        return true;
+    }
+
+    @Override
+    public void onRotation(int rotation) {
+        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
+        int b = lp.bottomMargin;
+        int t = lp.topMargin;
+        int l = lp.leftMargin;
+        int r = lp.rightMargin;
+        rotation = (rotation + 360) % 360;
+        if (rotation == 90) {
+            lp.setMargins(b, l, t, r);
+        } else if (rotation == 270) {
+            lp.setMargins(t, r, b, l);
+        } else if (rotation == 180) {
+            lp.setMargins(r, b, l, t);
+        }
+    }
+}
diff --git a/src/com/android/camera/ui/PieItem.java b/src/com/android/camera/ui/PieItem.java
index bbfa1dc..6128e04 100644
--- a/src/com/android/camera/ui/PieItem.java
+++ b/src/com/android/camera/ui/PieItem.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Path;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 
 import java.util.ArrayList;
@@ -36,12 +35,9 @@
 
     private Drawable mDrawable;
     private int level;
-    private float mCenter;
-    private float start;
-    private float sweep;
-    private float animate;
-    private int inner;
-    private int outer;
+    private int mPosition;
+    private int mCount;
+
     private boolean mSelected;
     private boolean mEnabled;
     private List<PieItem> mItems;
@@ -61,9 +57,19 @@
             setAlpha(1f);
         }
         mEnabled = true;
-        setAnimationAngle(getAnimationAngle());
-        start = -1;
-        mCenter = -1;
+    }
+
+    public void setPosition(int pos, int count) {
+        mPosition = pos;
+        mCount = count;
+    }
+
+    public int getPosition() {
+        return mPosition;
+    }
+
+    public int getCount() {
+        return mCount;
     }
 
     public boolean hasItems() {
@@ -85,6 +91,10 @@
         mItems = null;
     }
 
+    public void setLevel(int level) {
+        this.level = level;
+    }
+
     public void setPath(Path p) {
         mPath = p;
     }
@@ -102,14 +112,6 @@
         mDrawable.setAlpha((int) (255 * alpha));
     }
 
-    public void setAnimationAngle(float a) {
-        animate = a;
-    }
-
-    public float getAnimationAngle() {
-        return animate;
-    }
-
     public void setEnabled(boolean enabled) {
         mEnabled = enabled;
         if (mChangeAlphaWhenDisabled) {
@@ -137,41 +139,6 @@
         return level;
     }
 
-    public void setGeometry(float st, float sw, int inside, int outside) {
-        start = st;
-        sweep = sw;
-        inner = inside;
-        outer = outside;
-    }
-
-    public void setFixedSlice(float center, float sweep) {
-        mCenter = center;
-        this.sweep = sweep;
-    }
-
-    public float getCenter() {
-        return mCenter;
-    }
-
-    public float getStart() {
-        return start;
-    }
-
-    public float getStartAngle() {
-        return start + animate;
-    }
-
-    public float getSweep() {
-        return sweep;
-    }
-
-    public int getInnerRadius() {
-        return inner;
-    }
-
-    public int getOuterRadius() {
-        return outer;
-    }
 
     public void setOnClickListener(OnClickListener listener) {
         mOnClickListener = listener;
diff --git a/src/com/android/camera/ui/PieRenderer.java b/src/com/android/camera/ui/PieRenderer.java
index dffa47e..0d8312c 100644
--- a/src/com/android/camera/ui/PieRenderer.java
+++ b/src/com/android/camera/ui/PieRenderer.java
@@ -74,10 +74,14 @@
     private static final int MSG_OPEN = 0;
     private static final int MSG_CLOSE = 1;
     private static final int MSG_OPENSUBMENU = 2;
-    private static final float PIE_SWEEP = (float)(Math.PI / 2);
+
+    protected static float CENTER = (float) Math.PI / 2;
+    protected static final float SWEEP_SLICE = 0.14f;
+    protected static final float SWEEP_ARC = 0.23f;
+
     // geometry
-    private Point mSliceCenter;
     private int mRadius;
+    private int mRadiusInc;
 
     // the detection if touch is inside a slice is offset
     // inbounds by this amount to allow the selection to show before the
@@ -101,9 +105,11 @@
     private int mFocusY;
     private int mCenterX;
     private int mCenterY;
+    private int mArcCenterY;
+    private int mSliceCenterY;
     private int mPieCenterX;
     private int mPieCenterY;
-    private int mIconRadius;
+    private int mSliceRadius;
     private int mArcRadius;
     private int mArcOffset;
 
@@ -126,6 +132,9 @@
     private LinearAnimation mFadeIn;
     private FadeOutAnimation mFadeOut;
     private volatile boolean mFocusCancelled;
+    private PointF mPolar = new PointF();
+
+
 
     private Handler mHandler = new Handler() {
         public void handleMessage(Message msg) {
@@ -169,9 +178,9 @@
         mOpen.add(new PieItem(null, 0));
         Resources res = ctx.getResources();
         mRadius = (int) res.getDimensionPixelSize(R.dimen.pie_radius_start);
+        mRadiusInc = (int) res.getDimensionPixelSize(R.dimen.pie_radius_increment);
         mCircleSize = mRadius - res.getDimensionPixelSize(R.dimen.focus_radius_offset);
         mTouchOffset = (int) res.getDimensionPixelSize(R.dimen.pie_touch_offset);
-        mSliceCenter = new Point(0,0);
         mSelectedPaint = new Paint();
         mSelectedPaint.setColor(Color.argb(255, 51, 181, 229));
         mSelectedPaint.setAntiAlias(true);
@@ -201,7 +210,7 @@
         mMenuArcPaint.setColor(Color.argb(140, 255, 255, 255));
         mMenuArcPaint.setStrokeWidth(10);
         mMenuArcPaint.setStyle(Paint.Style.STROKE);
-        mIconRadius = res.getDimensionPixelSize(R.dimen.pie_item_radius);
+        mSliceRadius = res.getDimensionPixelSize(R.dimen.pie_item_radius);
         mArcRadius = res.getDimensionPixelSize(R.dimen.pie_arc_radius);
         mArcOffset = res.getDimensionPixelSize(R.dimen.pie_arc_offset);
     }
@@ -299,8 +308,8 @@
     public void setCenter(int x, int y) {
         mPieCenterX = x;
         mPieCenterY = y;
-        mSliceCenter.x = x;
-        mSliceCenter.y = y - mArcOffset + mIconRadius;
+        mSliceCenterY = y - mArcOffset + mSliceRadius;
+        mArcCenterY = y - mArcOffset + mArcRadius;
     }
 
     @Override
@@ -325,60 +334,40 @@
     }
 
     private void layoutPie() {
-        int inner = mIconRadius;
-        int outer = inner + mTouchOffset;
-        int gap = 1;
-        layoutItems(0, getRoot().getItems(), (float) (Math.PI / 2), inner, outer, gap);
+        layoutItems(0, getRoot().getItems());
     }
 
-    private void layoutItems(int level, List<PieItem> items, float centerAngle, int inner, int outer, int gap) {
-        float emptyangle = PIE_SWEEP / 16;
-        float sweep = (float) (PIE_SWEEP - 2 * emptyangle) / items.size();
-        float angle = centerAngle - PIE_SWEEP / 2 + emptyangle + sweep / 2;
-        // check if we have custom geometry
-        // first item we find triggers custom sweep for all
-        // this allows us to re-use the path
-        for (PieItem item : items) {
-            if (item.getCenter() >= 0) {
-                sweep = item.getSweep();
-                break;
-            }
-        }
-        Point p = new Point(mSliceCenter);
-        p.y -= level * mTouchOffset;
-        Path path = makeSlice(getDegrees(0) - gap, getDegrees(sweep) + gap,
-                outer, inner, p);
+    private void layoutItems(int level, List<PieItem> items) {
+        int extend = 1;
+        Path path = makeSlice(getDegrees(0) + extend, getDegrees(SWEEP_ARC) - extend,
+                mArcRadius, mArcRadius + mRadiusInc + mRadiusInc / 4,
+                mPieCenterX, mArcCenterY - level * mRadiusInc);
         for (PieItem item : items) {
             // shared between items
             item.setPath(path);
-            if (item.getCenter() >= 0) {
-                angle = item.getCenter();
-            }
+            float angle = getArcCenter(item);
             int w = item.getIntrinsicWidth();
             int h = item.getIntrinsicHeight();
             // move views to outer border
-            int r = inner + (outer - inner) * 2 / 3;
+            int r = mArcRadius + mRadiusInc * 2 / 3;
             int x = (int) (r * Math.cos(angle));
-            int y = mSliceCenter.y - (level * mTouchOffset) - (int) (r * Math.sin(angle)) - h / 2;
-            x = mSliceCenter.x + x - w / 2;
+            int y = mArcCenterY - (level * mRadiusInc) - (int) (r * Math.sin(angle)) - h / 2;
+            x = mPieCenterX + x - w / 2;
             item.setBounds(x, y, x + w, y + h);
-            float itemstart = angle - sweep / 2;
-            item.setGeometry(itemstart, sweep, inner, outer);
+            item.setLevel(level);
             if (item.hasItems()) {
-                layoutItems(level + 1, item.getItems(), MATH_PI_2, inner,
-                        outer, gap);
+                layoutItems(level + 1, item.getItems());
             }
-            angle += sweep;
         }
     }
 
-    private Path makeSlice(float start, float end, int outer, int inner, Point center) {
+    private Path makeSlice(float start, float end, int inner, int outer, int cx, int cy) {
         RectF bb =
-                new RectF(center.x - outer, center.y - outer, center.x + outer,
-                        center.y + outer);
+                new RectF(cx - outer, cy - outer, cx + outer,
+                        cy + outer);
         RectF bbi =
-                new RectF(center.x - inner, center.y - inner, center.x + inner,
-                        center.y + inner);
+                new RectF(cx - inner, cy - inner, cx + inner,
+                        cy + inner);
         Path path = new Path();
         path.arcTo(bb, start, end - start, true);
         path.arcTo(bbi, end, start - end);
@@ -386,6 +375,18 @@
         return path;
     }
 
+    private float getArcCenter(PieItem item) {
+        return getCenter(item.getPosition(), item.getCount(), SWEEP_ARC);
+    }
+
+    private float getSliceCenter(PieItem item) {
+        return getCenter(item.getPosition(), item.getCount(), SWEEP_SLICE);
+    }
+
+    private float getCenter(int pos, int count, float sweep) {
+        return CENTER + (count - 1) * sweep / 2f - pos * sweep;
+    }
+
     /**
      * converts a
      * @param angle from 0..PI to Android degrees (clockwise starting at 3 o'clock)
@@ -475,14 +476,14 @@
         }
         if (!hasOpenItem() || (mXFade != null)) {
             // draw base menu
-            drawArc(canvas, getLevel());
+            drawArc(canvas, getLevel(), getParent());
             for (PieItem item : getParent().getItems()) {
                 drawItem(Math.max(0, mOpen.size() - 2), canvas, item, alpha);
             }
         }
         if (hasOpenItem()) {
             int level = getLevel();
-            drawArc(canvas, level);
+            drawArc(canvas, level, getOpenItem());
             for (PieItem inner : getOpenItem().getItems()) {
                 if (mFadeOut != null) {
                     drawItem(level, canvas, inner, alpha);
@@ -494,25 +495,37 @@
         canvas.restoreToCount(state);
     }
 
-    private void drawArc(Canvas canvas, int level) {
+    private void drawArc(Canvas canvas, int level, PieItem item) {
         // arc
         if (mState == STATE_PIE) {
-            int nr = mArcRadius;
-            int cy = mPieCenterY - mArcOffset + mArcRadius  - level * mTouchOffset;
-            canvas.drawArc(new RectF(mPieCenterX - nr, cy - mArcRadius,
-                    mPieCenterX + nr, cy + mArcRadius),
-                    252, 36, false, mMenuArcPaint);
+            int min = Integer.MAX_VALUE;
+            int max = Integer.MIN_VALUE;
+            int count = 0;
+            for (PieItem child : item.getItems()) {
+                final int p = child.getPosition();
+                count = child.getCount();
+                if (p < min) min = p;
+                if (p > max) max = p;
+            }
+            float start =  CENTER + (count - 1) * SWEEP_ARC / 2f - min * SWEEP_ARC + SWEEP_ARC / 2f;
+            float end =  CENTER + (count - 1) * SWEEP_ARC / 2f - max * SWEEP_ARC - SWEEP_ARC / 2f;
+            int cy = mArcCenterY - level * mRadiusInc;
+            canvas.drawArc(new RectF(mPieCenterX - mArcRadius, cy - mArcRadius,
+                    mPieCenterX + mArcRadius, cy + mArcRadius),
+                    getDegrees(end), getDegrees(start) - getDegrees(end), false, mMenuArcPaint);
         }
     }
+
     private void drawItem(int level, Canvas canvas, PieItem item, float alpha) {
         if (mState == STATE_PIE) {
             if (item.getPath() != null) {
-                int y = mSliceCenter.y - level * mTouchOffset;
+                int y = mArcCenterY - level * mRadiusInc;
                 if (item.isSelected()) {
                     Paint p = mSelectedPaint;
                     int state = canvas.save();
-                    float r = getDegrees(item.getStartAngle());
-                    canvas.rotate(r, mSliceCenter.x, y);
+                    float angle = getArcCenter(item) - SWEEP_ARC / 2f;
+                    angle = getDegrees(angle);
+                    canvas.rotate(angle, mPieCenterX, y);
                     if (mFadeOut != null) {
                         p.setAlpha((int)(255 * alpha));
                     }
@@ -537,13 +550,13 @@
         float x = evt.getX();
         float y = evt.getY();
         int action = evt.getActionMasked();
-        PointF polar = getPolar(x, y, !mTapMode);
+        getPolar(x, y, !mTapMode, mPolar);
         if (MotionEvent.ACTION_DOWN == action) {
             mDown.x = (int) evt.getX();
             mDown.y = (int) evt.getY();
             mOpening = false;
             if (mTapMode) {
-                PieItem item = findItem(polar);
+                PieItem item = findItem(mPolar);
                 if ((item != null) && (mCurrentItem != item)) {
                     mState = STATE_PIE;
                     onEnter(item);
@@ -557,7 +570,7 @@
             if (isVisible()) {
                 PieItem item = mCurrentItem;
                 if (mTapMode) {
-                    item = findItem(polar);
+                    item = findItem(mPolar);
                     if (mOpening) {
                         mOpening = false;
                         return true;
@@ -582,7 +595,7 @@
             mHandler.removeMessages(MSG_OPENSUBMENU);
             return false;
         } else if (MotionEvent.ACTION_MOVE == action) {
-            if (pulledToCenter(polar)) {
+            if (pulledToCenter(mPolar)) {
                 mHandler.removeMessages(MSG_OPENSUBMENU);
                 if (hasOpenItem()) {
                     if (mCurrentItem != null) {
@@ -595,7 +608,7 @@
                 }
                 return false;
             }
-            PieItem item = findItem(polar);
+            PieItem item = findItem(mPolar);
             boolean moved = hasMoved(evt);
             if ((item != null) && (mCurrentItem != item) && (!mOpening || moved)) {
                 mHandler.removeMessages(MSG_OPENSUBMENU);
@@ -612,24 +625,32 @@
     }
 
     private boolean pulledToCenter(PointF polarCoords) {
-        return polarCoords.y < mIconRadius - mArcOffset;
+        return polarCoords.y < mArcRadius - mRadiusInc;
     }
 
-    private PointF getPolar(float x, float y, boolean useOffset) {
-        PointF res = new PointF();
+    private boolean inside(PointF polar, PieItem item) {
+        float start = getSliceCenter(item) - SWEEP_SLICE / 2f;
+        boolean res =  (mArcRadius < polar.y)
+                && (start < polar.x)
+                && (start + SWEEP_SLICE > polar.x)
+                && (!mTapMode || (mArcRadius + mRadiusInc > polar.y));
+        return res;
+    }
+
+    private void getPolar(float x, float y, boolean useOffset, PointF res) {
         // get angle and radius from x/y
         res.x = (float) Math.PI / 2;
-        x = x - mSliceCenter.x;
-        y = mSliceCenter.y - getLevel() * mTouchOffset - y;
-        res.y = (float) Math.sqrt(x * x + y * y);
+        x = x - mPieCenterX;
+        float y1 = mSliceCenterY - getLevel() * mRadiusInc - y;
+        float y2 = mArcCenterY - getLevel() * mRadiusInc - y;
+        res.y = (float) Math.sqrt(x * x + y2 * y2);
         if (x != 0) {
-            res.x = (float) Math.atan2(y,  x);
+            res.x = (float) Math.atan2(y1,  x);
             if (res.x < 0) {
                 res.x = (float) (2 * Math.PI + res.x);
             }
         }
         res.y = res.y + (useOffset ? mTouchOffset : 0);
-        return res;
     }
 
     private boolean hasMoved(MotionEvent e) {
@@ -733,12 +754,6 @@
         return null;
     }
 
-    private boolean inside(PointF polar, PieItem item) {
-        return (item.getInnerRadius() < polar.y)
-                && (item.getStartAngle() < polar.x)
-                && (item.getStartAngle() + item.getSweep() > polar.x)
-                && (!mTapMode || (item.getOuterRadius() > polar.y));
-    }
 
     @Override
     public boolean handlesTouch() {
diff --git a/src/com/android/camera/ui/RotatableLayout.java b/src/com/android/camera/ui/RotatableLayout.java
index 4edec5d..6836b39 100644
--- a/src/com/android/camera/ui/RotatableLayout.java
+++ b/src/com/android/camera/ui/RotatableLayout.java
@@ -39,6 +39,10 @@
 
     private static final String TAG = "RotatableLayout";
     private int mPrevRotation;
+    private RotationListener mListener = null;
+    public interface RotationListener {
+        public void onRotation(int rotation);
+    }
     public RotatableLayout(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
     }
@@ -53,12 +57,15 @@
 
     @Override
     public void onFinishInflate() { // get initial orientation
+        super.onFinishInflate();
         mPrevRotation = Util.getDisplayRotation((Activity) getContext());
     }
 
     @Override
     public void onConfigurationChanged(Configuration config) {
         super.onConfigurationChanged(config);
+        int rotation = Util.getDisplayRotation((Activity) getContext());
+        boolean clockwise = isClockWiseRotation(mPrevRotation, rotation);
         // Change the size of the layout
         ViewGroup.LayoutParams lp = getLayoutParams();
         int width = lp.width;
@@ -66,15 +73,33 @@
         lp.height = width;
         lp.width = height;
         setLayoutParams(lp);
+
         // rotate all the children
-        int rotation = Util.getDisplayRotation((Activity) getContext());
-        boolean clockwise = isClockWiseRotation(mPrevRotation, rotation);
         mPrevRotation = rotation;
+        rotateChildren(clockwise);
+    }
+
+    protected void rotateChildren(boolean clockwise) {
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
             rotate(child, clockwise);
         }
+        if (mListener != null) mListener.onRotation(clockwise ? 90 : 270);
+    }
+
+    protected void flipChildren() {
+        mPrevRotation = Util.getDisplayRotation((Activity) getContext());
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View child = getChildAt(i);
+            flip(child);
+        }
+        if (mListener != null) mListener.onRotation(180);
+    }
+
+    public void setRotationListener(RotationListener listener) {
+        mListener = listener;
     }
 
     public static boolean isClockWiseRotation(int prevRotation, int currentRotation) {
@@ -181,4 +206,10 @@
         lp.height = width;
         view.setLayoutParams(lp);
     }
+
+    // Rotate a given view 180 degrees
+    public static void flip(View view) {
+        rotateClockwise(view);
+        rotateClockwise(view);
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/camera/ui/SwitcherBackgroundView.java b/src/com/android/camera/ui/SwitcherBackgroundView.java
deleted file mode 100644
index 710412c..0000000
--- a/src/com/android/camera/ui/SwitcherBackgroundView.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.ui;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.View;
-import android.widget.FrameLayout.LayoutParams;
-
-import com.android.camera.Util;
-import com.android.gallery3d.R;
-
-/*
- * This is a simple view that has a gradient background. The background
- * needs to rotate when orientation changes, so that the side of the drawable
- * that is dark is always aligned to the side of the screen, and the side that is
- * closer to the center of the screen is transparent.
- * */
-public class SwitcherBackgroundView extends View
-{
-    public SwitcherBackgroundView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        setBackgroundResource(R.drawable.switcher_bg);
-    }
-
-    public SwitcherBackgroundView(Context context) {
-        super(context);
-        setBackgroundResource(R.drawable.switcher_bg);
-    }
-    @Override
-    public void onConfigurationChanged(Configuration config) {
-        super.onConfigurationChanged(config);
-        // remove current drawable and reset rotation
-        setBackgroundDrawable(null);
-        setRotationX(0);
-        setRotationY(0);
-        // if the switcher background is top aligned we need to flip the background
-        // drawable vertically; if left aligned, flip horizontally
-        int gravity = ((LayoutParams) getLayoutParams()).gravity;
-        if ((gravity & Gravity.TOP) == Gravity.TOP) {
-            setRotationX(180);
-        } else if ((gravity & Gravity.LEFT) == Gravity.LEFT) {
-            setRotationY(180);
-        }
-        setBackgroundResource(R.drawable.switcher_bg);
-    }
-}
diff --git a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
index 8c312a9..055309f 100644
--- a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
+++ b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
@@ -283,6 +283,13 @@
                     || request.getType() == RenderingRequest.ICON_RENDERING
                     || request.getType() == RenderingRequest.PARTIAL_RENDERING
                     || request.getType() == RenderingRequest.HIGHRES_RENDERING) {
+
+                if (request.getType() == RenderingRequest.ICON_RENDERING) {
+                    mEnvironment.setQuality(ImagePreset.QUALITY_ICON);
+                } else {
+                    mEnvironment.setQuality(ImagePreset.QUALITY_PREVIEW);
+                }
+
                 Bitmap bmp = preset.apply(bitmap, mEnvironment);
                 request.setBitmap(bmp);
                 mFiltersManager.freeFilterResources(preset);
@@ -393,7 +400,9 @@
                     mOutPixelsAllocation.getType());
             needsUpdate = true;
         }
-        mInPixelsAllocation.copyFrom(bitmap);
+        if (RS != null) {
+            mInPixelsAllocation.copyFrom(bitmap);
+        }
         if (bitmap.getWidth() != mWidth
                 || bitmap.getHeight() != mHeight) {
             mWidth = bitmap.getWidth();
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
index 2aeaed8..a0523c1 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
@@ -16,7 +16,6 @@
 
 package com.android.gallery3d.filtershow.filters;
 
-import android.app.Activity;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.support.v8.renderscript.*;
@@ -28,6 +27,8 @@
 public abstract class ImageFilterRS extends ImageFilter {
     private static final String LOGTAG = "ImageFilterRS";
     private boolean DEBUG = false;
+    private int mLastInputWidth = 0;
+    private int mLastInputHeight = 0;
 
     private volatile boolean mResourcesLoaded = false;
 
@@ -65,11 +66,19 @@
                 Log.v(LOGTAG, "apply filter " + getName() + " in pipeline " + pipeline.getName());
             }
             Resources rsc = pipeline.getResources();
+            boolean sizeChanged = false;
+            if (getInPixelsAllocation() != null
+                    && ((getInPixelsAllocation().getType().getX() != mLastInputWidth)
+                    || (getInPixelsAllocation().getType().getY() != mLastInputHeight))) {
+                sizeChanged = true;
+            }
             if (pipeline.prepareRenderscriptAllocations(bitmap)
-                    || !isResourcesLoaded()) {
+                    || !isResourcesLoaded() || sizeChanged) {
                 freeResources();
                 createFilter(rsc, scaleFactor, quality);
                 setResourcesLoaded(true);
+                mLastInputWidth = getInPixelsAllocation().getType().getX();
+                mLastInputHeight = getInPixelsAllocation().getType().getY();
             }
             bindScriptValues();
             runFilter();
diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java b/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
index 6c3417a..8fcc028 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/ImageDraw.java
@@ -83,8 +83,8 @@
     float[] mTmpPoint = new float[2]; // so we do not malloc
     @Override
     public boolean onTouchEvent(MotionEvent event) {
-        boolean ret = super.onTouchEvent(event);
         if (event.getPointerCount() > 1) {
+            boolean ret = super.onTouchEvent(event);
             if (mFRep.getCurrentDrawing() != null) {
                 mFRep.clearCurrentSection();
                 mEditorDraw.commitLocalRepresentation();
@@ -93,7 +93,7 @@
         }
         if (event.getAction() != MotionEvent.ACTION_DOWN) {
             if (mFRep.getCurrentDrawing() == null) {
-                return ret;
+                return super.onTouchEvent(event);
             }
         }
 
diff --git a/src_pd/com/android/gallery3d/util/UsageStatistics.java b/src_pd/com/android/gallery3d/util/UsageStatistics.java
index cf27ac5..9246ab1 100644
--- a/src_pd/com/android/gallery3d/util/UsageStatistics.java
+++ b/src_pd/com/android/gallery3d/util/UsageStatistics.java
@@ -36,6 +36,10 @@
     public static final String TRANSITION_BUTTON_TAP = "ButtonTap";
     public static final String TRANSITION_SWIPE = "Swipe";
 
+    public static final String ACTION_CAPTURE_START = "CaptureStart";
+    public static final String ACTION_CAPTURE_FAIL = "CaptureFail";
+    public static final String ACTION_CAPTURE_DONE = "CaptureDone";
+
     public static void initialize(Context context) {}
     public static void showOptInDialogIfNeeded(Activity activity) {}
     public static void setPendingTransitionCause(String cause) {}
diff --git a/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java
index 8c4fc3d..1286c58 100644
--- a/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java
+++ b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.util.Log;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -131,6 +132,60 @@
         }
     }
 
+    public void testOutputSpeed() throws Exception {
+        final String LOGTAG = "testOutputSpeed";
+        InputStream imageInputStream = null;
+        OutputStream imageOutputStream = null;
+        try {
+            try {
+                imageInputStream = getImageInputStream();
+                // Read the image data
+                Bitmap bmp = BitmapFactory.decodeStream(imageInputStream);
+                // The image is invalid
+                if (bmp == null) {
+                    return;
+                }
+                imageInputStream.close();
+                int nLoops = 20;
+                long totalReadDuration = 0;
+                long totalWriteDuration = 0;
+                for (int i = 0; i < nLoops; i++) {
+                    imageInputStream = reopenFileStream();
+                    // Read exif data
+                    long startTime = System.nanoTime();
+                    ExifData exifData = new ExifReader(mInterface).read(imageInputStream);
+                    long endTime = System.nanoTime();
+                    long duration = endTime - startTime;
+                    totalReadDuration += duration;
+                    Log.v(LOGTAG, " read time: " + duration);
+                    imageInputStream.close();
+
+                    // Encode the image with the exif data
+                    imageOutputStream = (OutputStream) new FileOutputStream(mTmpFile);
+                    ExifOutputStream exifOutputStream = new ExifOutputStream(imageOutputStream,
+                            mInterface);
+                    exifOutputStream.setExifData(exifData);
+                    startTime = System.nanoTime();
+                    bmp.compress(Bitmap.CompressFormat.JPEG, 90, exifOutputStream);
+                    endTime = System.nanoTime();
+                    duration = endTime - startTime;
+                    totalWriteDuration += duration;
+                    Log.v(LOGTAG, " write time: " + duration);
+                    exifOutputStream.close();
+                }
+                Log.v(LOGTAG, "======================= normal");
+                Log.v(LOGTAG, "avg read time: " + totalReadDuration / nLoops);
+                Log.v(LOGTAG, "avg write time: " + totalWriteDuration / nLoops);
+                Log.v(LOGTAG, "=======================");
+            } finally {
+                Util.closeSilently(imageInputStream);
+                Util.closeSilently(imageOutputStream);
+            }
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        }
+    }
+
     @Override
     public void tearDown() throws Exception {
         super.tearDown();
diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java b/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java
index 5f200ea..da86020 100644
--- a/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java
+++ b/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java
@@ -92,4 +92,17 @@
             return String.format(RES_ID_TITLE, mImageResourceId);
         }
     }
+
+    protected InputStream reopenFileStream() throws Exception {
+        try {
+            if (mImagePath != null) {
+                return new FileInputStream(mImagePath);
+            } else {
+                Resources res = getInstrumentation().getContext().getResources();
+                return res.openRawResource(mImageResourceId);
+            }
+        } catch (Exception e) {
+            throw new Exception(getImageTitle(), e);
+        }
+    }
 }