Merge "Moving crop to a separate activity.  Refactoring." into gb-ub-photos-bryce
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 171f036..87c7b80 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
-<manifest android:versionCode="40008"
-        android:versionName="1.1.40008"
+<manifest android:versionCode="40009"
+        android:versionName="1.1.40009"
         xmlns:android="http://schemas.android.com/apk/res/android"
         package="com.android.gallery3d">
 
@@ -20,7 +20,6 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
     <uses-permission android:name="android.permission.NFC" />
-    <uses-permission android:name="android.permission.READ_SMS" />
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
@@ -297,6 +296,19 @@
                 android:configChanges="keyboardHidden|orientation|screenSize"
                 android:theme="@style/Theme.Gallery.Dialog"/>
 
+        <activity android:name="com.android.camera.NewCameraActivity"
+                android:taskAffinity="com.android.camera.NewCameraActivity"
+                android:theme="@style/Theme.Camera"
+                android:label="@string/camera_label"
+                android:configChanges="orientation|screenSize|keyboardHidden"
+                android:clearTaskOnLaunch="true"
+                android:windowSoftInputMode="stateAlwaysHidden|adjustPan">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="com.android.camera.CameraActivity"
                 android:taskAffinity="com.android.camera.CameraActivity"
                 android:label="@string/camera_label"
diff --git a/res/drawable/filtershow_state_button_background b/res/drawable/filtershow_state_button_background
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/res/drawable/filtershow_state_button_background
diff --git a/res/layout-land/filtershow_activity.xml b/res/layout-land/filtershow_activity.xml
index d6f2f5d..f1294f5 100644
--- a/res/layout-land/filtershow_activity.xml
+++ b/res/layout-land/filtershow_activity.xml
@@ -126,6 +126,7 @@
                             android:id="@+id/fxList"
                             android:layout_width="match_parent"
                             android:layout_height="@dimen/thumbnail_size"
+                            android:background="@color/background_main_toolbar"
                             android:scrollbars="none" >
 
                         <LinearLayout
@@ -141,6 +142,7 @@
                             android:id="@+id/bordersList"
                             android:layout_width="match_parent"
                             android:layout_height="@dimen/thumbnail_size"
+                            android:background="@color/background_main_toolbar"
                             android:visibility="gone"
                             android:scrollbars="none" >
 
diff --git a/res/layout/camera.xml b/res/layout/camera.xml
new file mode 100644
index 0000000..a849234
--- /dev/null
+++ b/res/layout/camera.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<com.android.camera.ui.NewCameraRootView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/camera_app_root"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+</com.android.camera.ui.NewCameraRootView>
\ No newline at end of file
diff --git a/res/layout/filtershow_activity.xml b/res/layout/filtershow_activity.xml
index 6c96225..c3c593b 100644
--- a/res/layout/filtershow_activity.xml
+++ b/res/layout/filtershow_activity.xml
@@ -93,7 +93,7 @@
                             android:inflatedId="@+id/editorPanel"
                             android:layout="@layout/filtershow_editor_panel"
                             android:layout_width="match_parent"
-                            android:layout_height="wrap_content"
+                            android:layout_height="match_parent"
                             android:visibility="visible"  />
                     </LinearLayout>
 
@@ -101,6 +101,7 @@
                 android:id="@+id/fxList"
                 android:layout_width="match_parent"
                 android:layout_height="@dimen/thumbnail_size"
+                android:background="@color/background_main_toolbar"
                 android:scrollbars="none" >
 
                 <LinearLayout
@@ -116,6 +117,7 @@
                 android:id="@+id/bordersList"
                 android:layout_width="match_parent"
                 android:layout_height="@dimen/thumbnail_size"
+                android:background="@color/background_main_toolbar"
                 android:visibility="gone"
                 android:scrollbars="none" >
 
diff --git a/res/layout/filtershow_control_action_slider.xml b/res/layout/filtershow_control_action_slider.xml
index 7caf96d..734d750 100644
--- a/res/layout/filtershow_control_action_slider.xml
+++ b/res/layout/filtershow_control_action_slider.xml
@@ -18,23 +18,26 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res/com.example.imagefilterharness"
     android:layout_width="match_parent"
-    android:layout_height="150dp"
+    android:layout_height="match_parent"
     android:orientation="horizontal" >
 
     <ImageButton
         android:id="@+id/actionButton"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_gravity="left|center_vertical"
         android:scaleType="centerInside"
+        android:layout_weight="0"
         android:background="@drawable/filtershow_button_background"
         android:src="@drawable/filtershow_addpoint"
         android:paddingBottom="8dp"  />
 
     <SeekBar
         android:id="@+id/controlValueSeekBar"
-        android:layout_width="match_parent"
-         android:layout_height="wrap_content"
-        android:layout_gravity="fill_horizontal"
+        android:layout_width="0dip"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="1"
         style="@style/FilterShowSlider" />
 </LinearLayout>
 
diff --git a/res/layout/filtershow_control_style_chooser.xml b/res/layout/filtershow_control_style_chooser.xml
new file mode 100644
index 0000000..a5bc984
--- /dev/null
+++ b/res/layout/filtershow_control_style_chooser.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res/com.example.imagefilterharness"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="horizontal" >
+            <HorizontalScrollView
+                android:id="@+id/scrollList"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:scrollbars="none" >
+
+                <LinearLayout
+                    android:id="@+id/listStyles"
+                    android:layout_width="wrap_content"
+                    android:layout_height="match_parent"
+                    android:orientation="horizontal" >
+                </LinearLayout>
+            </HorizontalScrollView>
+</LinearLayout>
+
diff --git a/res/layout/filtershow_editor_panel.xml b/res/layout/filtershow_editor_panel.xml
index 2d085a8..e4bcfc8 100644
--- a/res/layout/filtershow_editor_panel.xml
+++ b/res/layout/filtershow_editor_panel.xml
@@ -18,15 +18,16 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/top"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
+    android:layout_height="match_parent"
     android:layout_weight="0"
+    android:baselineAligned="false"
     android:orientation="vertical"
     android:visibility="visible" >
 
     <LinearLayout
         android:id="@+id/controlArea"
         android:layout_width="match_parent"
-        android:layout_height="64dp"
+        android:layout_height="0dp"
         android:layout_weight="1"
         android:orientation="horizontal"
         android:visibility="visible" >
@@ -35,6 +36,7 @@
             android:id="@+id/primarySeekBar"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
             android:layout_weight="1"
             style="@style/FilterShowSlider" />
 
@@ -42,23 +44,24 @@
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:background="@color/filtershow_background"
+        android:layout_height="56dip"
+        android:layout_weight="0"
+        android:layout_gravity="bottom"
+        android:background="@color/background_main_toolbar"
         android:orientation="horizontal"
+        android:baselineAligned="false"
         android:visibility="visible" >
 
         <ImageButton
             android:id="@+id/cancelFilter"
             android:layout_width="wrap_content"
-            android:layout_height="94dip"
+            android:layout_height="fill_parent"
             android:layout_gravity="left|center_vertical"
+            android:background="@android:color/transparent"
             android:layout_weight=".1"
-            android:background="@drawable/filtershow_button_background"
             android:gravity="center"
             android:src="@drawable/ic_menu_cancel_holo_light"
-            android:textSize="18dip" 
-            style="@style/FilterShowBottomButton" />
+            android:textSize="18dip" />
 
         <ImageView
             android:layout_width="2dp"
@@ -78,7 +81,7 @@
                 android:layout_width="fill_parent"
                 android:layout_height="fill_parent"
                 android:layout_gravity="center"
-                android:background="@drawable/filtershow_button_background"
+                android:background="@android:color/transparent"
                 android:gravity="center"
                 android:text="@string/apply_effect"
                 android:textSize="18dip"
@@ -93,14 +96,13 @@
         <ImageButton
             android:id="@+id/applyFilter"
             android:layout_width="wrap_content"
-            android:layout_height="94dip"
+            android:layout_height="fill_parent"
             android:layout_gravity="right|center_vertical"
             android:layout_weight=".1"
-            android:background="@drawable/filtershow_button_background"
+            android:background="@android:color/transparent"
             android:gravity="center"
             android:src="@drawable/ic_menu_done_holo_light"
-            android:textSize="18dip" 
-            style="@style/FilterShowBottomButton" />
+            android:textSize="18dip" />
     </LinearLayout>
 
 </LinearLayout>
diff --git a/res/layout/new_photo_module.xml b/res/layout/new_photo_module.xml
new file mode 100644
index 0000000..70a7579
--- /dev/null
+++ b/res/layout/new_photo_module.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This layout is shared by phone and tablet in both landscape and portrait
+ orientation. The purpose of having this layout is to eventually not manually
+ recreate views when the orientation changes, by migrating the views that do not
+ need to be recreated in onConfigurationChanged from old photo_module to this
+ layout. -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center">
+    <TextureView
+        android:id="@+id/preview_content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+    <ViewStub android:id="@+id/face_view_stub"
+        android:inflatedId="@+id/face_view"
+        android:layout="@layout/face_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"/>
+    <com.android.camera.ui.RenderOverlay
+        android:id="@+id/render_overlay"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+    <include layout="@layout/camera_controls"
+        android:layout_gravity="center"
+        style="@style/CameraControls"/>
+</merge>
\ No newline at end of file
diff --git a/res/layout/new_video_module.xml b/res/layout/new_video_module.xml
new file mode 100644
index 0000000..9eb3e84
--- /dev/null
+++ b/res/layout/new_video_module.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!-- This layout is shared by phone and tablet in landscape orientation. -->
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_height="match_parent"
+    android:layout_width="match_parent">
+    <TextureView
+        android:id="@+id/preview_content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+    <FrameLayout android:id="@+id/preview_border"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:visibility="gone"
+            android:background="@drawable/ic_snapshot_border" />
+    <com.android.camera.ui.RenderOverlay
+        android:id="@+id/render_overlay"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+    <com.android.camera.ui.RotateLayout android:id="@+id/recording_time_rect"
+            style="@style/ViewfinderLabelLayout">
+        <include layout="@layout/viewfinder_labels_video" android:id="@+id/labels" />
+    </com.android.camera.ui.RotateLayout>
+    <ImageView android:id="@+id/review_image"
+            android:layout_height="match_parent"
+            android:layout_width="match_parent"
+            android:visibility="gone"
+            android:background="@android:color/black"/>
+    <ImageView
+            android:id="@+id/btn_play"
+            style="@style/ReviewControlIcon"
+            android:layout_centerInParent="true"
+            android:src="@drawable/ic_gallery_play_big"
+            android:visibility="gone"
+            android:onClick="onReviewPlayClicked"/>
+
+    <include layout="@layout/camera_controls"
+        android:layout_gravity="center"
+        style="@style/CameraControls"/>
+</merge>
diff --git a/res/menu/photo.xml b/res/menu/photo.xml
index 9074aaa..48742d1 100644
--- a/res/menu/photo.xml
+++ b/res/menu/photo.xml
@@ -47,6 +47,10 @@
             android:title="@string/edit"
             android:showAsAction="never"
             android:visible="false" />
+    <item android:id="@+id/action_simple_edit"
+          android:title="@string/simple_edit"
+          android:showAsAction="never"
+          android:visible="false" />
     <item android:id="@+id/action_rotate_ccw"
             android:showAsAction="never"
             android:title="@string/rotate_left" />
diff --git a/res/values/filtershow_strings.xml b/res/values/filtershow_strings.xml
index aa23c12..a7bc92d 100644
--- a/res/values/filtershow_strings.xml
+++ b/res/values/filtershow_strings.xml
@@ -184,7 +184,7 @@
     <!--  Label for the removing drawing from screen [CHAR LIMIT=14] -->
     <string name="draw_clear">Clear</string>
 
-    <!--  Label for the select the color [CHAR LIMIT=30] -->
+    <!--  Label for the select the color [CHAR LIMIT=35] -->
     <string name="color_pick_select">Choose custom color</string>
     <!--  The title for the color pick dialog [CHAR LIMIT=20] -->
     <string name="color_pick_title">Select Color</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6e35bfc..27663c9 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -167,10 +167,14 @@
     <!-- Toast message prompted when the specified item is not found [CHAR LIMIT=40]-->
     <string name="no_such_item">Couldn\'t find item.</string>
 
-    <!-- String used as a menu label. The suer can choose to edit the image
+    <!-- String used as a menu label. The user can choose to edit the image
          [CHAR_LIMIT=20]-->
     <string name="edit">Edit</string>
 
+    <!-- String used as a menu label. The user can choose to edit the image
+         [CHAR_LIMIT=20]-->
+    <string name="simple_edit">Simple Edit</string>
+
     <!-- String used as a title of a progress dialog. The user can
          choose to cache some Picasa picture albums on device, so it can
          be viewed offline. This string is shown when the request is being
diff --git a/src/com/android/camera/CameraManager.java b/src/com/android/camera/CameraManager.java
index b354654..af4b13f 100644
--- a/src/com/android/camera/CameraManager.java
+++ b/src/com/android/camera/CameraManager.java
@@ -72,6 +72,7 @@
     private static final int SET_PREVIEW_DISPLAY_ASYNC = 22;
     private static final int SET_PREVIEW_CALLBACK = 23;
     private static final int ENABLE_SHUTTER_SOUND = 24;
+    private static final int REFRESH_PARAMETERS = 25;
 
     private Handler mCameraHandler;
     private android.hardware.Camera mCamera;
@@ -247,6 +248,10 @@
                         enableShutterSound((msg.arg1 == 1) ? true : false);
                         return;
 
+                    case REFRESH_PARAMETERS:
+                        mParametersIsDirty = true;
+                        return;
+
                     default:
                         throw new RuntimeException("Invalid CameraProxy message=" + msg.what);
                 }
@@ -446,6 +451,10 @@
             return mParameters;
         }
 
+        public void refreshParameters() {
+            mCameraHandler.sendEmptyMessage(REFRESH_PARAMETERS);
+        }
+
         public void enableShutterSound(boolean enable) {
             mCameraHandler.obtainMessage(
                     ENABLE_SHUTTER_SOUND, (enable ? 1 : 0), 0).sendToTarget();
diff --git a/src/com/android/camera/NewCameraActivity.java b/src/com/android/camera/NewCameraActivity.java
new file mode 100644
index 0000000..46295d8
--- /dev/null
+++ b/src/com/android/camera/NewCameraActivity.java
@@ -0,0 +1,326 @@
+/*
+ * 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.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
+import com.android.camera.ui.NewCameraRootView;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.util.LightCycleHelper;
+
+public class NewCameraActivity extends Activity
+    implements CameraSwitchListener {
+    public static final int PHOTO_MODULE_INDEX = 0;
+    public static final int VIDEO_MODULE_INDEX = 1;
+    public static final int PANORAMA_MODULE_INDEX = 2;
+    public static final int LIGHTCYCLE_MODULE_INDEX = 3;
+    private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
+            "android.media.action.STILL_IMAGE_CAMERA_SECURE";
+    public static final String ACTION_IMAGE_CAPTURE_SECURE =
+            "android.media.action.IMAGE_CAPTURE_SECURE";
+    // The intent extra for camera from secure lock screen. True if the gallery
+    // should only show newly captured pictures. sSecureAlbumId does not
+    // increment. This is used when switching between camera, camcorder, and
+    // panorama. If the extra is not set, it is in the normal camera mode.
+    public static final String SECURE_CAMERA_EXTRA = "secure_camera";
+
+
+    private int mCurrentModuleIndex;
+    private NewCameraModule mCurrentModule;
+    private View mRootView;
+    private int mResultCodeForTesting;
+    private Intent mResultDataForTesting;
+    private OnScreenHint mStorageHint;
+    private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD;
+    private PhotoModule mController;
+    private boolean mAutoRotateScreen;
+    private boolean mSecureCamera;
+    private int mLastRawOrientation;
+    private MyOrientationEventListener mOrientationListener;
+    private class MyOrientationEventListener
+        extends OrientationEventListener {
+        public MyOrientationEventListener(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onOrientationChanged(int orientation) {
+            // We keep the last known orientation. So if the user first orient
+            // the camera then point the camera to floor or sky, we still have
+            // the correct orientation.
+            if (orientation == ORIENTATION_UNKNOWN) return;
+            mLastRawOrientation = orientation;
+            mCurrentModule.onOrientationChanged(orientation);
+        }
+    }
+    private MediaSaveService mMediaSaveService;
+    private ServiceConnection mConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName className, IBinder b) {
+                mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService();
+                mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
+            }
+            @Override
+            public void onServiceDisconnected(ComponentName className) {
+                mMediaSaveService = null;
+            }};
+
+    public MediaSaveService getMediaSaveService() {
+        return mMediaSaveService;
+    }
+
+    private void bindMediaSaveService() {
+        Intent intent = new Intent(this, MediaSaveService.class);
+        startService(intent);  // start service before binding it so the
+                               // service won't be killed if we unbind it.
+        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+    }
+
+    private void unbindMediaSaveService() {
+        mMediaSaveService.setListener(null);
+        unbindService(mConnection);
+    }
+
+    @Override
+    public void onCreate(Bundle state) {
+        super.onCreate(state);
+        setContentView(R.layout.camera);
+        if (ApiHelper.HAS_ROTATION_ANIMATION) {
+            setRotationAnimation();
+        }
+        // Check if this is in the secure camera mode.
+        Intent intent = getIntent();
+        String action = intent.getAction();
+        if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
+            mSecureCamera = true;
+            // Use a new album when this is started from the lock screen.
+        //TODO:    sSecureAlbumId++;
+        } else if (ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
+            mSecureCamera = true;
+        } else {
+            mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
+        }
+        /*TODO: if (mSecureCamera) {
+            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+            registerReceiver(mScreenOffReceiver, filter);
+            if (sScreenOffReceiver == null) {
+                sScreenOffReceiver = new ScreenOffReceiver();
+                getApplicationContext().registerReceiver(sScreenOffReceiver, filter);
+            }
+        }*/
+        mRootView = findViewById(R.id.camera_app_root);
+        mCurrentModule = new NewPhotoModule();
+        mCurrentModule.init(this, mRootView);
+        mOrientationListener = new MyOrientationEventListener(this);
+        bindMediaSaveService();
+    }
+
+    private void setRotationAnimation() {
+        int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
+        rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
+        Window win = getWindow();
+        WindowManager.LayoutParams winParams = win.getAttributes();
+        winParams.rotationAnimation = rotationAnimation;
+        win.setAttributes(winParams);
+    }
+
+    @Override
+    public void onUserInteraction() {
+        super.onUserInteraction();
+        mCurrentModule.onUserInteraction();
+    }
+
+    @Override
+    public void onPause() {
+        mOrientationListener.disable();
+        mCurrentModule.onPauseBeforeSuper();
+        super.onPause();
+        mCurrentModule.onPauseAfterSuper();
+    }
+
+    @Override
+    public void onResume() {
+        if (Settings.System.getInt(getContentResolver(),
+                Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+            mAutoRotateScreen = false;
+        } else {
+            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
+            mAutoRotateScreen = true;
+        }
+        mOrientationListener.enable();
+        mCurrentModule.onResumeBeforeSuper();
+        super.onResume();
+        mCurrentModule.onResumeAfterSuper();
+    }
+
+    @Override
+    public void onDestroy() {
+        unbindMediaSaveService();
+        super.onDestroy();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration config) {
+        super.onConfigurationChanged(config);
+        mCurrentModule.onConfigurationChanged(config);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        return mCurrentModule.dispatchTouchEvent(m);
+    }
+    public boolean isAutoRotateScreen() {
+        return mAutoRotateScreen;
+    }
+
+    protected void updateStorageSpace() {
+        mStorageSpace = Storage.getAvailableSpace();
+    }
+
+    protected long getStorageSpace() {
+        return mStorageSpace;
+    }
+
+    protected void updateStorageSpaceAndHint() {
+        updateStorageSpace();
+        updateStorageHint(mStorageSpace);
+    }
+
+    protected void updateStorageHint() {
+        updateStorageHint(mStorageSpace);
+    }
+
+    protected boolean updateStorageHintOnResume() {
+        return true;
+    }
+
+    protected void updateStorageHint(long storageSpace) {
+        String message = null;
+        if (storageSpace == Storage.UNAVAILABLE) {
+            message = getString(R.string.no_storage);
+        } else if (storageSpace == Storage.PREPARING) {
+            message = getString(R.string.preparing_sd);
+        } else if (storageSpace == Storage.UNKNOWN_SIZE) {
+            message = getString(R.string.access_sd_fail);
+        } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
+            message = getString(R.string.spaceIsLow_content);
+        }
+
+        if (message != null) {
+            if (mStorageHint == null) {
+                mStorageHint = OnScreenHint.makeText(this, message);
+            } else {
+                mStorageHint.setText(message);
+            }
+            mStorageHint.show();
+        } else if (mStorageHint != null) {
+            mStorageHint.cancel();
+            mStorageHint = null;
+        }
+    }
+
+    protected void setResultEx(int resultCode) {
+        mResultCodeForTesting = resultCode;
+        setResult(resultCode);
+    }
+
+    protected void setResultEx(int resultCode, Intent data) {
+        mResultCodeForTesting = resultCode;
+        mResultDataForTesting = data;
+        setResult(resultCode, data);
+    }
+
+    public int getResultCode() {
+        return mResultCodeForTesting;
+    }
+
+    public Intent getResultData() {
+        return mResultDataForTesting;
+    }
+
+    public boolean isSecureCamera() {
+        return mSecureCamera;
+    }
+
+    @Override
+    public void onCameraSelected(int i) {
+        if (mCurrentModuleIndex == i) return;
+
+        CameraHolder.instance().keep();
+        closeModule(mCurrentModule);
+        mCurrentModuleIndex = i;
+        switch (i) {
+            case VIDEO_MODULE_INDEX:
+                mCurrentModule = new NewVideoModule();
+                break;
+            case PHOTO_MODULE_INDEX:
+                mCurrentModule = new NewPhotoModule();
+                break;
+            /* TODO:
+            case PANORAMA_MODULE_INDEX:
+                mCurrentModule = new PanoramaModule();
+                break;
+            case LIGHTCYCLE_MODULE_INDEX:
+                mCurrentModule = LightCycleHelper.createPanoramaModule();
+                break; */
+           default:
+               break;
+        }
+
+        openModule(mCurrentModule);
+        mCurrentModule.onOrientationChanged(mLastRawOrientation);
+        if (mMediaSaveService != null) {
+            mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
+        }
+    }
+
+    private void openModule(NewCameraModule module) {
+        module.init(this, mRootView);
+        module.onResumeBeforeSuper();
+        module.onResumeAfterSuper();
+        ((NewCameraRootView) mRootView).cameraModuleChanged();
+    }
+
+    private void closeModule(NewCameraModule module) {
+        module.onPauseBeforeSuper();
+        module.onPauseAfterSuper();
+        ((ViewGroup) mRootView).removeAllViews();
+    }
+
+    @Override
+    public void onShowSwitcherPopup() {
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/camera/NewCameraModule.java b/src/com/android/camera/NewCameraModule.java
new file mode 100644
index 0000000..061cc6c
--- /dev/null
+++ b/src/com/android/camera/NewCameraModule.java
@@ -0,0 +1,76 @@
+/*
+ * 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.Intent;
+import android.content.res.Configuration;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+public interface NewCameraModule {
+
+    public void init(NewCameraActivity activity, View frame);
+
+    public void onFullScreenChanged(boolean full);
+
+    public void onPauseBeforeSuper();
+
+    public void onPauseAfterSuper();
+
+    public void onResumeBeforeSuper();
+
+    public void onResumeAfterSuper();
+
+    public void onConfigurationChanged(Configuration config);
+
+    public void onStop();
+
+    public void installIntentFilter();
+
+    public void onActivityResult(int requestCode, int resultCode, Intent data);
+
+    public boolean onBackPressed();
+
+    public boolean onKeyDown(int keyCode, KeyEvent event);
+
+    public boolean onKeyUp(int keyCode, KeyEvent event);
+
+    public void onSingleTapUp(View view, int x, int y);
+
+    public boolean dispatchTouchEvent(MotionEvent m);
+
+    public void onPreviewTextureCopied();
+
+    public void onCaptureTextureCopied();
+
+    public void onUserInteraction();
+
+    public boolean updateStorageHintOnResume();
+
+    public void updateCameraAppView();
+
+    public boolean needsSwitcher();
+
+    public boolean needsPieMenu();
+
+    public void onOrientationChanged(int orientation);
+
+    public void onShowSwitcherPopup();
+
+    public void onMediaSaveServiceConnected(MediaSaveService s);
+}
diff --git a/src/com/android/camera/NewPhotoMenu.java b/src/com/android/camera/NewPhotoMenu.java
new file mode 100644
index 0000000..962df0f
--- /dev/null
+++ b/src/com/android/camera/NewPhotoMenu.java
@@ -0,0 +1,247 @@
+/*
+ * 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.hardware.Camera.Parameters;
+import android.view.LayoutInflater;
+
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.ListPrefSettingPopup;
+import com.android.camera.ui.MoreSettingPopup;
+import com.android.camera.ui.PieItem;
+import com.android.camera.ui.PieItem.OnClickListener;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.TimerSettingPopup;
+import com.android.gallery3d.R;
+
+public class NewPhotoMenu extends PieController
+        implements MoreSettingPopup.Listener,
+        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 NewPhotoUI mUI;
+    private String[] mOtherKeys;
+    // First level popup
+    private MoreSettingPopup mPopup;
+    // Second level popup
+    private AbstractSettingPopup mSecondPopup;
+    private NewCameraActivity mActivity;
+
+    public NewPhotoMenu(NewCameraActivity activity, NewPhotoUI ui, PieRenderer pie) {
+        super(activity, pie);
+        mUI = ui;
+        mSettingOff = activity.getString(R.string.setting_off_value);
+        mActivity = activity;
+    }
+
+    public void initialize(PreferenceGroup group) {
+        super.initialize(group);
+        mPopup = null;
+        mSecondPopup = null;
+        PieItem item = null;
+        // flash
+        if (group.findPreference(CameraSettings.KEY_FLASH_MODE) != null) {
+            item = makeItem(CameraSettings.KEY_FLASH_MODE, POS_FLASH, 5);
+            mRenderer.addItem(item);
+        }
+        // exposure compensation
+        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.setPosition(POS_SWITCH, 5);
+            item.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(PieItem item) {
+                    // Find the index of next camera.
+                    ListPreference camPref = mPreferenceGroup
+                            .findPreference(CameraSettings.KEY_CAMERA_ID);
+                    if (camPref != null) {
+                        int index = camPref.findIndexOfValue(camPref.getValue());
+                        CharSequence[] values = camPref.getEntryValues();
+                        index = (index + 1) % values.length;
+                        int newCameraId = Integer
+                                .parseInt((String) values[index]);
+                        mListener.onCameraPickerClicked(newCameraId);
+                    }
+                }
+            });
+            mRenderer.addItem(item);
+        }
+        // hdr
+        if (group.findPreference(CameraSettings.KEY_CAMERA_HDR) != null) {
+            item = makeItem(R.drawable.ic_hdr);
+            item.setPosition(POS_HDR, 5);
+            item.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(PieItem item) {
+                    // Find the index of next camera.
+                    ListPreference pref = mPreferenceGroup
+                            .findPreference(CameraSettings.KEY_CAMERA_HDR);
+                    if (pref != null) {
+                        // toggle hdr value
+                        int index = (pref.findIndexOfValue(pref.getValue()) + 1) % 2;
+                        pref.setValueIndex(index);
+                        onSettingChanged(pref);
+                    }
+                }
+            });
+            mRenderer.addItem(item);
+        }
+
+        // more settings
+        PieItem more = makeItem(R.drawable.ic_settings_holo_light);
+        more.setPosition(POS_MORE, 5);
+        mRenderer.addItem(more);
+        // white balance
+        item = makeItem(CameraSettings.KEY_WHITE_BALANCE, POS_WB, 5);
+        more.addItem(item);
+        // settings popup
+        mOtherKeys = new String[] {
+                CameraSettings.KEY_SCENE_MODE,
+                CameraSettings.KEY_RECORD_LOCATION,
+                CameraSettings.KEY_PICTURE_SIZE,
+                CameraSettings.KEY_FOCUS_MODE,
+                CameraSettings.KEY_TIMER,
+                CameraSettings.KEY_TIMER_SOUND_EFFECTS,
+                };
+        item = makeItem(R.drawable.ic_settings_holo_light);
+        item.setPosition(POS_SET, 5);
+        item.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(PieItem item) {
+                if (mPopup == null) {
+                    initializePopup();
+                }
+                mUI.showPopup(mPopup);
+            }
+        });
+        more.addItem(item);
+    }
+
+    @Override
+    public void reloadPreferences() {
+        super.reloadPreferences();
+        if (mPopup != null) {
+            mPopup.reloadPreference();
+        }
+    }
+
+    @Override
+    // Hit when an item in the second-level popup gets selected
+    public void onListPrefChanged(ListPreference pref) {
+        if (mPopup != null && mSecondPopup != null) {
+                mUI.dismissPopup(true);
+                mPopup.reloadPreference();
+        }
+        onSettingChanged(pref);
+    }
+
+    @Override
+    public void overrideSettings(final String ... keyvalues) {
+        super.overrideSettings(keyvalues);
+        if (mPopup == null) initializePopup();
+        mPopup.overrideSettings(keyvalues);
+    }
+
+    protected void initializePopup() {
+        LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        MoreSettingPopup popup = (MoreSettingPopup) inflater.inflate(
+                R.layout.more_setting_popup, null, false);
+        popup.setSettingChangedListener(this);
+        popup.initialize(mPreferenceGroup, mOtherKeys);
+        if (mActivity.isSecureCamera()) {
+            // Prevent location preference from getting changed in secure camera mode
+            popup.setPreferenceEnabled(CameraSettings.KEY_RECORD_LOCATION, false);
+        }
+        mPopup = popup;
+    }
+
+    public void popupDismissed(boolean topPopupOnly) {
+        // if the 2nd level popup gets dismissed
+        if (mSecondPopup != null) {
+            mSecondPopup = null;
+            if (topPopupOnly) mUI.showPopup(mPopup);
+        }
+    }
+
+    // Return true if the preference has the specified key but not the value.
+    private static boolean notSame(ListPreference pref, String key, String value) {
+        return (key.equals(pref.getKey()) && !value.equals(pref.getValue()));
+    }
+
+    private void setPreference(String key, String value) {
+        ListPreference pref = mPreferenceGroup.findPreference(key);
+        if (pref != null && !value.equals(pref.getValue())) {
+            pref.setValue(value);
+            reloadPreferences();
+        }
+    }
+
+    @Override
+    public void onSettingChanged(ListPreference pref) {
+        // Reset the scene mode if HDR is set to on. Reset HDR if scene mode is
+        // set to non-auto.
+        if (notSame(pref, CameraSettings.KEY_CAMERA_HDR, mSettingOff)) {
+            setPreference(CameraSettings.KEY_SCENE_MODE, Parameters.SCENE_MODE_AUTO);
+        } else if (notSame(pref, CameraSettings.KEY_SCENE_MODE, Parameters.SCENE_MODE_AUTO)) {
+            setPreference(CameraSettings.KEY_CAMERA_HDR, mSettingOff);
+        }
+        super.onSettingChanged(pref);
+    }
+
+    @Override
+    // Hit when an item in the first-level popup gets selected, then bring up
+    // the second-level popup
+    public void onPreferenceClicked(ListPreference pref) {
+        if (mSecondPopup != null) return;
+
+        LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        if (CameraSettings.KEY_TIMER.equals(pref.getKey())) {
+            TimerSettingPopup timerPopup = (TimerSettingPopup) inflater.inflate(
+                    R.layout.timer_setting_popup, null, false);
+            timerPopup.initialize(pref);
+            timerPopup.setSettingChangedListener(this);
+            mUI.dismissPopup(true);
+            mSecondPopup = timerPopup;
+        } else {
+            ListPrefSettingPopup basic = (ListPrefSettingPopup) inflater.inflate(
+                    R.layout.list_pref_setting_popup, null, false);
+            basic.initialize(pref);
+            basic.setSettingChangedListener(this);
+            mUI.dismissPopup(true);
+            mSecondPopup = basic;
+        }
+        mUI.showPopup(mSecondPopup);
+    }
+}
diff --git a/src/com/android/camera/NewPhotoModule.java b/src/com/android/camera/NewPhotoModule.java
new file mode 100644
index 0000000..dfa1e0c
--- /dev/null
+++ b/src/com/android/camera/NewPhotoModule.java
@@ -0,0 +1,2005 @@
+/*
+ * 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.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.Size;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.location.Location;
+import android.media.CameraProfile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
+import com.android.camera.ui.PopupManager;
+import com.android.camera.ui.RotateTextToast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.Rational;
+import com.android.gallery3d.filtershow.CropExtras;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Formatter;
+import java.util.List;
+
+public class NewPhotoModule
+    implements NewCameraModule,
+    PhotoController,
+    FocusOverlayManager.Listener,
+    CameraPreference.OnPreferenceChangedListener,
+    ShutterButton.OnShutterButtonListener,
+    MediaSaveService.Listener,
+    OnCountDownFinishedListener,
+    SensorEventListener {
+
+    private static final String TAG = "CAM_PhotoModule";
+
+    // We number the request code from 1000 to avoid collision with Gallery.
+    private static final int REQUEST_CROP = 1000;
+
+    private static final int SETUP_PREVIEW = 1;
+    private static final int FIRST_TIME_INIT = 2;
+    private static final int CLEAR_SCREEN_DELAY = 3;
+    private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
+    private static final int CHECK_DISPLAY_ROTATION = 5;
+    private static final int SHOW_TAP_TO_FOCUS_TOAST = 6;
+    private static final int SWITCH_CAMERA = 7;
+    private static final int SWITCH_CAMERA_START_ANIMATION = 8;
+    private static final int CAMERA_OPEN_DONE = 9;
+    private static final int START_PREVIEW_DONE = 10;
+    private static final int OPEN_CAMERA_FAIL = 11;
+    private static final int CAMERA_DISABLED = 12;
+
+    // The subset of parameters we need to update in setCameraParameters().
+    private static final int UPDATE_PARAM_INITIALIZE = 1;
+    private static final int UPDATE_PARAM_ZOOM = 2;
+    private static final int UPDATE_PARAM_PREFERENCE = 4;
+    private static final int UPDATE_PARAM_ALL = -1;
+
+    // This is the timeout to keep the camera in onPause for the first time
+    // after screen on if the activity is started from secure lock screen.
+    private static final int KEEP_CAMERA_TIMEOUT = 1000; // ms
+
+    // copied from Camera hierarchy
+    private NewCameraActivity mActivity;
+    private CameraProxy mCameraDevice;
+    private int mCameraId;
+    private Parameters mParameters;
+    private boolean mPaused;
+
+    private NewPhotoUI mUI;
+
+    // -1 means camera is not switching.
+    protected int mPendingSwitchCameraId = -1;
+    private boolean mOpenCameraFail;
+    private boolean mCameraDisabled;
+
+    // When setCameraParametersWhenIdle() is called, we accumulate the subsets
+    // needed to be updated in mUpdateSet.
+    private int mUpdateSet;
+
+    private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+    private int mZoomValue;  // The current zoom value.
+
+    private Parameters mInitialParams;
+    private boolean mFocusAreaSupported;
+    private boolean mMeteringAreaSupported;
+    private boolean mAeLockSupported;
+    private boolean mAwbLockSupported;
+    private boolean mContinousFocusSupported;
+
+    // The degrees of the device rotated clockwise from its natural orientation.
+    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+    private ComboPreferences mPreferences;
+
+    private static final String sTempCropFilename = "crop-temp";
+
+    private ContentProviderClient mMediaProviderClient;
+    private boolean mFaceDetectionStarted = false;
+
+    // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
+    private String mCropValue;
+    private Uri mSaveUri;
+
+    // We use a queue to generated names of the images to be used later
+    // when the image is ready to be saved.
+    private NamedImages mNamedImages;
+
+    private Runnable mDoSnapRunnable = new Runnable() {
+        @Override
+        public void run() {
+            onShutterButtonClick();
+        }
+    };
+
+    private final StringBuilder mBuilder = new StringBuilder();
+    private final Formatter mFormatter = new Formatter(mBuilder);
+    private final Object[] mFormatterArgs = new Object[1];
+
+    /**
+     * An unpublished intent flag requesting to return as soon as capturing
+     * is completed.
+     *
+     * TODO: consider publishing by moving into MediaStore.
+     */
+    private static final String EXTRA_QUICK_CAPTURE =
+            "android.intent.extra.quickCapture";
+
+    // The display rotation in degrees. This is only valid when mCameraState is
+    // not PREVIEW_STOPPED.
+    private int mDisplayRotation;
+    // The value for android.hardware.Camera.setDisplayOrientation.
+    private int mCameraDisplayOrientation;
+    // The value for UI components like indicators.
+    private int mDisplayOrientation;
+    // The value for android.hardware.Camera.Parameters.setRotation.
+    private int mJpegRotation;
+    private boolean mFirstTimeInitialized;
+    private boolean mIsImageCaptureIntent;
+
+    private int mCameraState = PREVIEW_STOPPED;
+    private boolean mSnapshotOnIdle = false;
+
+    private ContentResolver mContentResolver;
+
+    private LocationManager mLocationManager;
+
+    private final ShutterCallback mShutterCallback = new ShutterCallback();
+    private final PostViewPictureCallback mPostViewPictureCallback =
+            new PostViewPictureCallback();
+    private final RawPictureCallback mRawPictureCallback =
+            new RawPictureCallback();
+    private final AutoFocusCallback mAutoFocusCallback =
+            new AutoFocusCallback();
+    private final Object mAutoFocusMoveCallback =
+            ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
+            ? new AutoFocusMoveCallback()
+            : null;
+
+    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
+
+    private long mFocusStartTime;
+    private long mShutterCallbackTime;
+    private long mPostViewPictureCallbackTime;
+    private long mRawPictureCallbackTime;
+    private long mJpegPictureCallbackTime;
+    private long mOnResumeTime;
+    private byte[] mJpegImageData;
+
+    // These latency time are for the CameraLatency test.
+    public long mAutoFocusTime;
+    public long mShutterLag;
+    public long mShutterToPictureDisplayedTime;
+    public long mPictureDisplayedToJpegCallbackTime;
+    public long mJpegCallbackFinishTime;
+    public long mCaptureStartTime;
+
+    // This handles everything about focus.
+    private FocusOverlayManager mFocusManager;
+
+    private String mSceneMode;
+
+    private final Handler mHandler = new MainHandler();
+    private PreferenceGroup mPreferenceGroup;
+
+    private boolean mQuickCapture;
+    private SensorManager mSensorManager;
+    private float[] mGData = new float[3];
+    private float[] mMData = new float[3];
+    private float[] mR = new float[16];
+    private int mHeading = -1;
+
+    CameraStartUpThread mCameraStartUpThread;
+    ConditionVariable mStartPreviewPrerequisiteReady = new ConditionVariable();
+
+    private MediaSaveService.OnMediaSavedListener mOnMediaSavedListener =
+            new MediaSaveService.OnMediaSavedListener() {
+                @Override
+                public void onMediaSaved(Uri uri) {
+                    if (uri != null) {
+                        // TODO: Commenting out the line below for now. need to get it working
+                        // mActivity.addSecureAlbumItemIfNeeded(false, uri);
+                        Util.broadcastNewPicture(mActivity, uri);
+                    }
+                }
+            };
+
+    // The purpose is not to block the main thread in onCreate and onResume.
+    private class CameraStartUpThread extends Thread {
+        private volatile boolean mCancelled;
+
+        public void cancel() {
+            mCancelled = true;
+            interrupt();
+        }
+
+        public boolean isCanceled() {
+            return mCancelled;
+        }
+
+        @Override
+        public void run() {
+            try {
+                // We need to check whether the activity is paused before long
+                // operations to ensure that onPause() can be done ASAP.
+                if (mCancelled) return;
+                mCameraDevice = Util.openCamera(mActivity, mCameraId);
+                mParameters = mCameraDevice.getParameters();
+                // Wait until all the initialization needed by startPreview are
+                // done.
+                mStartPreviewPrerequisiteReady.block();
+
+                initializeCapabilities();
+                if (mFocusManager == null) initializeFocusManager();
+                if (mCancelled) return;
+                setCameraParameters(UPDATE_PARAM_ALL);
+                mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
+                if (mCancelled) return;
+                startPreview();
+                mHandler.sendEmptyMessage(START_PREVIEW_DONE);
+                mOnResumeTime = SystemClock.uptimeMillis();
+                mHandler.sendEmptyMessage(CHECK_DISPLAY_ROTATION);
+            } catch (CameraHardwareException e) {
+                mHandler.sendEmptyMessage(OPEN_CAMERA_FAIL);
+            } catch (CameraDisabledException e) {
+                mHandler.sendEmptyMessage(CAMERA_DISABLED);
+            }
+        }
+    }
+
+    /**
+     * This Handler is used to post message back onto the main thread of the
+     * application
+     */
+    private class MainHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case SETUP_PREVIEW: {
+                    setupPreview();
+                    break;
+                }
+
+                case CLEAR_SCREEN_DELAY: {
+                    mActivity.getWindow().clearFlags(
+                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+                    break;
+                }
+
+                case FIRST_TIME_INIT: {
+                    initializeFirstTime();
+                    break;
+                }
+
+                case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
+                    setCameraParametersWhenIdle(0);
+                    break;
+                }
+
+                case CHECK_DISPLAY_ROTATION: {
+                    // Set the display orientation if display rotation has changed.
+                    // Sometimes this happens when the device is held upside
+                    // down and camera app is opened. Rotation animation will
+                    // take some time and the rotation value we have got may be
+                    // wrong. Framework does not have a callback for this now.
+                    if (Util.getDisplayRotation(mActivity) != mDisplayRotation) {
+                        setDisplayOrientation();
+                    }
+                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
+                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+                    }
+                    break;
+                }
+
+                case SHOW_TAP_TO_FOCUS_TOAST: {
+                    showTapToFocusToast();
+                    break;
+                }
+
+                case SWITCH_CAMERA: {
+                    switchCamera();
+                    break;
+                }
+
+                case SWITCH_CAMERA_START_ANIMATION: {
+                   // TODO: Need to revisit
+                   // ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
+                    break;
+                }
+
+                case CAMERA_OPEN_DONE: {
+                    onCameraOpened();
+                    break;
+                }
+
+                case START_PREVIEW_DONE: {
+                    onPreviewStarted();
+                    break;
+                }
+
+                case OPEN_CAMERA_FAIL: {
+                    mCameraStartUpThread = null;
+                    mOpenCameraFail = true;
+                    Util.showErrorAndFinish(mActivity,
+                            R.string.cannot_connect_camera);
+                    break;
+                }
+
+                case CAMERA_DISABLED: {
+                    mCameraStartUpThread = null;
+                    mCameraDisabled = true;
+                    Util.showErrorAndFinish(mActivity,
+                            R.string.camera_disabled);
+                    break;
+                }
+            }
+        }
+    }
+
+    @Override
+    public void init(NewCameraActivity activity, View parent) {
+        mActivity = activity;
+        mUI = new NewPhotoUI(activity, this, parent);
+        mPreferences = new ComboPreferences(mActivity);
+        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
+        mCameraId = getPreferredCameraId(mPreferences);
+
+        mContentResolver = mActivity.getContentResolver();
+
+        // To reduce startup time, open the camera and start the preview in
+        // another thread.
+        mCameraStartUpThread = new CameraStartUpThread();
+        mCameraStartUpThread.start();
+
+        // Surface texture is from camera screen nail and startPreview needs it.
+        // This must be done before startPreview.
+        mIsImageCaptureIntent = isImageCaptureIntent();
+
+        mPreferences.setLocalId(mActivity, mCameraId);
+        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+        // we need to reset exposure for the preview
+        resetExposureCompensation();
+        // Starting the preview needs preferences, camera screen nail, and
+        // focus area indicator.
+        mStartPreviewPrerequisiteReady.open();
+
+        initializeControlByIntent();
+        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
+        mLocationManager = new LocationManager(mActivity, mUI);
+        mSensorManager = (SensorManager)(mActivity.getSystemService(Context.SENSOR_SERVICE));
+    }
+
+    private void initializeControlByIntent() {
+        mUI.initializeControlByIntent();
+        if (mIsImageCaptureIntent) {
+            setupCaptureParams();
+        }
+    }
+
+    private void onPreviewStarted() {
+        mCameraStartUpThread = null;
+        setCameraState(IDLE);
+        startFaceDetection();
+        locationFirstRun();
+    }
+
+    // Prompt the user to pick to record location for the very first run of
+    // camera only
+    private void locationFirstRun() {
+        if (RecordLocationPreference.isSet(mPreferences)) {
+            return;
+        }
+        if (mActivity.isSecureCamera()) return;
+        // Check if the back camera exists
+        int backCameraId = CameraHolder.instance().getBackCameraId();
+        if (backCameraId == -1) {
+            // If there is no back camera, do not show the prompt.
+            return;
+        }
+
+        new AlertDialog.Builder(mActivity)
+            .setTitle(R.string.remember_location_title)
+            .setMessage(R.string.remember_location_prompt)
+            .setPositiveButton(R.string.remember_location_yes, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int arg1) {
+                    setLocationPreference(RecordLocationPreference.VALUE_ON);
+                }
+            })
+            .setNegativeButton(R.string.remember_location_no, new DialogInterface.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialog, int arg1) {
+                    dialog.cancel();
+                }
+            })
+            .setOnCancelListener(new DialogInterface.OnCancelListener() {
+                @Override
+                public void onCancel(DialogInterface dialog) {
+                    setLocationPreference(RecordLocationPreference.VALUE_OFF);
+                }
+            })
+            .show();
+    }
+
+    private void setLocationPreference(String value) {
+        mPreferences.edit()
+            .putString(CameraSettings.KEY_RECORD_LOCATION, value)
+            .apply();
+        // TODO: Fix this to use the actual onSharedPreferencesChanged listener
+        // instead of invoking manually
+        onSharedPreferenceChanged();
+    }
+
+    private void onCameraOpened() {
+        View root = mUI.getRootView();
+        // These depend on camera parameters.
+
+        int width = root.getWidth();
+        int height = root.getHeight();
+        mFocusManager.setPreviewSize(width, height);
+        openCameraCommon();
+    }
+
+    private void switchCamera() {
+        if (mPaused) return;
+
+        Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
+        mCameraId = mPendingSwitchCameraId;
+        mPendingSwitchCameraId = -1;
+        setCameraId(mCameraId);
+
+        // from onPause
+        closeCamera();
+        mUI.collapseCameraControls();
+        mUI.clearFaces();
+        if (mFocusManager != null) mFocusManager.removeMessages();
+
+        // Restart the camera and initialize the UI. From onCreate.
+        mPreferences.setLocalId(mActivity, mCameraId);
+        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+        try {
+            mCameraDevice = Util.openCamera(mActivity, mCameraId);
+            mParameters = mCameraDevice.getParameters();
+        } catch (CameraHardwareException e) {
+            Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+            return;
+        } catch (CameraDisabledException e) {
+            Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+            return;
+        }
+        initializeCapabilities();
+        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+        boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+        mFocusManager.setMirror(mirror);
+        mFocusManager.setParameters(mInitialParams);
+        setupPreview();
+
+        openCameraCommon();
+
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            // Start switch camera animation. Post a message because
+            // onFrameAvailable from the old camera may already exist.
+            mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
+        }
+    }
+
+    protected void setCameraId(int cameraId) {
+        ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+        pref.setValue("" + cameraId);
+    }
+
+    // either open a new camera or switch cameras
+    private void openCameraCommon() {
+        loadCameraPreferences();
+
+        mUI.onCameraOpened(mPreferenceGroup, mPreferences, mParameters, this);
+        updateSceneMode();
+        showTapToFocusToastIfNeeded();
+
+
+    }
+
+    public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) {
+        if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
+    }
+
+    private void resetExposureCompensation() {
+        String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
+                CameraSettings.EXPOSURE_DEFAULT_VALUE);
+        if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
+            Editor editor = mPreferences.edit();
+            editor.putString(CameraSettings.KEY_EXPOSURE, "0");
+            editor.apply();
+        }
+    }
+
+    private void keepMediaProviderInstance() {
+        // We want to keep a reference to MediaProvider in camera's lifecycle.
+        // TODO: Utilize mMediaProviderClient instance to replace
+        // ContentResolver calls.
+        if (mMediaProviderClient == null) {
+            mMediaProviderClient = mContentResolver
+                    .acquireContentProviderClient(MediaStore.AUTHORITY);
+        }
+    }
+
+    // Snapshots can only be taken after this is called. It should be called
+    // once only. We could have done these things in onCreate() but we want to
+    // make preview screen appear as soon as possible.
+    private void initializeFirstTime() {
+        if (mFirstTimeInitialized) return;
+
+        // Initialize location service.
+        boolean recordLocation = RecordLocationPreference.get(
+                mPreferences, mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+
+        keepMediaProviderInstance();
+
+        mUI.initializeFirstTime();
+        MediaSaveService s = mActivity.getMediaSaveService();
+        // We set the listener only when both service and shutterbutton
+        // are initialized.
+        if (s != null) {
+            s.setListener(this);
+        }
+
+        mNamedImages = new NamedImages();
+
+        mFirstTimeInitialized = true;
+        addIdleHandler();
+
+        mActivity.updateStorageSpaceAndHint();
+    }
+
+    // If the activity is paused and resumed, this method will be called in
+    // onResume.
+    private void initializeSecondTime() {
+        // Start location update if needed.
+        boolean recordLocation = RecordLocationPreference.get(
+                mPreferences, mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+        MediaSaveService s = mActivity.getMediaSaveService();
+        if (s != null) {
+            s.setListener(this);
+        }
+        mNamedImages = new NamedImages();
+        mUI.initializeSecondTime(mParameters);
+        keepMediaProviderInstance();
+    }
+
+    @Override
+    public void onSurfaceCreated(SurfaceHolder holder) {
+        // Do not access the camera if camera start up thread is not finished.
+        if (mCameraDevice == null || mCameraStartUpThread != null)
+            return;
+
+        mCameraDevice.setPreviewDisplayAsync(holder);
+        // This happens when onConfigurationChanged arrives, surface has been
+        // destroyed, and there is no onFullScreenChanged.
+        if (mCameraState == PREVIEW_STOPPED) {
+            setupPreview();
+        }
+    }
+
+    private void showTapToFocusToastIfNeeded() {
+        // Show the tap to focus toast if this is the first start.
+        if (mFocusAreaSupported &&
+                mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) {
+            // Delay the toast for one second to wait for orientation.
+            mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
+        }
+    }
+
+    private void addIdleHandler() {
+        MessageQueue queue = Looper.myQueue();
+        queue.addIdleHandler(new MessageQueue.IdleHandler() {
+            @Override
+            public boolean queueIdle() {
+                Storage.ensureOSXCompatible();
+                return false;
+            }
+        });
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void startFaceDetection() {
+        if (!ApiHelper.HAS_FACE_DETECTION) return;
+        if (mFaceDetectionStarted) return;
+        if (mParameters.getMaxNumDetectedFaces() > 0) {
+            mFaceDetectionStarted = true;
+            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+            mUI.onStartFaceDetection(mDisplayOrientation,
+                    (info.facing == CameraInfo.CAMERA_FACING_FRONT));
+            mCameraDevice.setFaceDetectionListener(mUI);
+            mCameraDevice.startFaceDetection();
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void stopFaceDetection() {
+        if (!ApiHelper.HAS_FACE_DETECTION) return;
+        if (!mFaceDetectionStarted) return;
+        if (mParameters.getMaxNumDetectedFaces() > 0) {
+            mFaceDetectionStarted = false;
+            mCameraDevice.setFaceDetectionListener(null);
+            mCameraDevice.stopFaceDetection();
+            mUI.clearFaces();
+        }
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (mCameraState == SWITCHING_CAMERA) return true;
+        return mUI.dispatchTouchEvent(m);
+    }
+
+    private final class ShutterCallback
+            implements android.hardware.Camera.ShutterCallback {
+        @Override
+        public void onShutter() {
+            mShutterCallbackTime = System.currentTimeMillis();
+            mShutterLag = mShutterCallbackTime - mCaptureStartTime;
+            Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
+        }
+    }
+
+    private final class PostViewPictureCallback implements PictureCallback {
+        @Override
+        public void onPictureTaken(
+                byte [] data, android.hardware.Camera camera) {
+            mPostViewPictureCallbackTime = System.currentTimeMillis();
+            Log.v(TAG, "mShutterToPostViewCallbackTime = "
+                    + (mPostViewPictureCallbackTime - mShutterCallbackTime)
+                    + "ms");
+        }
+    }
+
+    private final class RawPictureCallback implements PictureCallback {
+        @Override
+        public void onPictureTaken(
+                byte [] rawData, android.hardware.Camera camera) {
+            mRawPictureCallbackTime = System.currentTimeMillis();
+            Log.v(TAG, "mShutterToRawCallbackTime = "
+                    + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
+        }
+    }
+
+    private final class JpegPictureCallback implements PictureCallback {
+        Location mLocation;
+
+        public JpegPictureCallback(Location loc) {
+            mLocation = loc;
+        }
+
+        @Override
+        public void onPictureTaken(
+                final byte [] jpegData, final android.hardware.Camera camera) {
+            if (mPaused) {
+                return;
+            }
+            if (mSceneMode == Util.SCENE_MODE_HDR) {
+                mUI.showSwitcher();
+                //TODO: mActivity.setSwipingEnabled(true);
+            }
+
+            mJpegPictureCallbackTime = System.currentTimeMillis();
+            // If postview callback has arrived, the captured image is displayed
+            // in postview callback. If not, the captured image is displayed in
+            // raw picture callback.
+            if (mPostViewPictureCallbackTime != 0) {
+                mShutterToPictureDisplayedTime =
+                        mPostViewPictureCallbackTime - mShutterCallbackTime;
+                mPictureDisplayedToJpegCallbackTime =
+                        mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
+            } else {
+                mShutterToPictureDisplayedTime =
+                        mRawPictureCallbackTime - mShutterCallbackTime;
+                mPictureDisplayedToJpegCallbackTime =
+                        mJpegPictureCallbackTime - mRawPictureCallbackTime;
+            }
+            Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
+                    + mPictureDisplayedToJpegCallbackTime + "ms");
+
+             /*TODO:
+            // Only animate when in full screen capture mode
+            // i.e. If monkey/a user swipes to the gallery during picture taking,
+            // don't show animation
+            if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
+                    && mActivity.mShowCameraAppView) {
+                // Finish capture animation
+                ((CameraScreenNail) mActivity.mCameraScreenNail).animateSlide();
+            } */
+            mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
+            if (!mIsImageCaptureIntent) {
+                if (ApiHelper.CAN_START_PREVIEW_IN_JPEG_CALLBACK) {
+                    setupPreview();
+                } else {
+                    // Camera HAL of some devices have a bug. Starting preview
+                    // immediately after taking a picture will fail. Wait some
+                    // time before starting the preview.
+                    mHandler.sendEmptyMessageDelayed(SETUP_PREVIEW, 300);
+                }
+            }
+
+            if (!mIsImageCaptureIntent) {
+                // Calculate the width and the height of the jpeg.
+                Size s = mParameters.getPictureSize();
+                ExifInterface exif = Exif.getExif(jpegData);
+                int orientation = Exif.getOrientation(exif);
+                int width, height;
+                if ((mJpegRotation + orientation) % 180 == 0) {
+                    width = s.width;
+                    height = s.height;
+                } else {
+                    width = s.height;
+                    height = s.width;
+                }
+                String title = mNamedImages.getTitle();
+                long date = mNamedImages.getDate();
+                if (title == null) {
+                    Log.e(TAG, "Unbalanced name/data pair");
+                } else {
+                    if (date == -1) date = mCaptureStartTime;
+                    if (mHeading >= 0) {
+                        // heading direction has been updated by the sensor.
+                        ExifTag directionRefTag = exif.buildTag(
+                                ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
+                                ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
+                        ExifTag directionTag = exif.buildTag(
+                                ExifInterface.TAG_GPS_IMG_DIRECTION,
+                                new Rational(mHeading, 1));
+                        exif.setTag(directionRefTag);
+                        exif.setTag(directionTag);
+                    }
+                    mActivity.getMediaSaveService().addImage(
+                            jpegData, title, date, mLocation, width, height,
+                            orientation, exif, mOnMediaSavedListener, mContentResolver);
+                }
+            } else {
+                mJpegImageData = jpegData;
+                if (!mQuickCapture) {
+                    mUI.showPostCaptureAlert();
+                } else {
+                    onCaptureDone();
+                }
+            }
+
+            // Check this in advance of each shot so we don't add to shutter
+            // latency. It's true that someone else could write to the SD card in
+            // the mean time and fill it, but that could have happened between the
+            // shutter press and saving the JPEG too.
+            mActivity.updateStorageSpaceAndHint();
+
+            long now = System.currentTimeMillis();
+            mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
+            Log.v(TAG, "mJpegCallbackFinishTime = "
+                    + mJpegCallbackFinishTime + "ms");
+            mJpegPictureCallbackTime = 0;
+        }
+    }
+
+    private final class AutoFocusCallback
+            implements android.hardware.Camera.AutoFocusCallback {
+        @Override
+        public void onAutoFocus(
+                boolean focused, android.hardware.Camera camera) {
+            if (mPaused) return;
+
+            mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
+            Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
+            setCameraState(IDLE);
+            mFocusManager.onAutoFocus(focused, mUI.isShutterPressed());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private final class AutoFocusMoveCallback
+            implements android.hardware.Camera.AutoFocusMoveCallback {
+        @Override
+        public void onAutoFocusMoving(
+            boolean moving, android.hardware.Camera camera) {
+                mFocusManager.onAutoFocusMoving(moving);
+        }
+    }
+
+    private static class NamedImages {
+        private ArrayList<NamedEntity> mQueue;
+        private boolean mStop;
+        private NamedEntity mNamedEntity;
+
+        public NamedImages() {
+            mQueue = new ArrayList<NamedEntity>();
+        }
+
+        public void nameNewImage(ContentResolver resolver, long date) {
+            NamedEntity r = new NamedEntity();
+            r.title = Util.createJpegName(date);
+            r.date = date;
+            mQueue.add(r);
+        }
+
+        public String getTitle() {
+            if (mQueue.isEmpty()) {
+                mNamedEntity = null;
+                return null;
+            }
+            mNamedEntity = mQueue.get(0);
+            mQueue.remove(0);
+
+            return mNamedEntity.title;
+        }
+
+        // Must be called after getTitle().
+        public long getDate() {
+            if (mNamedEntity == null) return -1;
+            return mNamedEntity.date;
+        }
+
+        private static class NamedEntity {
+            String title;
+            long date;
+        }
+    }
+
+    private void setCameraState(int state) {
+        mCameraState = state;
+        switch (state) {
+        case PhotoController.PREVIEW_STOPPED:
+        case PhotoController.SNAPSHOT_IN_PROGRESS:
+        case PhotoController.FOCUSING:
+        case PhotoController.SWITCHING_CAMERA:
+            mUI.enableGestures(false);
+            break;
+        case PhotoController.IDLE:
+            mUI.enableGestures(true);
+            break;
+        }
+    }
+
+    private void animateFlash() {
+        /* //TODO:
+        // Only animate when in full screen capture mode
+        // i.e. If monkey/a user swipes to the gallery during picture taking,
+        // don't show animation
+        if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
+                && mActivity.mShowCameraAppView) {
+            // Start capture animation.
+            ((CameraScreenNail) mActivity.mCameraScreenNail).animateFlash(mDisplayRotation);
+        } */
+    }
+
+    @Override
+    public boolean capture() {
+        // If we are already in the middle of taking a snapshot or the image save request
+        // is full then ignore.
+        if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
+                || mCameraState == SWITCHING_CAMERA
+                || mActivity.getMediaSaveService().isQueueFull()) {
+            return false;
+        }
+        mCaptureStartTime = System.currentTimeMillis();
+        mPostViewPictureCallbackTime = 0;
+        mJpegImageData = null;
+
+        final boolean animateBefore = (mSceneMode == Util.SCENE_MODE_HDR);
+
+        if (animateBefore) {
+            animateFlash();
+        }
+
+        // Set rotation and gps data.
+        int orientation = (360 - mDisplayRotation) % 360;
+        // We need to be consistent with the framework orientation (i.e. the
+        // orientation of the UI.) when the auto-rotate screen setting is on.
+        if (mActivity.isAutoRotateScreen()) {
+            orientation = (360 - mDisplayRotation) % 360;
+        } else {
+            orientation = mOrientation;
+        }
+        mJpegRotation = Util.getJpegRotation(mCameraId, orientation);
+        mParameters.setRotation(mJpegRotation);
+        Location loc = mLocationManager.getCurrentLocation();
+        Util.setGpsParameters(mParameters, loc);
+        mCameraDevice.setParameters(mParameters);
+
+        mCameraDevice.takePicture2(mShutterCallback, mRawPictureCallback,
+                mPostViewPictureCallback, new JpegPictureCallback(loc),
+                mCameraState, mFocusManager.getFocusState());
+
+        if (!animateBefore) {
+            animateFlash();
+        }
+
+        mNamedImages.nameNewImage(mContentResolver, mCaptureStartTime);
+
+        mFaceDetectionStarted = false;
+        setCameraState(SNAPSHOT_IN_PROGRESS);
+        return true;
+    }
+
+    @Override
+    public void setFocusParameters() {
+        setCameraParameters(UPDATE_PARAM_PREFERENCE);
+    }
+
+    private int getPreferredCameraId(ComboPreferences preferences) {
+        int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
+        if (intentCameraId != -1) {
+            // Testing purpose. Launch a specific camera through the intent
+            // extras.
+            return intentCameraId;
+        } else {
+            return CameraSettings.readPreferredCameraId(preferences);
+        }
+    }
+
+    private void updateSceneMode() {
+        // If scene mode is set, we cannot set flash mode, white balance, and
+        // focus mode, instead, we read it from driver
+        if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
+            overrideCameraSettings(mParameters.getFlashMode(),
+                    mParameters.getWhiteBalance(), mParameters.getFocusMode());
+        } else {
+            overrideCameraSettings(null, null, null);
+        }
+    }
+
+    private void overrideCameraSettings(final String flashMode,
+            final String whiteBalance, final String focusMode) {
+        mUI.overrideSettings(
+                CameraSettings.KEY_FLASH_MODE, flashMode,
+                CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
+                CameraSettings.KEY_FOCUS_MODE, focusMode);
+    }
+
+    private void loadCameraPreferences() {
+        CameraSettings settings = new CameraSettings(mActivity, mInitialParams,
+                mCameraId, CameraHolder.instance().getCameraInfo());
+        mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
+    }
+
+    @Override
+    public void onOrientationChanged(int orientation) {
+        // We keep the last known orientation. So if the user first orient
+        // the camera then point the camera to floor or sky, we still have
+        // the correct orientation.
+        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
+        mOrientation = Util.roundOrientation(orientation, mOrientation);
+
+        // Show the toast after getting the first orientation changed.
+        if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
+            mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
+            showTapToFocusToast();
+        }
+    }
+
+    @Override
+    public void onStop() {
+        if (mMediaProviderClient != null) {
+            mMediaProviderClient.release();
+            mMediaProviderClient = null;
+        }
+    }
+
+    @Override
+    public void onCaptureCancelled() {
+        mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
+        mActivity.finish();
+    }
+
+    @Override
+    public void onCaptureRetake() {
+        if (mPaused)
+            return;
+        mUI.hidePostCaptureAlert();
+        setupPreview();
+    }
+
+    @Override
+    public void onCaptureDone() {
+        if (mPaused) {
+            return;
+        }
+
+        byte[] data = mJpegImageData;
+
+        if (mCropValue == null) {
+            // First handle the no crop case -- just return the value.  If the
+            // caller specifies a "save uri" then write the data to its
+            // stream. Otherwise, pass back a scaled down version of the bitmap
+            // directly in the extras.
+            if (mSaveUri != null) {
+                OutputStream outputStream = null;
+                try {
+                    outputStream = mContentResolver.openOutputStream(mSaveUri);
+                    outputStream.write(data);
+                    outputStream.close();
+
+                    mActivity.setResultEx(Activity.RESULT_OK);
+                    mActivity.finish();
+                } catch (IOException ex) {
+                    // ignore exception
+                } finally {
+                    Util.closeSilently(outputStream);
+                }
+            } else {
+                ExifInterface exif = Exif.getExif(data);
+                int orientation = Exif.getOrientation(exif);
+                Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
+                bitmap = Util.rotate(bitmap, orientation);
+                mActivity.setResultEx(Activity.RESULT_OK,
+                        new Intent("inline-data").putExtra("data", bitmap));
+                mActivity.finish();
+            }
+        } else {
+            // Save the image to a temp file and invoke the cropper
+            Uri tempUri = null;
+            FileOutputStream tempStream = null;
+            try {
+                File path = mActivity.getFileStreamPath(sTempCropFilename);
+                path.delete();
+                tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
+                tempStream.write(data);
+                tempStream.close();
+                tempUri = Uri.fromFile(path);
+            } catch (FileNotFoundException ex) {
+                mActivity.setResultEx(Activity.RESULT_CANCELED);
+                mActivity.finish();
+                return;
+            } catch (IOException ex) {
+                mActivity.setResultEx(Activity.RESULT_CANCELED);
+                mActivity.finish();
+                return;
+            } finally {
+                Util.closeSilently(tempStream);
+            }
+
+            Bundle newExtras = new Bundle();
+            if (mCropValue.equals("circle")) {
+                newExtras.putString("circleCrop", "true");
+            }
+            if (mSaveUri != null) {
+                newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
+            } else {
+                newExtras.putBoolean(CropExtras.KEY_RETURN_DATA, true);
+            }
+            if (mActivity.isSecureCamera()) {
+                newExtras.putBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, true);
+            }
+
+            Intent cropIntent = new Intent(FilterShowActivity.CROP_ACTION);
+
+            cropIntent.setData(tempUri);
+            cropIntent.putExtras(newExtras);
+
+            mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
+        }
+    }
+
+    @Override
+    public void onShutterButtonFocus(boolean pressed) {
+        if (mPaused || mUI.collapseCameraControls()
+                || (mCameraState == SNAPSHOT_IN_PROGRESS)
+                || (mCameraState == PREVIEW_STOPPED)) return;
+
+        // Do not do focus if there is not enough storage.
+        if (pressed && !canTakePicture()) return;
+
+        if (pressed) {
+            if (mSceneMode == Util.SCENE_MODE_HDR) {
+                mUI.hideSwitcher();
+                //TODO: mActivity.setSwipingEnabled(false);
+            }
+            mFocusManager.onShutterDown();
+        } else {
+            // for countdown mode, we need to postpone the shutter release
+            // i.e. lock the focus during countdown.
+            if (!mUI.isCountingDown()) {
+                mFocusManager.onShutterUp();
+            }
+        }
+    }
+
+    @Override
+    public void onShutterButtonClick() {
+        if (mPaused || mUI.collapseCameraControls()
+                || (mCameraState == SWITCHING_CAMERA)
+                || (mCameraState == PREVIEW_STOPPED)) return;
+
+        // Do not take the picture if there is not enough storage.
+        if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
+            Log.i(TAG, "Not enough space or storage not ready. remaining="
+                    + mActivity.getStorageSpace());
+            return;
+        }
+        Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
+
+        // If the user wants to do a snapshot while the previous one is still
+        // in progress, remember the fact and do it after we finish the previous
+        // one and re-start the preview. Snapshot in progress also includes the
+        // state that autofocus is focusing and a picture will be taken when
+        // focus callback arrives.
+        if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)
+                && !mIsImageCaptureIntent) {
+            mSnapshotOnIdle = true;
+            return;
+        }
+
+        String timer = mPreferences.getString(
+                CameraSettings.KEY_TIMER,
+                mActivity.getString(R.string.pref_camera_timer_default));
+        boolean playSound = mPreferences.getString(CameraSettings.KEY_TIMER_SOUND_EFFECTS,
+                mActivity.getString(R.string.pref_camera_timer_sound_default))
+                .equals(mActivity.getString(R.string.setting_on_value));
+
+        int seconds = Integer.parseInt(timer);
+        // When shutter button is pressed, check whether the previous countdown is
+        // finished. If not, cancel the previous countdown and start a new one.
+        if (mUI.isCountingDown()) {
+            mUI.cancelCountDown();
+        }
+        if (seconds > 0) {
+            mUI.startCountDown(seconds, playSound);
+        } else {
+           mSnapshotOnIdle = false;
+           mFocusManager.doSnap();
+        }
+    }
+
+    @Override
+    public void installIntentFilter() {
+    }
+
+    @Override
+    public boolean updateStorageHintOnResume() {
+        return mFirstTimeInitialized;
+    }
+
+    @Override
+    public void updateCameraAppView() {
+    }
+
+    @Override
+    public void onResumeBeforeSuper() {
+        mPaused = false;
+    }
+
+    @Override
+    public void onResumeAfterSuper() {
+        if (mOpenCameraFail || mCameraDisabled) return;
+
+        mJpegPictureCallbackTime = 0;
+        mZoomValue = 0;
+        // Start the preview if it is not started.
+        if (mCameraState == PREVIEW_STOPPED && mCameraStartUpThread == null) {
+            resetExposureCompensation();
+            mCameraStartUpThread = new CameraStartUpThread();
+            mCameraStartUpThread.start();
+        }
+
+        // If first time initialization is not finished, put it in the
+        // message queue.
+        if (!mFirstTimeInitialized) {
+            mHandler.sendEmptyMessage(FIRST_TIME_INIT);
+        } else {
+            initializeSecondTime();
+        }
+        keepScreenOnAwhile();
+
+        // Dismiss open menu if exists.
+        PopupManager.getInstance(mActivity).notifyShowPopup(null);
+        UsageStatistics.onContentViewChanged(
+                UsageStatistics.COMPONENT_CAMERA, "PhotoModule");
+
+        Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        if (gsensor != null) {
+            mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
+        }
+
+        Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+        if (msensor != null) {
+            mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
+        }
+    }
+
+    void waitCameraStartUpThread() {
+        try {
+            if (mCameraStartUpThread != null) {
+                mCameraStartUpThread.cancel();
+                mCameraStartUpThread.join();
+                mCameraStartUpThread = null;
+                setCameraState(IDLE);
+            }
+        } catch (InterruptedException e) {
+            // ignore
+        }
+    }
+
+    @Override
+    public void onPauseBeforeSuper() {
+        mPaused = true;
+        Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+        if (gsensor != null) {
+            mSensorManager.unregisterListener(this, gsensor);
+        }
+
+        Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+        if (msensor != null) {
+            mSensorManager.unregisterListener(this, msensor);
+        }
+    }
+
+    @Override
+    public void onPauseAfterSuper() {
+        // Wait the camera start up thread to finish.
+        waitCameraStartUpThread();
+
+        // When camera is started from secure lock screen for the first time
+        // after screen on, the activity gets onCreate->onResume->onPause->onResume.
+        // To reduce the latency, keep the camera for a short time so it does
+        // not need to be opened again.
+        if (mCameraDevice != null && mActivity.isSecureCamera()
+                && ActivityBase.isFirstStartAfterScreenOn()) {
+            ActivityBase.resetFirstStartAfterScreenOn();
+            CameraHolder.instance().keep(KEEP_CAMERA_TIMEOUT);
+        }
+        // Reset the focus first. Camera CTS does not guarantee that
+        // cancelAutoFocus is allowed after preview stops.
+        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+            mCameraDevice.cancelAutoFocus();
+        }
+        stopPreview();
+
+        mNamedImages = null;
+
+        if (mLocationManager != null) mLocationManager.recordLocation(false);
+
+        // If we are in an image capture intent and has taken
+        // a picture, we just clear it in onPause.
+        mJpegImageData = null;
+
+        // Remove the messages in the event queue.
+        mHandler.removeMessages(SETUP_PREVIEW);
+        mHandler.removeMessages(FIRST_TIME_INIT);
+        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
+        mHandler.removeMessages(SWITCH_CAMERA);
+        mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
+        mHandler.removeMessages(CAMERA_OPEN_DONE);
+        mHandler.removeMessages(START_PREVIEW_DONE);
+        mHandler.removeMessages(OPEN_CAMERA_FAIL);
+        mHandler.removeMessages(CAMERA_DISABLED);
+
+        closeCamera();
+
+        resetScreenOn();
+        mUI.onPause();
+
+        mPendingSwitchCameraId = -1;
+        if (mFocusManager != null) mFocusManager.removeMessages();
+        MediaSaveService s = mActivity.getMediaSaveService();
+        if (s != null) {
+            s.setListener(null);
+        }
+    }
+
+    /**
+     * The focus manager is the first UI related element to get initialized,
+     * and it requires the RenderOverlay, so initialize it here
+     */
+    private void initializeFocusManager() {
+        // Create FocusManager object. startPreview needs it.
+        // if mFocusManager not null, reuse it
+        // otherwise create a new instance
+        if (mFocusManager != null) {
+            mFocusManager.removeMessages();
+        } else {
+            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+            boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+            String[] defaultFocusModes = mActivity.getResources().getStringArray(
+                    R.array.pref_camera_focusmode_default_array);
+            mFocusManager = new FocusOverlayManager(mPreferences, defaultFocusModes,
+                    mInitialParams, this, mirror,
+                    mActivity.getMainLooper(), mUI);
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        Log.v(TAG, "onConfigurationChanged");
+        setDisplayOrientation();
+    }
+
+    @Override
+    public void onActivityResult(
+            int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case REQUEST_CROP: {
+                Intent intent = new Intent();
+                if (data != null) {
+                    Bundle extras = data.getExtras();
+                    if (extras != null) {
+                        intent.putExtras(extras);
+                    }
+                }
+                mActivity.setResultEx(resultCode, intent);
+                mActivity.finish();
+
+                File path = mActivity.getFileStreamPath(sTempCropFilename);
+                path.delete();
+
+                break;
+            }
+        }
+    }
+
+    private boolean canTakePicture() {
+        return isCameraIdle() && (mActivity.getStorageSpace() > Storage.LOW_STORAGE_THRESHOLD);
+    }
+
+    @Override
+    public void autoFocus() {
+        mFocusStartTime = System.currentTimeMillis();
+        mCameraDevice.autoFocus(mAutoFocusCallback);
+        setCameraState(FOCUSING);
+    }
+
+    @Override
+    public void cancelAutoFocus() {
+        mCameraDevice.cancelAutoFocus();
+        setCameraState(IDLE);
+        setCameraParameters(UPDATE_PARAM_PREFERENCE);
+    }
+
+    // Preview area is touched. Handle touch focus.
+    @Override
+    public void onSingleTapUp(View view, int x, int y) {
+        if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
+                || mCameraState == SNAPSHOT_IN_PROGRESS
+                || mCameraState == SWITCHING_CAMERA
+                || mCameraState == PREVIEW_STOPPED) {
+            return;
+        }
+
+        // Do not trigger touch focus if popup window is opened.
+        if (mUI.removeTopLevelPopup()) return;
+
+        // Check if metering area or focus area is supported.
+        if (!mFocusAreaSupported && !mMeteringAreaSupported) return;
+        mFocusManager.onSingleTapUp(x, y);
+    }
+
+    @Override
+    public boolean onBackPressed() {
+        return mUI.onBackPressed();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_VOLUME_UP:
+        case KeyEvent.KEYCODE_VOLUME_DOWN:
+        case KeyEvent.KEYCODE_FOCUS:
+            if (/*TODO: mActivity.isInCameraApp() &&*/ mFirstTimeInitialized) {
+                if (event.getRepeatCount() == 0) {
+                    onShutterButtonFocus(true);
+                }
+                return true;
+            }
+            return false;
+        case KeyEvent.KEYCODE_CAMERA:
+            if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
+                onShutterButtonClick();
+            }
+            return true;
+        case KeyEvent.KEYCODE_DPAD_CENTER:
+            // If we get a dpad center event without any focused view, move
+            // the focus to the shutter button and press it.
+            if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
+                // Start auto-focus immediately to reduce shutter lag. After
+                // the shutter button gets the focus, onShutterButtonFocus()
+                // will be called again but it is fine.
+                if (mUI.removeTopLevelPopup()) return true;
+                onShutterButtonFocus(true);
+                mUI.pressShutterButton();
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+        case KeyEvent.KEYCODE_VOLUME_UP:
+        case KeyEvent.KEYCODE_VOLUME_DOWN:
+            if (/*mActivity.isInCameraApp() && */ mFirstTimeInitialized) {
+                onShutterButtonClick();
+                return true;
+            }
+            return false;
+        case KeyEvent.KEYCODE_FOCUS:
+            if (mFirstTimeInitialized) {
+                onShutterButtonFocus(false);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void closeCamera() {
+        if (mCameraDevice != null) {
+            mCameraDevice.setZoomChangeListener(null);
+            if(ApiHelper.HAS_FACE_DETECTION) {
+                mCameraDevice.setFaceDetectionListener(null);
+            }
+            mCameraDevice.setErrorCallback(null);
+            CameraHolder.instance().release();
+            mFaceDetectionStarted = false;
+            mCameraDevice = null;
+            setCameraState(PREVIEW_STOPPED);
+            mFocusManager.onCameraReleased();
+        }
+    }
+
+    private void setDisplayOrientation() {
+        mDisplayRotation = Util.getDisplayRotation(mActivity);
+        mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
+        mCameraDisplayOrientation = mDisplayOrientation;
+        mUI.setDisplayOrientation(mDisplayOrientation);
+        if (mFocusManager != null) {
+            mFocusManager.setDisplayOrientation(mDisplayOrientation);
+        }
+        // Change the camera display orientation
+        if (mCameraDevice != null) {
+            mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
+        }
+    }
+
+    // Only called by UI thread.
+    private void setupPreview() {
+        mFocusManager.resetTouchFocus();
+        startPreview();
+        setCameraState(IDLE);
+        startFaceDetection();
+    }
+
+    // This can be called by UI Thread or CameraStartUpThread. So this should
+    // not modify the views.
+    private void startPreview() {
+        mCameraDevice.setErrorCallback(mErrorCallback);
+
+        // ICS camera frameworks has a bug. Face detection state is not cleared
+        // after taking a picture. Stop the preview to work around it. The bug
+        // was fixed in JB.
+        if (mCameraState != PREVIEW_STOPPED) stopPreview();
+
+        setDisplayOrientation();
+
+        if (!mSnapshotOnIdle) {
+            // If the focus mode is continuous autofocus, call cancelAutoFocus to
+            // resume it because it may have been paused by autoFocus call.
+            if (Util.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
+                mCameraDevice.cancelAutoFocus();
+            }
+            mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
+        }
+        setCameraParameters(UPDATE_PARAM_ALL);
+        // Let UI set its expected aspect ratio
+        mUI.setPreviewSize(mParameters.getPreviewSize());
+        Object st = mUI.getSurfaceTexture();
+        if (st != null) {
+           mCameraDevice.setPreviewTextureAsync((SurfaceTexture) st);
+        }
+
+        Log.v(TAG, "startPreview");
+        mCameraDevice.startPreviewAsync();
+        mFocusManager.onPreviewStarted();
+
+        if (mSnapshotOnIdle) {
+            mHandler.post(mDoSnapRunnable);
+        }
+    }
+
+    @Override
+    public void stopPreview() {
+        if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+            Log.v(TAG, "stopPreview");
+            mCameraDevice.stopPreview();
+            mFaceDetectionStarted = false;
+        }
+        setCameraState(PREVIEW_STOPPED);
+        if (mFocusManager != null) mFocusManager.onPreviewStopped();
+    }
+
+    @SuppressWarnings("deprecation")
+    private void updateCameraParametersInitialize() {
+        // Reset preview frame rate to the maximum because it may be lowered by
+        // video camera application.
+        List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
+        if (frameRates != null) {
+            Integer max = Collections.max(frameRates);
+            mParameters.setPreviewFrameRate(max);
+        }
+
+        mParameters.set(Util.RECORDING_HINT, Util.FALSE);
+
+        // Disable video stabilization. Convenience methods not available in API
+        // level <= 14
+        String vstabSupported = mParameters.get("video-stabilization-supported");
+        if ("true".equals(vstabSupported)) {
+            mParameters.set("video-stabilization", "false");
+        }
+    }
+
+    private void updateCameraParametersZoom() {
+        // Set zoom.
+        if (mParameters.isZoomSupported()) {
+            mParameters.setZoom(mZoomValue);
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void setAutoExposureLockIfSupported() {
+        if (mAeLockSupported) {
+            mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void setAutoWhiteBalanceLockIfSupported() {
+        if (mAwbLockSupported) {
+            mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void setFocusAreasIfSupported() {
+        if (mFocusAreaSupported) {
+            mParameters.setFocusAreas(mFocusManager.getFocusAreas());
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void setMeteringAreasIfSupported() {
+        if (mMeteringAreaSupported) {
+            // Use the same area for focus and metering.
+            mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
+        }
+    }
+
+    private void updateCameraParametersPreference() {
+        setAutoExposureLockIfSupported();
+        setAutoWhiteBalanceLockIfSupported();
+        setFocusAreasIfSupported();
+        setMeteringAreasIfSupported();
+
+        // Set picture size.
+        String pictureSize = mPreferences.getString(
+                CameraSettings.KEY_PICTURE_SIZE, null);
+        if (pictureSize == null) {
+            CameraSettings.initialCameraPictureSize(mActivity, mParameters);
+        } else {
+            List<Size> supported = mParameters.getSupportedPictureSizes();
+            CameraSettings.setCameraPictureSize(
+                    pictureSize, supported, mParameters);
+        }
+        Size size = mParameters.getPictureSize();
+
+        // Set a preview size that is closest to the viewfinder height and has
+        // the right aspect ratio.
+        List<Size> sizes = mParameters.getSupportedPreviewSizes();
+        Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
+                (double) size.width / size.height);
+        Size original = mParameters.getPreviewSize();
+        if (!original.equals(optimalSize)) {
+            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
+
+            // Zoom related settings will be changed for different preview
+            // sizes, so set and read the parameters to get latest values
+            mCameraDevice.setParameters(mParameters);
+            mParameters = mCameraDevice.getParameters();
+        }
+        Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
+
+        // Since changing scene mode may change supported values, set scene mode
+        // first. HDR is a scene mode. To promote it in UI, it is stored in a
+        // separate preference.
+        String hdr = mPreferences.getString(CameraSettings.KEY_CAMERA_HDR,
+                mActivity.getString(R.string.pref_camera_hdr_default));
+        if (mActivity.getString(R.string.setting_on_value).equals(hdr)) {
+            mSceneMode = Util.SCENE_MODE_HDR;
+        } else {
+            mSceneMode = mPreferences.getString(
+                CameraSettings.KEY_SCENE_MODE,
+                mActivity.getString(R.string.pref_camera_scenemode_default));
+        }
+        if (Util.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
+            if (!mParameters.getSceneMode().equals(mSceneMode)) {
+                mParameters.setSceneMode(mSceneMode);
+
+                // Setting scene mode will change the settings of flash mode,
+                // white balance, and focus mode. Here we read back the
+                // parameters, so we can know those settings.
+                mCameraDevice.setParameters(mParameters);
+                mParameters = mCameraDevice.getParameters();
+            }
+        } else {
+            mSceneMode = mParameters.getSceneMode();
+            if (mSceneMode == null) {
+                mSceneMode = Parameters.SCENE_MODE_AUTO;
+            }
+        }
+
+        // Set JPEG quality.
+        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
+                CameraProfile.QUALITY_HIGH);
+        mParameters.setJpegQuality(jpegQuality);
+
+        // For the following settings, we need to check if the settings are
+        // still supported by latest driver, if not, ignore the settings.
+
+        // Set exposure compensation
+        int value = CameraSettings.readExposure(mPreferences);
+        int max = mParameters.getMaxExposureCompensation();
+        int min = mParameters.getMinExposureCompensation();
+        if (value >= min && value <= max) {
+            mParameters.setExposureCompensation(value);
+        } else {
+            Log.w(TAG, "invalid exposure range: " + value);
+        }
+
+        if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
+            // Set flash mode.
+            String flashMode = mPreferences.getString(
+                    CameraSettings.KEY_FLASH_MODE,
+                    mActivity.getString(R.string.pref_camera_flashmode_default));
+            List<String> supportedFlash = mParameters.getSupportedFlashModes();
+            if (Util.isSupported(flashMode, supportedFlash)) {
+                mParameters.setFlashMode(flashMode);
+            } else {
+                flashMode = mParameters.getFlashMode();
+                if (flashMode == null) {
+                    flashMode = mActivity.getString(
+                            R.string.pref_camera_flashmode_no_flash);
+                }
+            }
+
+            // Set white balance parameter.
+            String whiteBalance = mPreferences.getString(
+                    CameraSettings.KEY_WHITE_BALANCE,
+                    mActivity.getString(R.string.pref_camera_whitebalance_default));
+            if (Util.isSupported(whiteBalance,
+                    mParameters.getSupportedWhiteBalance())) {
+                mParameters.setWhiteBalance(whiteBalance);
+            } else {
+                whiteBalance = mParameters.getWhiteBalance();
+                if (whiteBalance == null) {
+                    whiteBalance = Parameters.WHITE_BALANCE_AUTO;
+                }
+            }
+
+            // Set focus mode.
+            mFocusManager.overrideFocusMode(null);
+            mParameters.setFocusMode(mFocusManager.getFocusMode());
+        } else {
+            mFocusManager.overrideFocusMode(mParameters.getFocusMode());
+        }
+
+        if (mContinousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
+            updateAutoFocusMoveCallback();
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+    private void updateAutoFocusMoveCallback() {
+        if (mParameters.getFocusMode().equals(Util.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+            mCameraDevice.setAutoFocusMoveCallback(
+                (AutoFocusMoveCallback) mAutoFocusMoveCallback);
+        } else {
+            mCameraDevice.setAutoFocusMoveCallback(null);
+        }
+    }
+
+    // We separate the parameters into several subsets, so we can update only
+    // the subsets actually need updating. The PREFERENCE set needs extra
+    // locking because the preference can be changed from GLThread as well.
+    private void setCameraParameters(int updateSet) {
+        if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
+            updateCameraParametersInitialize();
+        }
+
+        if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
+            updateCameraParametersZoom();
+        }
+
+        if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
+            updateCameraParametersPreference();
+        }
+
+        mCameraDevice.setParameters(mParameters);
+    }
+
+    // If the Camera is idle, update the parameters immediately, otherwise
+    // accumulate them in mUpdateSet and update later.
+    private void setCameraParametersWhenIdle(int additionalUpdateSet) {
+        mUpdateSet |= additionalUpdateSet;
+        if (mCameraDevice == null) {
+            // We will update all the parameters when we open the device, so
+            // we don't need to do anything now.
+            mUpdateSet = 0;
+            return;
+        } else if (isCameraIdle()) {
+            setCameraParameters(mUpdateSet);
+            updateSceneMode();
+            mUpdateSet = 0;
+        } else {
+            if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
+                mHandler.sendEmptyMessageDelayed(
+                        SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
+            }
+        }
+    }
+
+    public boolean isCameraIdle() {
+        return (mCameraState == IDLE) ||
+                (mCameraState == PREVIEW_STOPPED) ||
+                ((mFocusManager != null) && mFocusManager.isFocusCompleted()
+                        && (mCameraState != SWITCHING_CAMERA));
+    }
+
+    public boolean isImageCaptureIntent() {
+        String action = mActivity.getIntent().getAction();
+        return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
+                || ActivityBase.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
+    }
+
+    private void setupCaptureParams() {
+        Bundle myExtras = mActivity.getIntent().getExtras();
+        if (myExtras != null) {
+            mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+            mCropValue = myExtras.getString("crop");
+        }
+    }
+
+    @Override
+    public void onSharedPreferenceChanged() {
+        // ignore the events after "onPause()"
+        if (mPaused) return;
+
+        boolean recordLocation = RecordLocationPreference.get(
+                mPreferences, mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+
+        setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
+        mUI.updateOnScreenIndicators(mParameters, mPreferences);
+    }
+
+    @Override
+    public void onCameraPickerClicked(int cameraId) {
+        if (mPaused || mPendingSwitchCameraId != -1) return;
+
+        mPendingSwitchCameraId = cameraId;
+
+        Log.v(TAG, "Start to switch camera. cameraId=" + cameraId);
+        // We need to keep a preview frame for the animation before
+        // releasing the camera. This will trigger onPreviewTextureCopied.
+        //TODO: Need to animate the camera switch
+        switchCamera();
+    }
+
+    // Preview texture has been copied. Now camera can be released and the
+    // animation can be started.
+    @Override
+    public void onPreviewTextureCopied() {
+        mHandler.sendEmptyMessage(SWITCH_CAMERA);
+    }
+
+    @Override
+    public void onCaptureTextureCopied() {
+    }
+
+    @Override
+    public void onUserInteraction() {
+      if (!mActivity.isFinishing()) keepScreenOnAwhile();
+    }
+
+    private void resetScreenOn() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    private void keepScreenOnAwhile() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+    }
+
+    @Override
+    public void onOverriddenPreferencesClicked() {
+        if (mPaused) return;
+        mUI.showPreferencesToast();
+    }
+
+    private void showTapToFocusToast() {
+        // TODO: Use a toast?
+        new RotateTextToast(mActivity, R.string.tap_to_focus, 0).show();
+        // Clear the preference.
+        Editor editor = mPreferences.edit();
+        editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
+        editor.apply();
+    }
+
+    private void initializeCapabilities() {
+        mInitialParams = mCameraDevice.getParameters();
+        mFocusAreaSupported = Util.isFocusAreaSupported(mInitialParams);
+        mMeteringAreaSupported = Util.isMeteringAreaSupported(mInitialParams);
+        mAeLockSupported = Util.isAutoExposureLockSupported(mInitialParams);
+        mAwbLockSupported = Util.isAutoWhiteBalanceLockSupported(mInitialParams);
+        mContinousFocusSupported = mInitialParams.getSupportedFocusModes().contains(
+                Util.FOCUS_MODE_CONTINUOUS_PICTURE);
+    }
+
+    @Override
+    public void onCountDownFinished() {
+        mSnapshotOnIdle = false;
+        mFocusManager.doSnap();
+        mFocusManager.onShutterUp();
+    }
+
+    @Override
+    public boolean needsSwitcher() {
+        return !mIsImageCaptureIntent;
+    }
+
+    @Override
+    public boolean needsPieMenu() {
+        return true;
+    }
+
+    @Override
+    public void onShowSwitcherPopup() {
+        mUI.onShowSwitcherPopup();
+    }
+
+    @Override
+    public int onZoomChanged(int index) {
+        // Not useful to change zoom value when the activity is paused.
+        if (mPaused) return index;
+        mZoomValue = index;
+        if (mParameters == null || mCameraDevice == null) return index;
+        // Set zoom parameters asynchronously
+        mParameters.setZoom(mZoomValue);
+        mCameraDevice.setParametersAsync(mParameters);
+        Parameters p = mCameraDevice.getParameters();
+        if (p != null) return p.getZoom();
+        return index;
+    }
+
+    @Override
+    public int getCameraState() {
+        return mCameraState;
+    }
+
+    @Override
+    public void onQueueStatus(boolean full) {
+        mUI.enableShutter(!full);
+    }
+
+    @Override
+    public void onMediaSaveServiceConnected(MediaSaveService s) {
+        // We set the listener only when both service and shutterbutton
+        // are initialized.
+        if (mFirstTimeInitialized) {
+            s.setListener(this);
+        }
+    }
+
+    @Override
+    public void onAccuracyChanged(Sensor sensor, int accuracy) {
+    }
+
+    @Override
+    public void onSensorChanged(SensorEvent event) {
+        int type = event.sensor.getType();
+        float[] data;
+        if (type == Sensor.TYPE_ACCELEROMETER) {
+            data = mGData;
+        } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
+            data = mMData;
+        } else {
+            // we should not be here.
+            return;
+        }
+        for (int i = 0; i < 3 ; i++) {
+            data[i] = event.values[i];
+        }
+        float[] orientation = new float[3];
+        SensorManager.getRotationMatrix(mR, null, mGData, mMData);
+        SensorManager.getOrientation(mR, orientation);
+        mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
+        if (mHeading < 0) {
+            mHeading += 360;
+        }
+    }
+/* Below is no longer needed, except to get rid of compile error
+ * TODO: Remove these
+ */
+
+    // TODO: Delete this function after old camera code is removed
+    @Override
+    public void onRestorePreferencesClicked() {}
+
+    @Override
+    public void onFullScreenChanged(boolean full) {
+        /* //TODO:
+        mUI.onFullScreenChanged(full);
+        if (ApiHelper.HAS_SURFACE_TEXTURE) {
+            if (mActivity.mCameraScreenNail != null) {
+                ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full);
+            }
+            return;
+        } */
+    }
+
+}
diff --git a/src/com/android/camera/NewPhotoUI.java b/src/com/android/camera/NewPhotoUI.java
new file mode 100644
index 0000000..97f9292
--- /dev/null
+++ b/src/com/android/camera/NewPhotoUI.java
@@ -0,0 +1,879 @@
+/*
+ * 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.graphics.Matrix;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.Camera.Face;
+import android.hardware.Camera.FaceDetectionListener;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.TextureView;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.camera.CameraPreference.OnPreferenceChangedListener;
+import com.android.camera.FocusOverlayManager.FocusUI;
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
+import com.android.camera.ui.CountDownView;
+import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
+import com.android.camera.ui.CameraSwitcher;
+import com.android.camera.ui.FaceView;
+import com.android.camera.ui.FocusIndicator;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.PieRenderer.PieListener;
+import com.android.camera.ui.RenderOverlay;
+import com.android.camera.ui.ZoomRenderer;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.io.IOException;
+import java.util.List;
+
+public class NewPhotoUI implements PieListener,
+    NewPreviewGestures.SingleTapListener,
+    NewPreviewGestures.CancelEventListener,
+    FocusUI, TextureView.SurfaceTextureListener,
+    LocationManager.Listener,
+    FaceDetectionListener,
+    NewPreviewGestures.SwipeListener {
+
+    private static final String TAG = "CAM_UI";
+    private static final int UPDATE_TRANSFORM_MATRIX = 1;
+    private NewCameraActivity mActivity;
+    private PhotoController mController;
+    private NewPreviewGestures mGestures;
+
+    private View mRootView;
+    private Object mSurfaceTexture;
+
+    private AbstractSettingPopup mPopup;
+    private ShutterButton mShutterButton;
+    private CountDownView mCountDownView;
+
+    private FaceView mFaceView;
+    private RenderOverlay mRenderOverlay;
+    private View mReviewCancelButton;
+    private View mReviewDoneButton;
+    private View mReviewRetakeButton;
+
+    private View mMenuButton;
+    private View mBlocker;
+    private NewPhotoMenu mMenu;
+    private CameraSwitcher mSwitcher;
+    private View mCameraControls;
+
+    // Small indicators which show the camera settings in the viewfinder.
+    private ImageView mExposureIndicator;
+    private ImageView mFlashIndicator;
+    private ImageView mSceneIndicator;
+    private ImageView mHdrIndicator;
+    // A view group that contains all the small indicators.
+    private View mOnScreenIndicators;
+
+    private PieRenderer mPieRenderer;
+    private ZoomRenderer mZoomRenderer;
+    private Toast mNotSelectableToast;
+
+    private int mZoomMax;
+    private List<Integer> mZoomRatios;
+
+    private int mPreviewWidth = 0;
+    private int mPreviewHeight = 0;
+    private float mSurfaceTextureUncroppedWidth;
+    private float mSurfaceTextureUncroppedHeight;
+
+    private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener;
+    private TextureView mTextureView;
+    private Matrix mMatrix = null;
+    private float mAspectRatio = 4f / 3f;
+    private final Object mLock = new Object();
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case UPDATE_TRANSFORM_MATRIX:
+                    setTransformMatrix(mPreviewWidth, mPreviewHeight);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    public interface SurfaceTextureSizeChangedListener {
+        public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight);
+    }
+
+    private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right,
+                int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+            int width = right - left;
+            int height = bottom - top;
+            // Full-screen screennail
+            int w = width;
+            int h = height;
+            if (Util.getDisplayRotation(mActivity) % 180 != 0) {
+                w = height;
+                h = width;
+            }
+            if (mPreviewWidth != width || mPreviewHeight != height) {
+                mPreviewWidth = width;
+                mPreviewHeight = height;
+                onScreenSizeChanged(width, height, w, h);
+                mController.onScreenSizeChanged(width, height, w, h);
+            }
+        }
+    };
+
+    public NewPhotoUI(NewCameraActivity activity, PhotoController controller, View parent) {
+        mActivity = activity;
+        mController = controller;
+        mRootView = parent;
+
+        mActivity.getLayoutInflater().inflate(R.layout.new_photo_module,
+                (ViewGroup) mRootView, true);
+        mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
+        // display the view
+        mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);
+        mTextureView.setSurfaceTextureListener(this);
+        mTextureView.addOnLayoutChangeListener(mLayoutListener);
+        initIndicators();
+
+        mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
+        mSwitcher = (CameraSwitcher) mRootView.findViewById(R.id.camera_switcher);
+        mSwitcher.setCurrentIndex(0);
+        mSwitcher.setSwitchListener((CameraSwitchListener) mActivity);
+        mMenuButton = mRootView.findViewById(R.id.menu);
+        if (ApiHelper.HAS_FACE_DETECTION) {
+            ViewStub faceViewStub = (ViewStub) mRootView
+                    .findViewById(R.id.face_view_stub);
+            if (faceViewStub != null) {
+                faceViewStub.inflate();
+                mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
+                setSurfaceTextureSizeChangedListener(
+                        (SurfaceTextureSizeChangedListener) mFaceView);
+            }
+        }
+        mCameraControls = mRootView.findViewById(R.id.camera_controls);
+    }
+
+    public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) {
+        setTransformMatrix(width, height);
+    }
+
+    public void setSurfaceTextureSizeChangedListener(SurfaceTextureSizeChangedListener listener) {
+        mSurfaceTextureSizeListener = listener;
+    }
+
+    public void setPreviewSize(Size size) {
+        int width = size.width;
+        int height = size.height;
+        if (width == 0 || height == 0) {
+            Log.w(TAG, "Preview size should not be 0.");
+            return;
+        }
+        if (width > height) {
+            mAspectRatio = (float) width / height;
+        } else {
+            mAspectRatio = (float) height / width;
+        }
+        mHandler.sendEmptyMessage(UPDATE_TRANSFORM_MATRIX);
+    }
+
+    private void setTransformMatrix(int width, int height) {
+        mMatrix = mTextureView.getTransform(mMatrix);
+        int orientation = Util.getDisplayRotation(mActivity);
+        float scaleX = 1f, scaleY = 1f;
+        float scaledTextureWidth, scaledTextureHeight;
+        if (width > height) {
+            scaledTextureWidth = Math.max(width,
+                    (int) (height * mAspectRatio));
+            scaledTextureHeight = Math.max(height,
+                    (int)(width / mAspectRatio));
+        } else {
+            scaledTextureWidth = Math.max(width,
+                    (int) (height / mAspectRatio));
+            scaledTextureHeight = Math.max(height,
+                    (int) (width * mAspectRatio));
+        }
+
+        if (mSurfaceTextureUncroppedWidth != scaledTextureWidth ||
+                mSurfaceTextureUncroppedHeight != scaledTextureHeight) {
+            mSurfaceTextureUncroppedWidth = scaledTextureWidth;
+            mSurfaceTextureUncroppedHeight = scaledTextureHeight;
+            if (mSurfaceTextureSizeListener != null) {
+                mSurfaceTextureSizeListener.onSurfaceTextureSizeChanged(
+                        (int) mSurfaceTextureUncroppedWidth, (int) mSurfaceTextureUncroppedHeight);
+            }
+        }
+        scaleX = scaledTextureWidth / width;
+        scaleY = scaledTextureHeight / height;
+        mMatrix.setScale(scaleX, scaleY, (float) width / 2, (float) height / 2);
+        mTextureView.setTransform(mMatrix);
+    }
+
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        synchronized (mLock) {
+            mSurfaceTexture = surface;
+            mLock.notifyAll();
+        }
+    }
+
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        // Ignored, Camera does all the work for us
+    }
+
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        mSurfaceTexture = null;
+        mController.stopPreview();
+        Log.w(TAG, "surfaceTexture is destroyed");
+        return true;
+    }
+
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        // Invoked every time there's a new Camera preview frame
+    }
+
+    public View getRootView() {
+        return mRootView;
+    }
+
+    private void initIndicators() {
+        mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators);
+        mExposureIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_exposure_indicator);
+        mFlashIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_flash_indicator);
+        mSceneIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_scenemode_indicator);
+        mHdrIndicator = (ImageView) mOnScreenIndicators.findViewById(R.id.menu_hdr_indicator);
+    }
+
+    public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs,
+            Camera.Parameters params, OnPreferenceChangedListener listener) {
+        if (mPieRenderer == null) {
+            mPieRenderer = new PieRenderer(mActivity);
+            mPieRenderer.setPieListener(this);
+        }
+
+        if (mMenu == null) {
+            mMenu = new NewPhotoMenu(mActivity, this, mPieRenderer);
+            mMenu.setListener(listener);
+        }
+        mMenu.initialize(prefGroup);
+
+        if (mZoomRenderer == null) {
+            mZoomRenderer = new ZoomRenderer(mActivity);
+        }
+        mRenderOverlay.addRenderer(mPieRenderer);
+        mRenderOverlay.addRenderer(mZoomRenderer);
+
+        if (mGestures == null) {
+            // this will handle gesture disambiguation and dispatching
+            mGestures = new NewPreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer,
+                    this);
+            mGestures.setCancelEventListener(this);
+        }
+        mGestures.clearTouchReceivers();
+        mGestures.setRenderOverlay(mRenderOverlay);
+        mGestures.addTouchReceiver(mMenuButton);
+        mGestures.addTouchReceiver(mBlocker);
+        // make sure to add touch targets for image capture
+        if (mController.isImageCaptureIntent()) {
+            if (mReviewCancelButton != null) {
+                mGestures.addTouchReceiver(mReviewCancelButton);
+            }
+            if (mReviewDoneButton != null) {
+                mGestures.addTouchReceiver(mReviewDoneButton);
+            }
+        }
+        mRenderOverlay.requestLayout();
+
+        initializeZoom(params);
+        updateOnScreenIndicators(params, prefs);
+    }
+
+    private void openMenu() {
+        if (mPieRenderer != null) {
+            // If autofocus is not finished, cancel autofocus so that the
+            // subsequent touch can be handled by PreviewGestures
+            if (mController.getCameraState() == PhotoController.FOCUSING) {
+                    mController.cancelAutoFocus();
+            }
+            mPieRenderer.showInCenter();
+        }
+    }
+
+    public void initializeControlByIntent() {
+        mBlocker = mActivity.findViewById(R.id.blocker);
+        mMenuButton = mActivity.findViewById(R.id.menu);
+        mMenuButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                openMenu();
+            }
+        });
+        if (mController.isImageCaptureIntent()) {
+            hideSwitcher();
+            ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls);
+            mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
+
+            mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
+            mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
+            mReviewRetakeButton = mActivity.findViewById(R.id.btn_retake);
+            mReviewCancelButton.setVisibility(View.VISIBLE);
+
+            mReviewDoneButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onCaptureDone();
+                }
+            });
+            mReviewCancelButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onCaptureCancelled();
+                }
+            });
+
+            mReviewRetakeButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onCaptureRetake();
+                }
+            });
+        }
+    }
+
+    public void hideUI() {
+        mCameraControls.setVisibility(View.INVISIBLE);
+        hideSwitcher();
+        mShutterButton.setVisibility(View.GONE);
+    }
+
+    public void showUI() {
+        mCameraControls.setVisibility(View.VISIBLE);
+        showSwitcher();
+        mShutterButton.setVisibility(View.VISIBLE);
+    }
+
+    public void hideSwitcher() {
+        mSwitcher.closePopup();
+        mSwitcher.setVisibility(View.INVISIBLE);
+    }
+
+    public void showSwitcher() {
+        mSwitcher.setVisibility(View.VISIBLE);
+    }
+
+    // called from onResume but only the first time
+    public  void initializeFirstTime() {
+        // Initialize shutter button.
+        mShutterButton.setImageResource(R.drawable.btn_new_shutter);
+        mShutterButton.setOnShutterButtonListener(mController);
+        mShutterButton.setVisibility(View.VISIBLE);
+    }
+
+    // called from onResume every other time
+    public void initializeSecondTime(Camera.Parameters params) {
+        initializeZoom(params);
+        if (mController.isImageCaptureIntent()) {
+            hidePostCaptureAlert();
+        }
+        if (mMenu != null) {
+            mMenu.reloadPreferences();
+        }
+    }
+
+    public void initializeZoom(Camera.Parameters params) {
+        if ((params == null) || !params.isZoomSupported()
+                || (mZoomRenderer == null)) return;
+        mZoomMax = params.getMaxZoom();
+        mZoomRatios = params.getZoomRatios();
+        // Currently we use immediate zoom for fast zooming to get better UX and
+        // there is no plan to take advantage of the smooth zoom.
+        if (mZoomRenderer != null) {
+            mZoomRenderer.setZoomMax(mZoomMax);
+            mZoomRenderer.setZoom(params.getZoom());
+            mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom()));
+            mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
+        }
+    }
+
+    public void showGpsOnScreenIndicator(boolean hasSignal) { }
+
+    public void hideGpsOnScreenIndicator() { }
+
+    public void overrideSettings(final String ... keyvalues) {
+        mMenu.overrideSettings(keyvalues);
+    }
+
+    public void updateOnScreenIndicators(Camera.Parameters params,
+            ComboPreferences prefs) {
+        if (params == null) return;
+        updateSceneOnScreenIndicator(params.getSceneMode());
+        updateExposureOnScreenIndicator(params,
+                CameraSettings.readExposure(prefs));
+        updateFlashOnScreenIndicator(params.getFlashMode());
+        updateHdrOnScreenIndicator(params.getSceneMode());
+    }
+
+    private void updateExposureOnScreenIndicator(Camera.Parameters params, int value) {
+        if (mExposureIndicator == null) {
+            return;
+        }
+        int id = 0;
+        float step = params.getExposureCompensationStep();
+        value = (int) Math.round(value * step);
+        switch(value) {
+        case -3:
+            id = R.drawable.ic_indicator_ev_n3;
+            break;
+        case -2:
+            id = R.drawable.ic_indicator_ev_n2;
+            break;
+        case -1:
+            id = R.drawable.ic_indicator_ev_n1;
+            break;
+        case 0:
+            id = R.drawable.ic_indicator_ev_0;
+            break;
+        case 1:
+            id = R.drawable.ic_indicator_ev_p1;
+            break;
+        case 2:
+            id = R.drawable.ic_indicator_ev_p2;
+            break;
+        case 3:
+            id = R.drawable.ic_indicator_ev_p3;
+            break;
+        }
+        mExposureIndicator.setImageResource(id);
+    }
+
+    private void updateFlashOnScreenIndicator(String value) {
+        if (mFlashIndicator == null) {
+            return;
+        }
+        if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) {
+            mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
+        } else {
+            if (Parameters.FLASH_MODE_AUTO.equals(value)) {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto);
+            } else if (Parameters.FLASH_MODE_ON.equals(value)) {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on);
+            } else {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
+            }
+        }
+    }
+
+    private void updateSceneOnScreenIndicator(String value) {
+        if (mSceneIndicator == null) {
+            return;
+        }
+        if ((value == null) || Parameters.SCENE_MODE_AUTO.equals(value)
+                || Parameters.SCENE_MODE_HDR.equals(value)) {
+            mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_off);
+        } else {
+            mSceneIndicator.setImageResource(R.drawable.ic_indicator_sce_on);
+        }
+    }
+
+    private void updateHdrOnScreenIndicator(String value) {
+        if (mHdrIndicator == null) {
+            return;
+        }
+        if ((value != null) && Parameters.SCENE_MODE_HDR.equals(value)) {
+            mHdrIndicator.setImageResource(R.drawable.ic_indicator_hdr_on);
+        } else {
+            mHdrIndicator.setImageResource(R.drawable.ic_indicator_hdr_off);
+        }
+    }
+
+    public void setCameraState(int state) {
+    }
+
+    // Gestures and touch events
+
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (mPopup != null || mSwitcher.showsPopup()) {
+            boolean handled = mRootView.dispatchTouchEvent(m);
+            if (!handled && mPopup != null) {
+                dismissPopup(false);
+            }
+            return handled;
+        } else if (mGestures != null && mRenderOverlay != null) {
+            if (mGestures.dispatchTouch(m)) {
+                return true;
+            } else {
+                return mRootView.dispatchTouchEvent(m);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void onTouchEventCancelled(MotionEvent cancelEvent) {
+        mRootView.dispatchTouchEvent(cancelEvent);
+    }
+
+    public void enableGestures(boolean enable) {
+        if (mGestures != null) {
+            mGestures.setEnabled(enable);
+        }
+    }
+
+    // forward from preview gestures to controller
+    @Override
+    public void onSingleTapUp(View view, int x, int y) {
+        mController.onSingleTapUp(view, x, y);
+    }
+
+    public boolean onBackPressed() {
+        if (mPieRenderer != null && mPieRenderer.showsItems()) {
+            mPieRenderer.hide();
+            return true;
+        }
+        // In image capture mode, back button should:
+        // 1) if there is any popup, dismiss them, 2) otherwise, get out of
+        // image capture
+        if (mController.isImageCaptureIntent()) {
+            if (!removeTopLevelPopup()) {
+                // no popup to dismiss, cancel image capture
+                mController.onCaptureCancelled();
+            }
+            return true;
+        } else if (!mController.isCameraIdle()) {
+            // ignore backs while we're taking a picture
+            return true;
+        } else {
+            return removeTopLevelPopup();
+        }
+    }
+
+    public void onFullScreenChanged(boolean full) {
+        if (mFaceView != null) {
+            mFaceView.setBlockDraw(!full);
+        }
+        if (mPopup != null) {
+            dismissPopup(false, full);
+        }
+        if (mGestures != null) {
+            mGestures.setEnabled(full);
+        }
+        if (mRenderOverlay != null) {
+            // this can not happen in capture mode
+            mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
+        }
+        if (mPieRenderer != null) {
+            mPieRenderer.setBlockFocus(!full);
+        }
+        setShowMenu(full);
+        if (mBlocker != null) {
+            mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
+        }
+        if (!full && mCountDownView != null) mCountDownView.cancelCountDown();
+    }
+
+    public boolean removeTopLevelPopup() {
+        // Remove the top level popup or dialog box and return true if there's any
+        if (mPopup != null) {
+            dismissPopup(true);
+            return true;
+        }
+        return false;
+    }
+
+    public void showPopup(AbstractSettingPopup popup) {
+        hideUI();
+        mBlocker.setVisibility(View.INVISIBLE);
+        setShowMenu(false);
+        mPopup = popup;
+        mPopup.setVisibility(View.VISIBLE);
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT);
+        lp.gravity = Gravity.CENTER;
+        ((FrameLayout) mRootView).addView(mPopup, lp);
+    }
+
+    public void dismissPopup(boolean topPopupOnly) {
+        dismissPopup(topPopupOnly, true);
+    }
+
+    private void dismissPopup(boolean topOnly, boolean fullScreen) {
+        if (fullScreen) {
+            showUI();
+            mBlocker.setVisibility(View.VISIBLE);
+        }
+        setShowMenu(fullScreen);
+        if (mPopup != null) {
+            ((FrameLayout) mRootView).removeView(mPopup);
+            mPopup = null;
+        }
+        mMenu.popupDismissed(topOnly);
+    }
+
+    public void onShowSwitcherPopup() {
+        if (mPieRenderer != null && mPieRenderer.showsItems()) {
+            mPieRenderer.hide();
+        }
+    }
+
+    private void setShowMenu(boolean show) {
+        if (mOnScreenIndicators != null) {
+            mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+        if (mMenuButton != null) {
+            mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    public boolean collapseCameraControls() {
+        // Remove all the popups/dialog boxes
+        boolean ret = false;
+        if (mPopup != null) {
+            dismissPopup(false);
+            ret = true;
+        }
+        return ret;
+    }
+
+    protected void showPostCaptureAlert() {
+        mOnScreenIndicators.setVisibility(View.GONE);
+        mMenuButton.setVisibility(View.GONE);
+        Util.fadeIn(mReviewDoneButton);
+        mShutterButton.setVisibility(View.INVISIBLE);
+        Util.fadeIn(mReviewRetakeButton);
+    }
+
+    protected void hidePostCaptureAlert() {
+        mOnScreenIndicators.setVisibility(View.VISIBLE);
+        mMenuButton.setVisibility(View.VISIBLE);
+        Util.fadeOut(mReviewDoneButton);
+        mShutterButton.setVisibility(View.VISIBLE);
+        Util.fadeOut(mReviewRetakeButton);
+    }
+
+    public void setDisplayOrientation(int orientation) {
+        if (mFaceView != null) {
+            mFaceView.setDisplayOrientation(orientation);
+        }
+    }
+
+    // shutter button handling
+
+    public boolean isShutterPressed() {
+        return mShutterButton.isPressed();
+    }
+
+    public void enableShutter(boolean enabled) {
+        if (mShutterButton != null) {
+            mShutterButton.setEnabled(enabled);
+        }
+    }
+
+    public void pressShutterButton() {
+        if (mShutterButton.isInTouchMode()) {
+            mShutterButton.requestFocusFromTouch();
+        } else {
+            mShutterButton.requestFocus();
+        }
+        mShutterButton.setPressed(true);
+    }
+
+    private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
+        @Override
+        public void onZoomValueChanged(int index) {
+            int newZoom = mController.onZoomChanged(index);
+            if (mZoomRenderer != null) {
+                mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
+            }
+        }
+
+        @Override
+        public void onZoomStart() {
+            if (mPieRenderer != null) {
+                mPieRenderer.setBlockFocus(true);
+            }
+        }
+
+        @Override
+        public void onZoomEnd() {
+            if (mPieRenderer != null) {
+                mPieRenderer.setBlockFocus(false);
+            }
+        }
+    }
+
+    @Override
+    public void onPieOpened(int centerX, int centerY) {
+      //TODO:   mActivity.cancelActivityTouchHandling();
+      //TODO:    mActivity.setSwipingEnabled(false);
+        if (mFaceView != null) {
+            mFaceView.setBlockDraw(true);
+        }
+    }
+
+    @Override
+    public void onPieClosed() {
+      //TODO:     mActivity.setSwipingEnabled(true);
+        if (mFaceView != null) {
+            mFaceView.setBlockDraw(false);
+        }
+    }
+
+    public Object getSurfaceTexture() {
+        synchronized (mLock) {
+            if (mSurfaceTexture == null) {
+                try {
+                    mLock.wait();
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "Unexpected interruption when waiting to get surface texture");
+                }
+            }
+        }
+        return mSurfaceTexture;
+    }
+
+    // Countdown timer
+
+    private void initializeCountDown() {
+        mActivity.getLayoutInflater().inflate(R.layout.count_down_to_capture,
+                (ViewGroup) mRootView, true);
+        mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture));
+        mCountDownView.setCountDownFinishedListener((OnCountDownFinishedListener) mController);
+    }
+
+    public boolean isCountingDown() {
+        return mCountDownView != null && mCountDownView.isCountingDown();
+    }
+
+    public void cancelCountDown() {
+        if (mCountDownView == null) return;
+        mCountDownView.cancelCountDown();
+    }
+
+    public void startCountDown(int sec, boolean playSound) {
+        if (mCountDownView == null) initializeCountDown();
+        mCountDownView.startCountDown(sec, playSound);
+    }
+
+    public void showPreferencesToast() {
+        if (mNotSelectableToast == null) {
+            String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
+            mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
+        }
+        mNotSelectableToast.show();
+    }
+
+    public void onPause() {
+        cancelCountDown();
+
+        // Clear UI.
+        collapseCameraControls();
+        if (mFaceView != null) mFaceView.clear();
+
+        mPreviewWidth = 0;
+        mPreviewHeight = 0;
+    }
+
+    // focus UI implementation
+
+    private FocusIndicator getFocusIndicator() {
+        return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer;
+    }
+
+    @Override
+    public boolean hasFaces() {
+        return (mFaceView != null && mFaceView.faceExists());
+    }
+
+    public void clearFaces() {
+        if (mFaceView != null) mFaceView.clear();
+    }
+
+    @Override
+    public void clearFocus() {
+        FocusIndicator indicator = getFocusIndicator();
+        if (indicator != null) indicator.clear();
+    }
+
+    @Override
+    public void setFocusPosition(int x, int y) {
+        mPieRenderer.setFocus(x, y);
+    }
+
+    @Override
+    public void onFocusStarted() {
+        getFocusIndicator().showStart();
+    }
+
+    @Override
+    public void onFocusSucceeded(boolean timeout) {
+        getFocusIndicator().showSuccess(timeout);
+    }
+
+    @Override
+    public void onFocusFailed(boolean timeout) {
+        getFocusIndicator().showFail(timeout);
+    }
+
+    @Override
+    public void pauseFaceDetection() {
+        if (mFaceView != null) mFaceView.pause();
+    }
+
+    @Override
+    public void resumeFaceDetection() {
+        if (mFaceView != null) mFaceView.resume();
+    }
+
+    public void onStartFaceDetection(int orientation, boolean mirror) {
+        mFaceView.clear();
+        mFaceView.setVisibility(View.VISIBLE);
+        mFaceView.setDisplayOrientation(orientation);
+        mFaceView.setMirror(mirror);
+        mFaceView.resume();
+    }
+
+    @Override
+    public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
+        mFaceView.setFaces(faces);
+    }
+
+    @Override
+    public void onSwipe(int direction) {
+        if (direction == PreviewGestures.DIR_UP) {
+            openMenu();
+        }
+    }
+}
diff --git a/src/com/android/camera/NewPreviewGestures.java b/src/com/android/camera/NewPreviewGestures.java
new file mode 100644
index 0000000..2718e55
--- /dev/null
+++ b/src/com/android/camera/NewPreviewGestures.java
@@ -0,0 +1,358 @@
+package com.android.camera;
+
+/*
+ * 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.
+ */
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.android.camera.PreviewGestures.SwipeListener;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.RenderOverlay;
+import com.android.camera.ui.ZoomRenderer;
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class NewPreviewGestures
+        implements ScaleGestureDetector.OnScaleGestureListener {
+
+    private static final String TAG = "CAM_gestures";
+
+    private static final long TIMEOUT_PIE = 200;
+    private static final int MSG_PIE = 1;
+    private static final int MODE_NONE = 0;
+    private static final int MODE_PIE = 1;
+    private static final int MODE_ZOOM = 2;
+    private static final int MODE_MODULE = 3;
+    private static final int MODE_ALL = 4;
+    private static final int MODE_SWIPE = 5;
+
+    public static final int DIR_UP = 0;
+    public static final int DIR_DOWN = 1;
+    public static final int DIR_LEFT = 2;
+    public static final int DIR_RIGHT = 3;
+
+    private NewCameraActivity mActivity;
+    private SingleTapListener mTapListener;
+    private CancelEventListener mCancelEventListener;
+    private RenderOverlay mOverlay;
+    private PieRenderer mPie;
+    private ZoomRenderer mZoom;
+    private MotionEvent mDown;
+    private MotionEvent mCurrent;
+    private ScaleGestureDetector mScale;
+    private List<View> mReceivers;
+    private int mMode;
+    private int mSlop;
+    private int mTapTimeout;
+    private boolean mEnabled;
+    private boolean mZoomOnly;
+    private int mOrientation;
+    private int[] mLocation;
+    private SwipeListener mSwipeListener;
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_PIE) {
+                mMode = MODE_PIE;
+                openPie();
+                cancelActivityTouchHandling(mDown);
+            }
+        }
+    };
+
+    public interface SingleTapListener {
+        public void onSingleTapUp(View v, int x, int y);
+    }
+
+    public interface CancelEventListener {
+        public void onTouchEventCancelled(MotionEvent cancelEvent);
+    }
+
+    interface SwipeListener {
+        public void onSwipe(int direction);
+    }
+
+    public NewPreviewGestures(NewCameraActivity ctx, SingleTapListener tapListener,
+            ZoomRenderer zoom, PieRenderer pie, SwipeListener swipe) {
+        mActivity = ctx;
+        mTapListener = tapListener;
+        mPie = pie;
+        mZoom = zoom;
+        mMode = MODE_ALL;
+        mScale = new ScaleGestureDetector(ctx, this);
+        mSlop = (int) ctx.getResources().getDimension(R.dimen.pie_touch_slop);
+        mTapTimeout = ViewConfiguration.getTapTimeout();
+        mEnabled = true;
+        mLocation = new int[2];
+        mSwipeListener = swipe;
+    }
+
+    public void setCancelEventListener(CancelEventListener listener) {
+        mCancelEventListener = listener;
+    }
+
+    public void setRenderOverlay(RenderOverlay overlay) {
+        mOverlay = overlay;
+    }
+
+    public void setOrientation(int orientation) {
+        mOrientation = orientation;
+    }
+
+    public void setEnabled(boolean enabled) {
+        mEnabled = enabled;
+        if (!enabled) {
+            cancelPie();
+        }
+    }
+
+    public void setZoomOnly(boolean zoom) {
+        mZoomOnly = zoom;
+    }
+
+    public void addTouchReceiver(View v) {
+        if (mReceivers == null) {
+            mReceivers = new ArrayList<View>();
+        }
+        mReceivers.add(v);
+    }
+
+    public void clearTouchReceivers() {
+        if (mReceivers != null) {
+            mReceivers.clear();
+        }
+    }
+
+    public boolean dispatchTouch(MotionEvent m) {
+        if (!mEnabled) {
+            return false;
+        }
+        mCurrent = m;
+        if (MotionEvent.ACTION_DOWN == m.getActionMasked()) {
+            if (checkReceivers(m)) {
+                mMode = MODE_MODULE;
+                return false;
+            } else {
+                mMode = MODE_ALL;
+                mDown = MotionEvent.obtain(m);
+                if (mPie != null && mPie.showsItems()) {
+                    mMode = MODE_PIE;
+                    return sendToPie(m);
+                }
+                if (mPie != null && !mZoomOnly) {
+                    mHandler.sendEmptyMessageDelayed(MSG_PIE, TIMEOUT_PIE);
+                }
+                if (mZoom != null) {
+                    mScale.onTouchEvent(m);
+                }
+                // make sure this is ok
+                return false;
+            }
+        } else if (mMode == MODE_NONE) {
+            return false;
+        } else if (mMode == MODE_SWIPE) {
+            if (MotionEvent.ACTION_UP == m.getActionMasked()) {
+                mSwipeListener.onSwipe(getSwipeDirection(m));
+            }
+            return true;
+        } else if (mMode == MODE_PIE) {
+            if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
+                sendToPie(makeCancelEvent(m));
+                if (mZoom != null) {
+                    onScaleBegin(mScale);
+                }
+            } else {
+                return sendToPie(m);
+            }
+            return true;
+        } else if (mMode == MODE_ZOOM) {
+            mScale.onTouchEvent(m);
+            if (!mScale.isInProgress() && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
+                mMode = MODE_NONE;
+                onScaleEnd(mScale);
+            }
+            return true;
+        } else if (mMode == MODE_MODULE) {
+            return false;
+        } else {
+            // didn't receive down event previously;
+            // assume module wasn't initialzed and ignore this event.
+            if (mDown == null) {
+                return true;
+            }
+            if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
+                if (!mZoomOnly) {
+                    cancelPie();
+                    sendToPie(makeCancelEvent(m));
+                }
+                if (mZoom != null) {
+                    mScale.onTouchEvent(m);
+                    onScaleBegin(mScale);
+                }
+            } else if ((mMode == MODE_ZOOM) && !mScale.isInProgress()
+                    && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
+                // user initiated and stopped zoom gesture without zooming
+                mScale.onTouchEvent(m);
+                onScaleEnd(mScale);
+            }
+            // not zoom or pie mode and no timeout yet
+            if (mZoom != null) {
+                boolean res = mScale.onTouchEvent(m);
+                if (mScale.isInProgress()) {
+                    cancelPie();
+                    cancelActivityTouchHandling(m);
+                    return res;
+                }
+            }
+            if (MotionEvent.ACTION_UP == m.getActionMasked()) {
+                cancelPie();
+                cancelActivityTouchHandling(m);
+                // must have been tap
+                if (m.getEventTime() - mDown.getEventTime() < mTapTimeout) {
+                    mTapListener.onSingleTapUp(null,
+                            (int) mDown.getX() - mOverlay.getWindowPositionX(),
+                            (int) mDown.getY() - mOverlay.getWindowPositionY());
+                    return true;
+                } else {
+                    return false;
+                }
+            } else if (MotionEvent.ACTION_MOVE == m.getActionMasked()) {
+                if ((Math.abs(m.getX() - mDown.getX()) > mSlop)
+                        || Math.abs(m.getY() - mDown.getY()) > mSlop) {
+                    // moved too far and no timeout yet, no focus or pie
+                    cancelPie();
+                    int dir = getSwipeDirection(m);
+                    if (dir == DIR_LEFT) {
+                        mMode = MODE_MODULE;
+                        return false;
+                    } else {
+                        cancelActivityTouchHandling(m);
+                        mMode = MODE_NONE;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    private boolean checkReceivers(MotionEvent m) {
+        if (mReceivers != null) {
+            for (View receiver : mReceivers) {
+                if (isInside(m, receiver)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    // left tests for finger moving right to left
+    private int getSwipeDirection(MotionEvent m) {
+        float dx = 0;
+        float dy = 0;
+        switch (mOrientation) {
+        case 0:
+            dx = m.getX() - mDown.getX();
+            dy = m.getY() - mDown.getY();
+            break;
+        case 90:
+            dx = - (m.getY() - mDown.getY());
+            dy = m.getX() - mDown.getX();
+            break;
+        case 180:
+            dx = -(m.getX() - mDown.getX());
+            dy = m.getY() - mDown.getY();
+            break;
+        case 270:
+            dx = m.getY() - mDown.getY();
+            dy = m.getX() - mDown.getX();
+            break;
+        }
+        if (dx < 0 && (Math.abs(dy) / -dx < 2)) return DIR_LEFT;
+        if (dx > 0 && (Math.abs(dy) / dx < 2)) return DIR_RIGHT;
+        if (dy > 0) return DIR_DOWN;
+        return DIR_UP;
+    }
+
+    private boolean isInside(MotionEvent evt, View v) {
+        v.getLocationInWindow(mLocation);
+        return (v.getVisibility() == View.VISIBLE
+                && evt.getX() >= mLocation[0] && evt.getX() < mLocation[0] + v.getWidth()
+                && evt.getY() >= mLocation[1] && evt.getY() < mLocation[1] + v.getHeight());
+    }
+
+    public void cancelActivityTouchHandling(MotionEvent m) {
+        if (mCancelEventListener != null) {
+            mCancelEventListener.onTouchEventCancelled(makeCancelEvent(m));
+        }
+    }
+
+    private MotionEvent makeCancelEvent(MotionEvent m) {
+        MotionEvent c = MotionEvent.obtain(m);
+        c.setAction(MotionEvent.ACTION_CANCEL);
+        return c;
+    }
+
+    private void openPie() {
+        mDown.offsetLocation(-mOverlay.getWindowPositionX(),
+                -mOverlay.getWindowPositionY());
+        mOverlay.directDispatchTouch(mDown, mPie);
+    }
+
+    private void cancelPie() {
+        mHandler.removeMessages(MSG_PIE);
+    }
+
+    private boolean sendToPie(MotionEvent m) {
+        m.offsetLocation(-mOverlay.getWindowPositionX(),
+                -mOverlay.getWindowPositionY());
+        return mOverlay.directDispatchTouch(m, mPie);
+    }
+
+    @Override
+    public boolean onScale(ScaleGestureDetector detector) {
+        return mZoom.onScale(detector);
+    }
+
+    @Override
+    public boolean onScaleBegin(ScaleGestureDetector detector) {
+        if (mMode != MODE_ZOOM) {
+            mMode = MODE_ZOOM;
+            cancelActivityTouchHandling(mCurrent);
+        }
+        if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
+            return mZoom.onScaleBegin(detector);
+        } else {
+            return true;
+        }
+    }
+
+    @Override
+    public void onScaleEnd(ScaleGestureDetector detector) {
+        if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
+            mZoom.onScaleEnd(detector);
+        }
+    }
+}
+
diff --git a/src/com/android/camera/NewVideoMenu.java b/src/com/android/camera/NewVideoMenu.java
new file mode 100644
index 0000000..1dec27a
--- /dev/null
+++ b/src/com/android/camera/NewVideoMenu.java
@@ -0,0 +1,190 @@
+/*
+ * 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.app.Activity;
+import android.content.Context;
+import android.view.LayoutInflater;
+
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.ListPrefSettingPopup;
+import com.android.camera.ui.MoreSettingPopup;
+import com.android.camera.ui.PieItem;
+import com.android.camera.ui.PieItem.OnClickListener;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.TimeIntervalPopup;
+import com.android.gallery3d.R;
+
+public class NewVideoMenu extends PieController
+        implements MoreSettingPopup.Listener,
+        ListPrefSettingPopup.Listener,
+        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 NewVideoUI mUI;
+    private String[] mOtherKeys;
+    private AbstractSettingPopup mPopup;
+
+    private static final int POPUP_NONE = 0;
+    private static final int POPUP_FIRST_LEVEL = 1;
+    private static final int POPUP_SECOND_LEVEL = 2;
+    private int mPopupStatus;
+    private NewCameraActivity mActivity;
+
+    public NewVideoMenu(NewCameraActivity activity, NewVideoUI ui, PieRenderer pie) {
+        super(activity, pie);
+        mUI = ui;
+        mActivity = activity;
+    }
+
+    public void initialize(PreferenceGroup group) {
+        super.initialize(group);
+        mPopup = null;
+        mPopupStatus = POPUP_NONE;
+
+        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
+        item = makeItem(R.drawable.ic_switch_video_facing_holo_light);
+        item.setPosition(POS_SWITCH, 5);
+        item.setOnClickListener(new OnClickListener() {
+
+            @Override
+            public void onClick(PieItem item) {
+                // Find the index of next camera.
+                ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+                if (pref != null) {
+                    int index = pref.findIndexOfValue(pref.getValue());
+                    CharSequence[] values = pref.getEntryValues();
+                    index = (index + 1) % values.length;
+                    int newCameraId = Integer.parseInt((String) values[index]);
+                    mListener.onCameraPickerClicked(newCameraId);
+                }
+            }
+        });
+        mRenderer.addItem(item);
+        // settings popup
+        mOtherKeys = new String[] {
+                CameraSettings.KEY_VIDEO_EFFECT,
+                CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
+                CameraSettings.KEY_VIDEO_QUALITY,
+                CameraSettings.KEY_RECORD_LOCATION
+        };
+        item = makeItem(R.drawable.ic_settings_holo_light);
+        item.setPosition(POS_SET, 5);
+        item.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(PieItem item) {
+                if (mPopup == null || mPopupStatus != POPUP_FIRST_LEVEL) {
+                    initializePopup();
+                    mPopupStatus = POPUP_FIRST_LEVEL;
+                }
+                mUI.showPopup(mPopup);
+            }
+        });
+        mRenderer.addItem(item);
+    }
+
+    @Override
+    public void reloadPreferences() {
+        super.reloadPreferences();
+        if (mPopup != null) {
+            mPopup.reloadPreference();
+        }
+    }
+
+    @Override
+    public void overrideSettings(final String ... keyvalues) {
+        super.overrideSettings(keyvalues);
+        if (mPopup == null || mPopupStatus != POPUP_FIRST_LEVEL) {
+            mPopupStatus = POPUP_FIRST_LEVEL;
+            initializePopup();
+        }
+        ((MoreSettingPopup) mPopup).overrideSettings(keyvalues);
+    }
+
+    @Override
+    // Hit when an item in the second-level popup gets selected
+    public void onListPrefChanged(ListPreference pref) {
+        if (mPopup != null) {
+            if (mPopupStatus == POPUP_SECOND_LEVEL) {
+                mUI.dismissPopup(true);
+            }
+        }
+        super.onSettingChanged(pref);
+    }
+
+    protected void initializePopup() {
+        LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        MoreSettingPopup popup = (MoreSettingPopup) inflater.inflate(
+                R.layout.more_setting_popup, null, false);
+        popup.setSettingChangedListener(this);
+        popup.initialize(mPreferenceGroup, mOtherKeys);
+        if (mActivity.isSecureCamera()) {
+            // Prevent location preference from getting changed in secure camera mode
+            popup.setPreferenceEnabled(CameraSettings.KEY_RECORD_LOCATION, false);
+        }
+        mPopup = popup;
+    }
+
+    public void popupDismissed(boolean topPopupOnly) {
+        // if the 2nd level popup gets dismissed
+        if (mPopupStatus == POPUP_SECOND_LEVEL) {
+            initializePopup();
+            mPopupStatus = POPUP_FIRST_LEVEL;
+            if (topPopupOnly) mUI.showPopup(mPopup);
+        }
+    }
+
+    @Override
+    // Hit when an item in the first-level popup gets selected, then bring up
+    // the second-level popup
+    public void onPreferenceClicked(ListPreference pref) {
+        if (mPopupStatus != POPUP_FIRST_LEVEL) return;
+
+        LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+
+        if (CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL.equals(pref.getKey())) {
+            TimeIntervalPopup timeInterval = (TimeIntervalPopup) inflater.inflate(
+                    R.layout.time_interval_popup, null, false);
+            timeInterval.initialize((IconListPreference) pref);
+            timeInterval.setSettingChangedListener(this);
+            mUI.dismissPopup(true);
+            mPopup = timeInterval;
+        } else {
+            ListPrefSettingPopup basic = (ListPrefSettingPopup) inflater.inflate(
+                    R.layout.list_pref_setting_popup, null, false);
+            basic.initialize(pref);
+            basic.setSettingChangedListener(this);
+            mUI.dismissPopup(true);
+            mPopup = basic;
+        }
+        mUI.showPopup(mPopup);
+        mPopupStatus = POPUP_SECOND_LEVEL;
+    }
+
+}
diff --git a/src/com/android/camera/NewVideoModule.java b/src/com/android/camera/NewVideoModule.java
new file mode 100644
index 0000000..f8c36c5
--- /dev/null
+++ b/src/com/android/camera/NewVideoModule.java
@@ -0,0 +1,2344 @@
+/*
+ * 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.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.Size;
+import android.location.Location;
+import android.media.CamcorderProfile;
+import android.media.CameraProfile;
+import android.media.MediaRecorder;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.ui.PopupManager;
+import com.android.camera.ui.RotateTextToast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.OrientationManager;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.util.AccessibilityUtils;
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+public class NewVideoModule implements NewCameraModule,
+    VideoController,
+    CameraPreference.OnPreferenceChangedListener,
+    ShutterButton.OnShutterButtonListener,
+    MediaRecorder.OnErrorListener,
+    MediaRecorder.OnInfoListener,
+    EffectsRecorder.EffectsListener {
+
+    private static final String TAG = "CAM_VideoModule";
+
+    // We number the request code from 1000 to avoid collision with Gallery.
+    private static final int REQUEST_EFFECT_BACKDROPPER = 1000;
+
+    private static final int CHECK_DISPLAY_ROTATION = 3;
+    private static final int CLEAR_SCREEN_DELAY = 4;
+    private static final int UPDATE_RECORD_TIME = 5;
+    private static final int ENABLE_SHUTTER_BUTTON = 6;
+    private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
+    private static final int SWITCH_CAMERA = 8;
+    private static final int SWITCH_CAMERA_START_ANIMATION = 9;
+    private static final int HIDE_SURFACE_VIEW = 10;
+
+    private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+    private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
+
+    /**
+     * An unpublished intent flag requesting to start recording straight away
+     * and return as soon as recording is stopped.
+     * TODO: consider publishing by moving into MediaStore.
+     */
+    private static final String EXTRA_QUICK_CAPTURE =
+            "android.intent.extra.quickCapture";
+
+    private static final int MIN_THUMB_SIZE = 64;
+    // module fields
+    private NewCameraActivity mActivity;
+    private boolean mPaused;
+    private int mCameraId;
+    private Parameters mParameters;
+
+    private Boolean mCameraOpened = false;
+
+    private boolean mSnapshotInProgress = false;
+
+    private static final String EFFECT_BG_FROM_GALLERY = "gallery";
+
+    private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
+
+    private ComboPreferences mPreferences;
+    private PreferenceGroup mPreferenceGroup;
+
+    private boolean mIsVideoCaptureIntent;
+    private boolean mQuickCapture;
+
+    private MediaRecorder mMediaRecorder;
+    private EffectsRecorder mEffectsRecorder;
+    private boolean mEffectsDisplayResult;
+
+    private int mEffectType = EffectsRecorder.EFFECT_NONE;
+    private Object mEffectParameter = null;
+    private String mEffectUriFromGallery = null;
+    private String mPrefVideoEffectDefault;
+    private boolean mResetEffect = true;
+
+    private boolean mSwitchingCamera;
+    private boolean mMediaRecorderRecording = false;
+    private long mRecordingStartTime;
+    private boolean mRecordingTimeCountsDown = false;
+    private long mOnResumeTime;
+    // The video file that the hardware camera is about to record into
+    // (or is recording into.)
+    private String mVideoFilename;
+    private ParcelFileDescriptor mVideoFileDescriptor;
+
+    // The video file that has already been recorded, and that is being
+    // examined by the user.
+    private String mCurrentVideoFilename;
+    private Uri mCurrentVideoUri;
+    private ContentValues mCurrentVideoValues;
+
+    private CamcorderProfile mProfile;
+
+    // The video duration limit. 0 menas no limit.
+    private int mMaxVideoDurationInMs;
+
+    // Time Lapse parameters.
+    private boolean mCaptureTimeLapse = false;
+    // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
+    private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
+
+    boolean mPreviewing = false; // True if preview is started.
+    // The display rotation in degrees. This is only valid when mPreviewing is
+    // true.
+    private int mDisplayRotation;
+    private int mCameraDisplayOrientation;
+
+    private int mDesiredPreviewWidth;
+    private int mDesiredPreviewHeight;
+    private ContentResolver mContentResolver;
+
+    private LocationManager mLocationManager;
+    private OrientationManager mOrientationManager;
+
+    private VideoNamer mVideoNamer;
+    private Surface mSurface;
+    private int mPendingSwitchCameraId;
+    private boolean mOpenCameraFail;
+    private boolean mCameraDisabled;
+    private final Handler mHandler = new MainHandler();
+    private NewVideoUI mUI;
+    private CameraProxy mCameraDevice;
+
+    // The degrees of the device rotated clockwise from its natural orientation.
+    private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+
+    private int mZoomValue;  // The current zoom value.
+
+    private boolean mRestoreFlash;  // This is used to check if we need to restore the flash
+                                    // status when going back from gallery.
+
+    private MediaSaveService.OnMediaSavedListener mOnMediaSavedListener =
+            new MediaSaveService.OnMediaSavedListener() {
+                @Override
+                public void onMediaSaved(Uri uri) {
+                    if (uri != null) {
+                        Util.broadcastNewPicture(mActivity, uri);
+                    }
+                }
+            };
+
+
+    protected class CameraOpenThread extends Thread {
+        @Override
+        public void run() {
+            openCamera();
+        }
+    }
+
+    private void openCamera() {
+        try {
+            synchronized(mCameraOpened) {
+                if (!mCameraOpened) {
+                    mCameraDevice = Util.openCamera(mActivity, mCameraId);
+                    mCameraOpened = true;
+                }
+            }
+            mParameters = mCameraDevice.getParameters();
+        } catch (CameraHardwareException e) {
+            mOpenCameraFail = true;
+        } catch (CameraDisabledException e) {
+            mCameraDisabled = true;
+        }
+    }
+
+    // This Handler is used to post message back onto the main thread of the
+    // application
+    private class MainHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+
+                case ENABLE_SHUTTER_BUTTON:
+                    mUI.enableShutter(true);
+                    break;
+
+                case CLEAR_SCREEN_DELAY: {
+                    mActivity.getWindow().clearFlags(
+                            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+                    break;
+                }
+
+                case UPDATE_RECORD_TIME: {
+                    updateRecordingTime();
+                    break;
+                }
+
+                case CHECK_DISPLAY_ROTATION: {
+                    // Restart the preview if display rotation has changed.
+                    // Sometimes this happens when the device is held upside
+                    // down and camera app is opened. Rotation animation will
+                    // take some time and the rotation value we have got may be
+                    // wrong. Framework does not have a callback for this now.
+                    if ((Util.getDisplayRotation(mActivity) != mDisplayRotation)
+                            && !mMediaRecorderRecording && !mSwitchingCamera) {
+                        startPreview();
+                    }
+                    if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
+                        mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+                    }
+                    break;
+                }
+
+                case SHOW_TAP_TO_SNAPSHOT_TOAST: {
+                    showTapToSnapshotToast();
+                    break;
+                }
+
+                case SWITCH_CAMERA: {
+                    switchCamera();
+                    break;
+                }
+
+                case SWITCH_CAMERA_START_ANIMATION: {
+                    //TODO:
+                    //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
+
+                    // Enable all camera controls.
+                    mSwitchingCamera = false;
+                    break;
+                }
+
+                default:
+                    Log.v(TAG, "Unhandled message: " + msg.what);
+                    break;
+            }
+        }
+    }
+
+    private BroadcastReceiver mReceiver = null;
+
+    private class MyBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
+                stopVideoRecording();
+            } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
+                Toast.makeText(mActivity,
+                        mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
+            }
+        }
+    }
+
+    private String createName(long dateTaken) {
+        Date date = new Date(dateTaken);
+        SimpleDateFormat dateFormat = new SimpleDateFormat(
+                mActivity.getString(R.string.video_file_name_format));
+
+        return dateFormat.format(date);
+    }
+
+    private int getPreferredCameraId(ComboPreferences preferences) {
+        int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
+        if (intentCameraId != -1) {
+            // Testing purpose. Launch a specific camera through the intent
+            // extras.
+            return intentCameraId;
+        } else {
+            return CameraSettings.readPreferredCameraId(preferences);
+        }
+    }
+
+    private void initializeSurfaceView() {
+        if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {  // API level < 16
+            mUI.initializeSurfaceView();
+        }
+    }
+
+    @Override
+    public void init(NewCameraActivity activity, View root) {
+        mActivity = activity;
+        mUI = new NewVideoUI(activity, this, root);
+        mPreferences = new ComboPreferences(mActivity);
+        CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
+        mCameraId = getPreferredCameraId(mPreferences);
+
+        mPreferences.setLocalId(mActivity, mCameraId);
+        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+
+        mPrefVideoEffectDefault = mActivity.getString(R.string.pref_video_effect_default);
+        resetEffect();
+        mOrientationManager = new OrientationManager(mActivity);
+
+        /*
+         * To reduce startup time, we start the preview in another thread.
+         * We make sure the preview is started at the end of onCreate.
+         */
+        CameraOpenThread cameraOpenThread = new CameraOpenThread();
+        cameraOpenThread.start();
+
+        mContentResolver = mActivity.getContentResolver();
+
+        // Surface texture is from camera screen nail and startPreview needs it.
+        // This must be done before startPreview.
+        mIsVideoCaptureIntent = isVideoCaptureIntent();
+        initializeSurfaceView();
+
+        // Make sure camera device is opened.
+        try {
+            cameraOpenThread.join();
+            if (mOpenCameraFail) {
+                Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+                return;
+            } else if (mCameraDisabled) {
+                Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+                return;
+            }
+        } catch (InterruptedException ex) {
+            // ignore
+        }
+
+        readVideoPreferences();
+        mUI.setPrefChangedListener(this);
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                startPreview();
+            }
+        }).start();
+
+        mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
+        mLocationManager = new LocationManager(mActivity, null);
+
+        mUI.setOrientationIndicator(0, false);
+        setDisplayOrientation();
+
+        mUI.showTimeLapseUI(mCaptureTimeLapse);
+        initializeVideoSnapshot();
+        resizeForPreviewAspectRatio();
+
+        initializeVideoControl();
+        mPendingSwitchCameraId = -1;
+        mUI.updateOnScreenIndicators(mParameters);
+
+        // Disable the shutter button if effects are ON since it might take
+        // a little more time for the effects preview to be ready. We do not
+        // want to allow recording before that happens. The shutter button
+        // will be enabled when we get the message from effectsrecorder that
+        // the preview is running. This becomes critical when the camera is
+        // swapped.
+        if (effectsActive()) {
+            mUI.enableShutter(false);
+        }
+    }
+
+    // SingleTapListener
+    // Preview area is touched. Take a picture.
+    @Override
+    public void onSingleTapUp(View view, int x, int y) {
+        if (mMediaRecorderRecording && effectsActive()) {
+            new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint,
+                    mOrientation).show();
+            return;
+        }
+
+        MediaSaveService s = mActivity.getMediaSaveService();
+        if (mPaused || mSnapshotInProgress || effectsActive() || s == null || s.isQueueFull()) {
+            return;
+        }
+
+        if (!mMediaRecorderRecording) {
+            // check for dismissing popup
+            mUI.dismissPopup(true);
+            return;
+        }
+
+        // Set rotation and gps data.
+        int rotation = Util.getJpegRotation(mCameraId, mOrientation);
+        mParameters.setRotation(rotation);
+        Location loc = mLocationManager.getCurrentLocation();
+        Util.setGpsParameters(mParameters, loc);
+        mCameraDevice.setParameters(mParameters);
+
+        Log.v(TAG, "Video snapshot start");
+        mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc));
+        showVideoSnapshotUI(true);
+        mSnapshotInProgress = true;
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
+    }
+
+    @Override
+    public void onStop() {}
+
+    private void loadCameraPreferences() {
+        CameraSettings settings = new CameraSettings(mActivity, mParameters,
+                mCameraId, CameraHolder.instance().getCameraInfo());
+        // Remove the video quality preference setting when the quality is given in the intent.
+        mPreferenceGroup = filterPreferenceScreenByIntent(
+                settings.getPreferenceGroup(R.xml.video_preferences));
+    }
+
+    private void initializeVideoControl() {
+        loadCameraPreferences();
+        mUI.initializePopup(mPreferenceGroup);
+        if (effectsActive()) {
+            mUI.overrideSettings(
+                    CameraSettings.KEY_VIDEO_QUALITY,
+                    Integer.toString(getLowVideoQuality()));
+        }
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    private static int getLowVideoQuality() {
+        if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) {
+            return CamcorderProfile.QUALITY_480P;
+        } else {
+            return CamcorderProfile.QUALITY_LOW;
+        }
+    }
+
+
+    @Override
+    public void onOrientationChanged(int orientation) {
+        // We keep the last known orientation. So if the user first orient
+        // the camera then point the camera to floor or sky, we still have
+        // the correct orientation.
+        if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
+        int newOrientation = Util.roundOrientation(orientation, mOrientation);
+
+        if (mOrientation != newOrientation) {
+            mOrientation = newOrientation;
+            // The input of effects recorder is affected by
+            // android.hardware.Camera.setDisplayOrientation. Its value only
+            // compensates the camera orientation (no Display.getRotation).
+            // So the orientation hint here should only consider sensor
+            // orientation.
+            if (effectsActive()) {
+                mEffectsRecorder.setOrientationHint(mOrientation);
+            }
+        }
+
+        // Show the toast after getting the first orientation changed.
+        if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
+            mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
+            showTapToSnapshotToast();
+        }
+    }
+
+    private void startPlayVideoActivity() {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
+        try {
+            mActivity.startActivity(intent);
+        } catch (ActivityNotFoundException ex) {
+            Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
+        }
+    }
+
+    @OnClickAttr
+    public void onReviewPlayClicked(View v) {
+        startPlayVideoActivity();
+    }
+
+    @OnClickAttr
+    public void onReviewDoneClicked(View v) {
+        doReturnToCaller(true);
+    }
+
+    @OnClickAttr
+    public void onReviewCancelClicked(View v) {
+        stopVideoRecording();
+        doReturnToCaller(false);
+    }
+
+    private void onStopVideoRecording() {
+        mEffectsDisplayResult = true;
+        boolean recordFail = stopVideoRecording();
+        if (mIsVideoCaptureIntent) {
+            if (!effectsActive()) {
+                if (mQuickCapture) {
+                    doReturnToCaller(!recordFail);
+                } else if (!recordFail) {
+                    showCaptureResult();
+                }
+            }
+        } else if (!recordFail){
+            // Start capture animation.
+            if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+                // The capture animation is disabled on ICS because we use SurfaceView
+                // for preview during recording. When the recording is done, we switch
+                // back to use SurfaceTexture for preview and we need to stop then start
+                // the preview. This will cause the preview flicker since the preview
+                // will not be continuous for a short period of time.
+                // TODO: need to get the capture animation to work 
+                // ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
+            }
+        }
+    }
+
+    public void onProtectiveCurtainClick(View v) {
+        // Consume clicks
+    }
+
+    @Override
+    public void onShutterButtonClick() {
+        if (mUI.collapseCameraControls() || mSwitchingCamera) return;
+
+        boolean stop = mMediaRecorderRecording;
+
+        if (stop) {
+            onStopVideoRecording();
+        } else {
+            startVideoRecording();
+        }
+        mUI.enableShutter(false);
+
+        // Keep the shutter button disabled when in video capture intent
+        // mode and recording is stopped. It'll be re-enabled when
+        // re-take button is clicked.
+        if (!(mIsVideoCaptureIntent && stop)) {
+            mHandler.sendEmptyMessageDelayed(
+                    ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
+        }
+    }
+
+    @Override
+    public void onShutterButtonFocus(boolean pressed) {
+        mUI.setShutterPressed(pressed);
+    }
+
+    private void readVideoPreferences() {
+        // The preference stores values from ListPreference and is thus string type for all values.
+        // We need to convert it to int manually.
+        String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId,
+                mActivity.getResources().getString(R.string.pref_video_quality_default));
+        String videoQuality =
+                mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
+                        defaultQuality);
+        int quality = Integer.valueOf(videoQuality);
+
+        // Set video quality.
+        Intent intent = mActivity.getIntent();
+        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
+            int extraVideoQuality =
+                    intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
+            if (extraVideoQuality > 0) {
+                quality = CamcorderProfile.QUALITY_HIGH;
+            } else {  // 0 is mms.
+                quality = CamcorderProfile.QUALITY_LOW;
+            }
+        }
+
+        // Set video duration limit. The limit is read from the preference,
+        // unless it is specified in the intent.
+        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
+            int seconds =
+                    intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
+            mMaxVideoDurationInMs = 1000 * seconds;
+        } else {
+            mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
+        }
+
+        // Set effect
+        mEffectType = CameraSettings.readEffectType(mPreferences);
+        if (mEffectType != EffectsRecorder.EFFECT_NONE) {
+            mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
+            // Set quality to be no higher than 480p.
+            CamcorderProfile profile = CamcorderProfile.get(mCameraId, quality);
+            if (profile.videoFrameHeight > 480) {
+                quality = getLowVideoQuality();
+            }
+        } else {
+            mEffectParameter = null;
+        }
+        // Read time lapse recording interval.
+        if (ApiHelper.HAS_TIME_LAPSE_RECORDING) {
+            String frameIntervalStr = mPreferences.getString(
+                    CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
+                    mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default));
+            mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
+            mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
+        }
+        // TODO: This should be checked instead directly +1000.
+        if (mCaptureTimeLapse) quality += 1000;
+        mProfile = CamcorderProfile.get(mCameraId, quality);
+        getDesiredPreviewSize();
+    }
+
+    private void writeDefaultEffectToPrefs()  {
+        ComboPreferences.Editor editor = mPreferences.edit();
+        editor.putString(CameraSettings.KEY_VIDEO_EFFECT,
+                mActivity.getString(R.string.pref_video_effect_default));
+        editor.apply();
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    private void getDesiredPreviewSize() {
+        mParameters = mCameraDevice.getParameters();
+        if (ApiHelper.HAS_GET_SUPPORTED_VIDEO_SIZE) {
+            if (mParameters.getSupportedVideoSizes() == null || effectsActive()) {
+                mDesiredPreviewWidth = mProfile.videoFrameWidth;
+                mDesiredPreviewHeight = mProfile.videoFrameHeight;
+            } else {  // Driver supports separates outputs for preview and video.
+                List<Size> sizes = mParameters.getSupportedPreviewSizes();
+                Size preferred = mParameters.getPreferredPreviewSizeForVideo();
+                int product = preferred.width * preferred.height;
+                Iterator<Size> it = sizes.iterator();
+                // Remove the preview sizes that are not preferred.
+                while (it.hasNext()) {
+                    Size size = it.next();
+                    if (size.width * size.height > product) {
+                        it.remove();
+                    }
+                }
+                Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
+                        (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
+                mDesiredPreviewWidth = optimalSize.width;
+                mDesiredPreviewHeight = optimalSize.height;
+            }
+        } else {
+            mDesiredPreviewWidth = mProfile.videoFrameWidth;
+            mDesiredPreviewHeight = mProfile.videoFrameHeight;
+        }
+        mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
+        Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
+                ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
+    }
+
+    private void resizeForPreviewAspectRatio() {
+        mUI.setAspectRatio(
+                (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
+    }
+
+    @Override
+    public void installIntentFilter() {
+        // install an intent filter to receive SD card related events.
+        IntentFilter intentFilter =
+                new IntentFilter(Intent.ACTION_MEDIA_EJECT);
+        intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        intentFilter.addDataScheme("file");
+        mReceiver = new MyBroadcastReceiver();
+        mActivity.registerReceiver(mReceiver, intentFilter);
+    }
+
+    @Override
+    public void onResumeBeforeSuper() {
+        mPaused = false;
+    }
+
+    @Override
+    public void onResumeAfterSuper() {
+        if (mOpenCameraFail || mCameraDisabled)
+            return;
+        mUI.enableShutter(false);
+        mZoomValue = 0;
+
+        showVideoSnapshotUI(false);
+
+        if (!mPreviewing) {
+            resetEffect();
+            openCamera();
+            if (mOpenCameraFail) {
+                Util.showErrorAndFinish(mActivity,
+                        R.string.cannot_connect_camera);
+                return;
+            } else if (mCameraDisabled) {
+                Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+                return;
+            }
+            readVideoPreferences();
+            resizeForPreviewAspectRatio();
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    startPreview();
+                }
+            }).start();
+        } else {
+            // preview already started
+            mUI.enableShutter(true);
+        }
+
+        // Initializing it here after the preview is started.
+        mUI.initializeZoom(mParameters);
+
+        keepScreenOnAwhile();
+
+        // Initialize location service.
+        boolean recordLocation = RecordLocationPreference.get(mPreferences,
+                mContentResolver);
+        mLocationManager.recordLocation(recordLocation);
+
+        if (mPreviewing) {
+            mOnResumeTime = SystemClock.uptimeMillis();
+            mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+        }
+        // Dismiss open menu if exists.
+        PopupManager.getInstance(mActivity).notifyShowPopup(null);
+
+        mVideoNamer = new VideoNamer();
+        UsageStatistics.onContentViewChanged(
+                UsageStatistics.COMPONENT_CAMERA, "VideoModule");
+    }
+
+    private void setDisplayOrientation() {
+        mDisplayRotation = Util.getDisplayRotation(mActivity);
+        mCameraDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
+        // Change the camera display orientation
+        if (mCameraDevice != null) {
+            mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
+        }
+    }
+
+    @Override
+    public int onZoomChanged(int index) {
+        // Not useful to change zoom value when the activity is paused.
+        if (mPaused) return index;
+        mZoomValue = index;
+        if (mParameters == null || mCameraDevice == null) return index;
+        // Set zoom parameters asynchronously
+        mParameters.setZoom(mZoomValue);
+        mCameraDevice.setParametersAsync(mParameters);
+        Parameters p = mCameraDevice.getParameters();
+        if (p != null) return p.getZoom();
+        return index;
+    }
+    private void startPreview() {
+        Log.v(TAG, "startPreview");
+
+        mCameraDevice.setErrorCallback(mErrorCallback);
+        if (mPreviewing == true) {
+            stopPreview();
+            if (effectsActive() && mEffectsRecorder != null) {
+                mEffectsRecorder.release();
+                mEffectsRecorder = null;
+            }
+        }
+
+        setDisplayOrientation();
+        mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
+        setCameraParameters();
+
+        try {
+            if (!effectsActive()) {
+                SurfaceTexture surfaceTexture = mUI.getSurfaceTexture();
+                if (surfaceTexture == null) {
+                    return; // The texture has been destroyed (pause, etc)
+                }
+                mCameraDevice.setPreviewTextureAsync(surfaceTexture);
+                mCameraDevice.startPreviewAsync();
+                mPreviewing = true;
+                onPreviewStarted();
+            } else {
+                initializeEffectsPreview();
+                mEffectsRecorder.startPreview();
+                mPreviewing = true;
+                onPreviewStarted();
+            }
+        } catch (Throwable ex) {
+            closeCamera();
+            throw new RuntimeException("startPreview failed", ex);
+        } finally {
+            mActivity.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (mOpenCameraFail) {
+                        Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+                    } else if (mCameraDisabled) {
+                        Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+                    }
+                }
+            });
+        }
+
+    }
+
+    private void onPreviewStarted() {
+        mUI.enableShutter(true);
+    }
+
+    @Override
+    public void stopPreview() {
+        if (!mPreviewing) return;
+        mCameraDevice.stopPreview();
+        mPreviewing = false;
+    }
+
+    // Closing the effects out. Will shut down the effects graph.
+    private void closeEffects() {
+        Log.v(TAG, "Closing effects");
+        mEffectType = EffectsRecorder.EFFECT_NONE;
+        if (mEffectsRecorder == null) {
+            Log.d(TAG, "Effects are already closed. Nothing to do");
+            return;
+        }
+        // This call can handle the case where the camera is already released
+        // after the recording has been stopped.
+        mEffectsRecorder.release();
+        mEffectsRecorder = null;
+    }
+
+    // By default, we want to close the effects as well with the camera.
+    private void closeCamera() {
+        closeCamera(true);
+    }
+
+    // In certain cases, when the effects are active, we may want to shutdown
+    // only the camera related parts, and handle closing the effects in the
+    // effectsUpdate callback.
+    // For example, in onPause, we want to make the camera available to
+    // outside world immediately, however, want to wait till the effects
+    // callback to shut down the effects. In such a case, we just disconnect
+    // the effects from the camera by calling disconnectCamera. That way
+    // the effects can handle that when shutting down.
+    //
+    // @param closeEffectsAlso - indicates whether we want to close the
+    // effects also along with the camera.
+    private void closeCamera(boolean closeEffectsAlso) {
+        Log.v(TAG, "closeCamera");
+        if (mCameraDevice == null) {
+            Log.d(TAG, "already stopped.");
+            return;
+        }
+
+        if (mEffectsRecorder != null) {
+            // Disconnect the camera from effects so that camera is ready to
+            // be released to the outside world.
+            mEffectsRecorder.disconnectCamera();
+        }
+        if (closeEffectsAlso) closeEffects();
+        mCameraDevice.setZoomChangeListener(null);
+        mCameraDevice.setErrorCallback(null);
+        synchronized(mCameraOpened) {
+            if (mCameraOpened) {
+                CameraHolder.instance().release();
+            }
+            mCameraOpened = false;
+        }
+        mCameraDevice = null;
+        mPreviewing = false;
+        mSnapshotInProgress = false;
+    }
+
+    private void releasePreviewResources() {
+        if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+            mUI.hideSurfaceView();
+        }
+    }
+
+    @Override
+    public void onPauseBeforeSuper() {
+        mPaused = true;
+
+        if (mMediaRecorderRecording) {
+            // Camera will be released in onStopVideoRecording.
+            onStopVideoRecording();
+        } else {
+            closeCamera();
+            if (!effectsActive()) releaseMediaRecorder();
+        }
+        if (effectsActive()) {
+            // If the effects are active, make sure we tell the graph that the
+            // surfacetexture is not valid anymore. Disconnect the graph from
+            // the display. This should be done before releasing the surface
+            // texture.
+            mEffectsRecorder.disconnectDisplay();
+        } else {
+            // Close the file descriptor and clear the video namer only if the
+            // effects are not active. If effects are active, we need to wait
+            // till we get the callback from the Effects that the graph is done
+            // recording. That also needs a change in the stopVideoRecording()
+            // call to not call closeCamera if the effects are active, because
+            // that will close down the effects are well, thus making this if
+            // condition invalid.
+            closeVideoFileDescriptor();
+            clearVideoNamer();
+        }
+
+        releasePreviewResources();
+
+        if (mReceiver != null) {
+            mActivity.unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
+        resetScreenOn();
+
+        if (mLocationManager != null) mLocationManager.recordLocation(false);
+
+        mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
+        mHandler.removeMessages(SWITCH_CAMERA);
+        mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
+        mPendingSwitchCameraId = -1;
+        mSwitchingCamera = false;
+        // Call onPause after stopping video recording. So the camera can be
+        // released as soon as possible.
+    }
+
+    @Override
+    public void onPauseAfterSuper() {
+    }
+
+    @Override
+    public void onUserInteraction() {
+        if (!mMediaRecorderRecording && !mActivity.isFinishing()) {
+            keepScreenOnAwhile();
+        }
+    }
+
+    @Override
+    public boolean onBackPressed() {
+        if (mPaused) return true;
+        if (mMediaRecorderRecording) {
+            onStopVideoRecording();
+            return true;
+        } else if (mUI.hidePieRenderer()) {
+            return true;
+        } else {
+            return mUI.removeTopLevelPopup();
+        }
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // Do not handle any key if the activity is paused.
+        if (mPaused) {
+            return true;
+        }
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_CAMERA:
+                if (event.getRepeatCount() == 0) {
+                    mUI.clickShutter();
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                if (event.getRepeatCount() == 0) {
+                    mUI.clickShutter();
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_MENU:
+                if (mMediaRecorderRecording) return true;
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_CAMERA:
+                mUI.pressShutter(false);
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    public boolean isVideoCaptureIntent() {
+        String action = mActivity.getIntent().getAction();
+        return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
+    }
+
+    private void doReturnToCaller(boolean valid) {
+        Intent resultIntent = new Intent();
+        int resultCode;
+        if (valid) {
+            resultCode = Activity.RESULT_OK;
+            resultIntent.setData(mCurrentVideoUri);
+        } else {
+            resultCode = Activity.RESULT_CANCELED;
+        }
+        mActivity.setResultEx(resultCode, resultIntent);
+        mActivity.finish();
+    }
+
+    private void cleanupEmptyFile() {
+        if (mVideoFilename != null) {
+            File f = new File(mVideoFilename);
+            if (f.length() == 0 && f.delete()) {
+                Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
+                mVideoFilename = null;
+            }
+        }
+    }
+
+    private void setupMediaRecorderPreviewDisplay() {
+        // Nothing to do here if using SurfaceTexture.
+        if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+            // We stop the preview here before unlocking the device because we
+            // need to change the SurfaceTexture to SurfaceView for preview.
+            stopPreview();
+            mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder());
+            // The orientation for SurfaceTexture is different from that for
+            // SurfaceView. For SurfaceTexture we don't need to consider the
+            // display rotation. Just consider the sensor's orientation and we
+            // will set the orientation correctly when showing the texture.
+            // Gallery will handle the orientation for the preview. For
+            // SurfaceView we will have to take everything into account so the
+            // display rotation is considered.
+            mCameraDevice.setDisplayOrientation(
+                    Util.getDisplayOrientation(mDisplayRotation, mCameraId));
+            mCameraDevice.startPreviewAsync();
+            mPreviewing = true;
+            mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface());
+        }
+    }
+
+    // Prepares media recorder.
+    private void initializeRecorder() {
+        Log.v(TAG, "initializeRecorder");
+        // If the mCameraDevice is null, then this activity is going to finish
+        if (mCameraDevice == null) return;
+
+        if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+            // Set the SurfaceView to visible so the surface gets created.
+            // surfaceCreated() is called immediately when the visibility is
+            // changed to visible. Thus, mSurfaceViewReady should become true
+            // right after calling setVisibility().
+            mUI.showSurfaceView();
+        }
+
+        Intent intent = mActivity.getIntent();
+        Bundle myExtras = intent.getExtras();
+
+        long requestedSizeLimit = 0;
+        closeVideoFileDescriptor();
+        if (mIsVideoCaptureIntent && myExtras != null) {
+            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+            if (saveUri != null) {
+                try {
+                    mVideoFileDescriptor =
+                            mContentResolver.openFileDescriptor(saveUri, "rw");
+                    mCurrentVideoUri = saveUri;
+                } catch (java.io.FileNotFoundException ex) {
+                    // invalid uri
+                    Log.e(TAG, ex.toString());
+                }
+            }
+            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
+        }
+        mMediaRecorder = new MediaRecorder();
+
+        setupMediaRecorderPreviewDisplay();
+        // Unlock the camera object before passing it to media recorder.
+        mCameraDevice.unlock();
+        mCameraDevice.waitDone();
+        mMediaRecorder.setCamera(mCameraDevice.getCamera());
+        if (!mCaptureTimeLapse) {
+            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+        }
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mMediaRecorder.setProfile(mProfile);
+        mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
+        if (mCaptureTimeLapse) {
+            double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
+            setCaptureRate(mMediaRecorder, fps);
+        }
+
+        setRecordLocation();
+
+        // Set output file.
+        // Try Uri in the intent first. If it doesn't exist, use our own
+        // instead.
+        if (mVideoFileDescriptor != null) {
+            mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
+        } else {
+            generateVideoFilename(mProfile.fileFormat);
+            mMediaRecorder.setOutputFile(mVideoFilename);
+        }
+
+        // Set maximum file size.
+        long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
+        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
+            maxFileSize = requestedSizeLimit;
+        }
+
+        try {
+            mMediaRecorder.setMaxFileSize(maxFileSize);
+        } catch (RuntimeException exception) {
+            // We are going to ignore failure of setMaxFileSize here, as
+            // a) The composer selected may simply not support it, or
+            // b) The underlying media framework may not handle 64-bit range
+            // on the size restriction.
+        }
+
+        // See android.hardware.Camera.Parameters.setRotation for
+        // documentation.
+        // Note that mOrientation here is the device orientation, which is the opposite of
+        // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
+        // which is the orientation the graphics need to rotate in order to render correctly.
+        int rotation = 0;
+        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
+            CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+            if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
+                rotation = (info.orientation - mOrientation + 360) % 360;
+            } else {  // back-facing camera
+                rotation = (info.orientation + mOrientation) % 360;
+            }
+        }
+        mMediaRecorder.setOrientationHint(rotation);
+
+        try {
+            mMediaRecorder.prepare();
+        } catch (IOException e) {
+            Log.e(TAG, "prepare failed for " + mVideoFilename, e);
+            releaseMediaRecorder();
+            throw new RuntimeException(e);
+        }
+
+        mMediaRecorder.setOnErrorListener(this);
+        mMediaRecorder.setOnInfoListener(this);
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+    private static void setCaptureRate(MediaRecorder recorder, double fps) {
+        recorder.setCaptureRate(fps);
+    }
+
+    @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+    private void setRecordLocation() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            Location loc = mLocationManager.getCurrentLocation();
+            if (loc != null) {
+                mMediaRecorder.setLocation((float) loc.getLatitude(),
+                        (float) loc.getLongitude());
+            }
+        }
+    }
+
+    private void initializeEffectsPreview() {
+        Log.v(TAG, "initializeEffectsPreview");
+        // If the mCameraDevice is null, then this activity is going to finish
+        if (mCameraDevice == null) return;
+
+        boolean inLandscape = (mActivity.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE);
+
+        CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+
+        mEffectsDisplayResult = false;
+        mEffectsRecorder = new EffectsRecorder(mActivity);
+
+        // TODO: Confirm none of the following need to go to initializeEffectsRecording()
+        // and none of these change even when the preview is not refreshed.
+        mEffectsRecorder.setCameraDisplayOrientation(mCameraDisplayOrientation);
+        mEffectsRecorder.setCamera(mCameraDevice);
+        mEffectsRecorder.setCameraFacing(info.facing);
+        mEffectsRecorder.setProfile(mProfile);
+        mEffectsRecorder.setEffectsListener(this);
+        mEffectsRecorder.setOnInfoListener(this);
+        mEffectsRecorder.setOnErrorListener(this);
+
+        // The input of effects recorder is affected by
+        // android.hardware.Camera.setDisplayOrientation. Its value only
+        // compensates the camera orientation (no Display.getRotation). So the
+        // orientation hint here should only consider sensor orientation.
+        int orientation = 0;
+        if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
+            orientation = mOrientation;
+        }
+        mEffectsRecorder.setOrientationHint(orientation);
+
+        mEffectsRecorder.setPreviewSurfaceTexture(mUI.getSurfaceTexture(),
+            mUI.getPreviewWidth(), mUI.getPreviewHeight());
+
+        if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
+                ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
+            mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery);
+        } else {
+            mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
+        }
+    }
+
+    private void initializeEffectsRecording() {
+        Log.v(TAG, "initializeEffectsRecording");
+
+        Intent intent = mActivity.getIntent();
+        Bundle myExtras = intent.getExtras();
+
+        long requestedSizeLimit = 0;
+        closeVideoFileDescriptor();
+        if (mIsVideoCaptureIntent && myExtras != null) {
+            Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+            if (saveUri != null) {
+                try {
+                    mVideoFileDescriptor =
+                            mContentResolver.openFileDescriptor(saveUri, "rw");
+                    mCurrentVideoUri = saveUri;
+                } catch (java.io.FileNotFoundException ex) {
+                    // invalid uri
+                    Log.e(TAG, ex.toString());
+                }
+            }
+            requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
+        }
+
+        mEffectsRecorder.setProfile(mProfile);
+        // important to set the capture rate to zero if not timelapsed, since the
+        // effectsrecorder object does not get created again for each recording
+        // session
+        if (mCaptureTimeLapse) {
+            mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
+        } else {
+            mEffectsRecorder.setCaptureRate(0);
+        }
+
+        // Set output file
+        if (mVideoFileDescriptor != null) {
+            mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
+        } else {
+            generateVideoFilename(mProfile.fileFormat);
+            mEffectsRecorder.setOutputFile(mVideoFilename);
+        }
+
+        // Set maximum file size.
+        long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
+        if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
+            maxFileSize = requestedSizeLimit;
+        }
+        mEffectsRecorder.setMaxFileSize(maxFileSize);
+        mEffectsRecorder.setMaxDuration(mMaxVideoDurationInMs);
+    }
+
+
+    private void releaseMediaRecorder() {
+        Log.v(TAG, "Releasing media recorder.");
+        if (mMediaRecorder != null) {
+            cleanupEmptyFile();
+            mMediaRecorder.reset();
+            mMediaRecorder.release();
+            mMediaRecorder = null;
+        }
+        mVideoFilename = null;
+    }
+
+    private void releaseEffectsRecorder() {
+        Log.v(TAG, "Releasing effects recorder.");
+        if (mEffectsRecorder != null) {
+            cleanupEmptyFile();
+            mEffectsRecorder.release();
+            mEffectsRecorder = null;
+        }
+        mEffectType = EffectsRecorder.EFFECT_NONE;
+        mVideoFilename = null;
+    }
+
+    private void generateVideoFilename(int outputFileFormat) {
+        long dateTaken = System.currentTimeMillis();
+        String title = createName(dateTaken);
+        // Used when emailing.
+        String filename = title + convertOutputFormatToFileExt(outputFileFormat);
+        String mime = convertOutputFormatToMimeType(outputFileFormat);
+        String path = Storage.DIRECTORY + '/' + filename;
+        String tmpPath = path + ".tmp";
+        mCurrentVideoValues = new ContentValues(7);
+        mCurrentVideoValues.put(Video.Media.TITLE, title);
+        mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
+        mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
+        mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
+        mCurrentVideoValues.put(Video.Media.DATA, path);
+        mCurrentVideoValues.put(Video.Media.RESOLUTION,
+                Integer.toString(mProfile.videoFrameWidth) + "x" +
+                Integer.toString(mProfile.videoFrameHeight));
+        Location loc = mLocationManager.getCurrentLocation();
+        if (loc != null) {
+            mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
+            mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
+        }
+        mVideoNamer.prepareUri(mContentResolver, mCurrentVideoValues);
+        mVideoFilename = tmpPath;
+        Log.v(TAG, "New video filename: " + mVideoFilename);
+    }
+
+    private boolean addVideoToMediaStore() {
+        boolean fail = false;
+        if (mVideoFileDescriptor == null) {
+            mCurrentVideoValues.put(Video.Media.SIZE,
+                    new File(mCurrentVideoFilename).length());
+            long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
+            if (duration > 0) {
+                if (mCaptureTimeLapse) {
+                    duration = getTimeLapseVideoLength(duration);
+                }
+                mCurrentVideoValues.put(Video.Media.DURATION, duration);
+            } else {
+                Log.w(TAG, "Video duration <= 0 : " + duration);
+            }
+            try {
+                mCurrentVideoUri = mVideoNamer.getUri();
+                //TODO: mActivity.addSecureAlbumItemIfNeeded(true, mCurrentVideoUri);
+
+                // Rename the video file to the final name. This avoids other
+                // apps reading incomplete data.  We need to do it after the
+                // above mVideoNamer.getUri() call, so we are certain that the
+                // previous insert to MediaProvider is completed.
+                String finalName = mCurrentVideoValues.getAsString(
+                        Video.Media.DATA);
+                if (new File(mCurrentVideoFilename).renameTo(new File(finalName))) {
+                    mCurrentVideoFilename = finalName;
+                }
+
+                mContentResolver.update(mCurrentVideoUri, mCurrentVideoValues
+                        , null, null);
+                mActivity.sendBroadcast(new Intent(Util.ACTION_NEW_VIDEO,
+                        mCurrentVideoUri));
+            } catch (Exception e) {
+                // We failed to insert into the database. This can happen if
+                // the SD card is unmounted.
+                Log.e(TAG, "failed to add video to media store", e);
+                mCurrentVideoUri = null;
+                mCurrentVideoFilename = null;
+                fail = true;
+            } finally {
+                Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
+            }
+        }
+        mCurrentVideoValues = null;
+        return fail;
+    }
+
+    private void deleteCurrentVideo() {
+        // Remove the video and the uri if the uri is not passed in by intent.
+        if (mCurrentVideoFilename != null) {
+            deleteVideoFile(mCurrentVideoFilename);
+            mCurrentVideoFilename = null;
+            if (mCurrentVideoUri != null) {
+                mContentResolver.delete(mCurrentVideoUri, null, null);
+                mCurrentVideoUri = null;
+            }
+        }
+        mActivity.updateStorageSpaceAndHint();
+    }
+
+    private void deleteVideoFile(String fileName) {
+        Log.v(TAG, "Deleting video " + fileName);
+        File f = new File(fileName);
+        if (!f.delete()) {
+            Log.v(TAG, "Could not delete " + fileName);
+        }
+    }
+
+    private PreferenceGroup filterPreferenceScreenByIntent(
+            PreferenceGroup screen) {
+        Intent intent = mActivity.getIntent();
+        if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
+            CameraSettings.removePreferenceFromScreen(screen,
+                    CameraSettings.KEY_VIDEO_QUALITY);
+        }
+
+        if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
+            CameraSettings.removePreferenceFromScreen(screen,
+                    CameraSettings.KEY_VIDEO_QUALITY);
+        }
+        return screen;
+    }
+
+    // from MediaRecorder.OnErrorListener
+    @Override
+    public void onError(MediaRecorder mr, int what, int extra) {
+        Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
+        if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
+            // We may have run out of space on the sdcard.
+            stopVideoRecording();
+            mActivity.updateStorageSpaceAndHint();
+        }
+    }
+
+    // from MediaRecorder.OnInfoListener
+    @Override
+    public void onInfo(MediaRecorder mr, int what, int extra) {
+        if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
+            if (mMediaRecorderRecording) onStopVideoRecording();
+        } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
+            if (mMediaRecorderRecording) onStopVideoRecording();
+
+            // Show the toast.
+            Toast.makeText(mActivity, R.string.video_reach_size_limit,
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    /*
+     * Make sure we're not recording music playing in the background, ask the
+     * MediaPlaybackService to pause playback.
+     */
+    private void pauseAudioPlayback() {
+        // Shamelessly copied from MediaPlaybackService.java, which
+        // should be public, but isn't.
+        Intent i = new Intent("com.android.music.musicservicecommand");
+        i.putExtra("command", "pause");
+
+        mActivity.sendBroadcast(i);
+    }
+
+    // For testing.
+    public boolean isRecording() {
+        return mMediaRecorderRecording;
+    }
+
+    private void startVideoRecording() {
+        Log.v(TAG, "startVideoRecording");
+        // TODO: mActivity.setSwipingEnabled(false);
+
+        mActivity.updateStorageSpaceAndHint();
+        if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
+            Log.v(TAG, "Storage issue, ignore the start request");
+            return;
+        }
+
+        mCurrentVideoUri = null;
+        if (effectsActive()) {
+            initializeEffectsRecording();
+            if (mEffectsRecorder == null) {
+                Log.e(TAG, "Fail to initialize effect recorder");
+                return;
+            }
+        } else {
+            initializeRecorder();
+            if (mMediaRecorder == null) {
+                Log.e(TAG, "Fail to initialize media recorder");
+                return;
+            }
+        }
+
+        pauseAudioPlayback();
+
+        if (effectsActive()) {
+            try {
+                mEffectsRecorder.startRecording();
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Could not start effects recorder. ", e);
+                releaseEffectsRecorder();
+                return;
+            }
+        } else {
+            try {
+                mMediaRecorder.start(); // Recording is now started
+            } catch (RuntimeException e) {
+                Log.e(TAG, "Could not start media recorder. ", e);
+                releaseMediaRecorder();
+                // If start fails, frameworks will not lock the camera for us.
+                mCameraDevice.lock();
+                return;
+            }
+        }
+
+        // Make sure the video recording has started before announcing
+        // this in accessibility.
+        AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
+                mActivity.getString(R.string.video_recording_started));
+
+        // The parameters might have been altered by MediaRecorder already.
+        // We need to force mCameraDevice to refresh before getting it.
+        mCameraDevice.refreshParameters();
+        // The parameters may have been changed by MediaRecorder upon starting
+        // recording. We need to alter the parameters if we support camcorder
+        // zoom. To reduce latency when setting the parameters during zoom, we
+        // update mParameters here once.
+        if (ApiHelper.HAS_ZOOM_WHEN_RECORDING) {
+            mParameters = mCameraDevice.getParameters();
+        }
+
+        mUI.enableCameraControls(false);
+
+        mMediaRecorderRecording = true;
+        mOrientationManager.lockOrientation();
+        mRecordingStartTime = SystemClock.uptimeMillis();
+        mUI.showRecordingUI(true, mParameters.isZoomSupported());
+
+        updateRecordingTime();
+        keepScreenOn();
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                UsageStatistics.ACTION_CAPTURE_START, "Video");
+    }
+
+    private void showCaptureResult() {
+        Bitmap bitmap = null;
+        if (mVideoFileDescriptor != null) {
+            bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
+                    mDesiredPreviewWidth);
+        } else if (mCurrentVideoFilename != null) {
+            bitmap = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename,
+                    mDesiredPreviewWidth);
+        }
+        if (bitmap != null) {
+            // MetadataRetriever already rotates the thumbnail. We should rotate
+            // it to match the UI orientation (and mirror if it is front-facing camera).
+            CameraInfo[] info = CameraHolder.instance().getCameraInfo();
+            boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
+            bitmap = Util.rotateAndMirror(bitmap, 0, mirror);
+            mUI.showReviewImage(bitmap);
+        }
+
+        mUI.showReviewControls();
+        mUI.enableCameraControls(false);
+        mUI.showTimeLapseUI(false);
+    }
+
+    private void hideAlert() {
+        mUI.enableCameraControls(true);
+        mUI.hideReviewUI();
+        if (mCaptureTimeLapse) {
+            mUI.showTimeLapseUI(true);
+        }
+    }
+
+    private boolean stopVideoRecording() {
+        Log.v(TAG, "stopVideoRecording");
+        //TODO: mUI.setSwipingEnabled(true);
+        mUI.showSwitcher();
+
+        boolean fail = false;
+        if (mMediaRecorderRecording) {
+            boolean shouldAddToMediaStoreNow = false;
+
+            try {
+                if (effectsActive()) {
+                    // This is asynchronous, so we can't add to media store now because thumbnail
+                    // may not be ready. In such case addVideoToMediaStore is called later
+                    // through a callback from the MediaEncoderFilter to EffectsRecorder,
+                    // and then to the VideoModule.
+                    mEffectsRecorder.stopRecording();
+                } else {
+                    mMediaRecorder.setOnErrorListener(null);
+                    mMediaRecorder.setOnInfoListener(null);
+                    mMediaRecorder.stop();
+                    shouldAddToMediaStoreNow = true;
+                }
+                mCurrentVideoFilename = mVideoFilename;
+                Log.v(TAG, "stopVideoRecording: Setting current video filename: "
+                        + mCurrentVideoFilename);
+                AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
+                        mActivity.getString(R.string.video_recording_stopped));
+            } catch (RuntimeException e) {
+                Log.e(TAG, "stop fail",  e);
+                if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
+                fail = true;
+            }
+            mMediaRecorderRecording = false;
+            mOrientationManager.unlockOrientation();
+
+            // If the activity is paused, this means activity is interrupted
+            // during recording. Release the camera as soon as possible because
+            // face unlock or other applications may need to use the camera.
+            // However, if the effects are active, then we can only release the
+            // camera and cannot release the effects recorder since that will
+            // stop the graph. It is possible to separate out the Camera release
+            // part and the effects release part. However, the effects recorder
+            // does hold on to the camera, hence, it needs to be "disconnected"
+            // from the camera in the closeCamera call.
+            if (mPaused) {
+                // Closing only the camera part if effects active. Effects will
+                // be closed in the callback from effects.
+                boolean closeEffects = !effectsActive();
+                closeCamera(closeEffects);
+            }
+
+            mUI.showRecordingUI(false, mParameters.isZoomSupported());
+            if (!mIsVideoCaptureIntent) {
+                mUI.enableCameraControls(true);
+            }
+            // The orientation was fixed during video recording. Now make it
+            // reflect the device orientation as video recording is stopped.
+            mUI.setOrientationIndicator(0, true);
+            keepScreenOnAwhile();
+            if (shouldAddToMediaStoreNow) {
+                if (addVideoToMediaStore()) fail = true;
+            }
+        }
+        // always release media recorder if no effects running
+        if (!effectsActive()) {
+            releaseMediaRecorder();
+            if (!mPaused) {
+                mCameraDevice.lock();
+                mCameraDevice.waitDone();
+                if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+                    stopPreview();
+                    mUI.hideSurfaceView();
+                    // Switch back to use SurfaceTexture for preview.
+                    startPreview();
+                }
+            }
+        }
+        // Update the parameters here because the parameters might have been altered
+        // by MediaRecorder.
+        if (!mPaused) mParameters = mCameraDevice.getParameters();
+        UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+                fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
+                    UsageStatistics.ACTION_CAPTURE_DONE, "Video",
+                    SystemClock.uptimeMillis() - mRecordingStartTime);
+        return fail;
+    }
+
+    private void resetScreenOn() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    private void keepScreenOnAwhile() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+    }
+
+    private void keepScreenOn() {
+        mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+        mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+
+    private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
+        long seconds = milliSeconds / 1000; // round down to compute seconds
+        long minutes = seconds / 60;
+        long hours = minutes / 60;
+        long remainderMinutes = minutes - (hours * 60);
+        long remainderSeconds = seconds - (minutes * 60);
+
+        StringBuilder timeStringBuilder = new StringBuilder();
+
+        // Hours
+        if (hours > 0) {
+            if (hours < 10) {
+                timeStringBuilder.append('0');
+            }
+            timeStringBuilder.append(hours);
+
+            timeStringBuilder.append(':');
+        }
+
+        // Minutes
+        if (remainderMinutes < 10) {
+            timeStringBuilder.append('0');
+        }
+        timeStringBuilder.append(remainderMinutes);
+        timeStringBuilder.append(':');
+
+        // Seconds
+        if (remainderSeconds < 10) {
+            timeStringBuilder.append('0');
+        }
+        timeStringBuilder.append(remainderSeconds);
+
+        // Centi seconds
+        if (displayCentiSeconds) {
+            timeStringBuilder.append('.');
+            long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
+            if (remainderCentiSeconds < 10) {
+                timeStringBuilder.append('0');
+            }
+            timeStringBuilder.append(remainderCentiSeconds);
+        }
+
+        return timeStringBuilder.toString();
+    }
+
+    private long getTimeLapseVideoLength(long deltaMs) {
+        // For better approximation calculate fractional number of frames captured.
+        // This will update the video time at a higher resolution.
+        double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
+        return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
+    }
+
+    private void updateRecordingTime() {
+        if (!mMediaRecorderRecording) {
+            return;
+        }
+        long now = SystemClock.uptimeMillis();
+        long delta = now - mRecordingStartTime;
+
+        // Starting a minute before reaching the max duration
+        // limit, we'll countdown the remaining time instead.
+        boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
+                && delta >= mMaxVideoDurationInMs - 60000);
+
+        long deltaAdjusted = delta;
+        if (countdownRemainingTime) {
+            deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
+        }
+        String text;
+
+        long targetNextUpdateDelay;
+        if (!mCaptureTimeLapse) {
+            text = millisecondToTimeString(deltaAdjusted, false);
+            targetNextUpdateDelay = 1000;
+        } else {
+            // The length of time lapse video is different from the length
+            // of the actual wall clock time elapsed. Display the video length
+            // only in format hh:mm:ss.dd, where dd are the centi seconds.
+            text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
+            targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
+        }
+
+        mUI.setRecordingTime(text);
+
+        if (mRecordingTimeCountsDown != countdownRemainingTime) {
+            // Avoid setting the color on every update, do it only
+            // when it needs changing.
+            mRecordingTimeCountsDown = countdownRemainingTime;
+
+            int color = mActivity.getResources().getColor(countdownRemainingTime
+                    ? R.color.recording_time_remaining_text
+                    : R.color.recording_time_elapsed_text);
+
+            mUI.setRecordingTimeTextColor(color);
+        }
+
+        long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
+        mHandler.sendEmptyMessageDelayed(
+                UPDATE_RECORD_TIME, actualNextUpdateDelay);
+    }
+
+    private static boolean isSupported(String value, List<String> supported) {
+        return supported == null ? false : supported.indexOf(value) >= 0;
+    }
+
+    @SuppressWarnings("deprecation")
+    private void setCameraParameters() {
+        mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
+        mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
+
+        // Set flash mode.
+        String flashMode;
+        if (mUI.isVisible()) {
+            flashMode = mPreferences.getString(
+                    CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
+                    mActivity.getString(R.string.pref_camera_video_flashmode_default));
+        } else {
+            flashMode = Parameters.FLASH_MODE_OFF;
+        }
+        List<String> supportedFlash = mParameters.getSupportedFlashModes();
+        if (isSupported(flashMode, supportedFlash)) {
+            mParameters.setFlashMode(flashMode);
+        } else {
+            flashMode = mParameters.getFlashMode();
+            if (flashMode == null) {
+                flashMode = mActivity.getString(
+                        R.string.pref_camera_flashmode_no_flash);
+            }
+        }
+
+        // Set white balance parameter.
+        String whiteBalance = mPreferences.getString(
+                CameraSettings.KEY_WHITE_BALANCE,
+                mActivity.getString(R.string.pref_camera_whitebalance_default));
+        if (isSupported(whiteBalance,
+                mParameters.getSupportedWhiteBalance())) {
+            mParameters.setWhiteBalance(whiteBalance);
+        } else {
+            whiteBalance = mParameters.getWhiteBalance();
+            if (whiteBalance == null) {
+                whiteBalance = Parameters.WHITE_BALANCE_AUTO;
+            }
+        }
+
+        // Set zoom.
+        if (mParameters.isZoomSupported()) {
+            mParameters.setZoom(mZoomValue);
+        }
+
+        // Set continuous autofocus.
+        List<String> supportedFocus = mParameters.getSupportedFocusModes();
+        if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
+            mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+        }
+
+        mParameters.set(Util.RECORDING_HINT, Util.TRUE);
+
+        // Enable video stabilization. Convenience methods not available in API
+        // level <= 14
+        String vstabSupported = mParameters.get("video-stabilization-supported");
+        if ("true".equals(vstabSupported)) {
+            mParameters.set("video-stabilization", "true");
+        }
+
+        // Set picture size.
+        // The logic here is different from the logic in still-mode camera.
+        // There we determine the preview size based on the picture size, but
+        // here we determine the picture size based on the preview size.
+        List<Size> supported = mParameters.getSupportedPictureSizes();
+        Size optimalSize = Util.getOptimalVideoSnapshotPictureSize(supported,
+                (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
+        Size original = mParameters.getPictureSize();
+        if (!original.equals(optimalSize)) {
+            mParameters.setPictureSize(optimalSize.width, optimalSize.height);
+        }
+        Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
+                optimalSize.height);
+
+        // Set JPEG quality.
+        int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
+                CameraProfile.QUALITY_HIGH);
+        mParameters.setJpegQuality(jpegQuality);
+
+        mCameraDevice.setParameters(mParameters);
+        // Keep preview size up to date.
+        mParameters = mCameraDevice.getParameters();
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case REQUEST_EFFECT_BACKDROPPER:
+                if (resultCode == Activity.RESULT_OK) {
+                    // onActivityResult() runs before onResume(), so this parameter will be
+                    // seen by startPreview from onResume()
+                    mEffectUriFromGallery = data.getData().toString();
+                    Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery);
+                    mResetEffect = false;
+                } else {
+                    mEffectUriFromGallery = null;
+                    Log.w(TAG, "No URI from gallery");
+                    mResetEffect = true;
+                }
+                break;
+        }
+    }
+
+    @Override
+    public void onEffectsUpdate(int effectId, int effectMsg) {
+        Log.v(TAG, "onEffectsUpdate. Effect Message = " + effectMsg);
+        if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) {
+            // Effects have shut down. Hide learning message if any,
+            // and restart regular preview.
+            checkQualityAndStartPreview();
+        } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) {
+            // This follows the codepath from onStopVideoRecording.
+            if (mEffectsDisplayResult && !addVideoToMediaStore()) {
+                if (mIsVideoCaptureIntent) {
+                    if (mQuickCapture) {
+                        doReturnToCaller(true);
+                    } else {
+                        showCaptureResult();
+                    }
+                }
+            }
+            mEffectsDisplayResult = false;
+            // In onPause, these were not called if the effects were active. We
+            // had to wait till the effects recording is complete to do this.
+            if (mPaused) {
+                closeVideoFileDescriptor();
+                clearVideoNamer();
+            }
+        } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) {
+            // Enable the shutter button once the preview is complete.
+            mUI.enableShutter(true);
+        }
+        // In onPause, this was not called if the effects were active. We had to
+        // wait till the effects completed to do this.
+        if (mPaused) {
+            Log.v(TAG, "OnEffectsUpdate: closing effects if activity paused");
+            closeEffects();
+        }
+    }
+
+    public void onCancelBgTraining(View v) {
+        // Write default effect out to shared prefs
+        writeDefaultEffectToPrefs();
+        // Tell VideoCamer to re-init based on new shared pref values.
+        onSharedPreferenceChanged();
+    }
+
+    @Override
+    public synchronized void onEffectsError(Exception exception, String fileName) {
+        // TODO: Eventually we may want to show the user an error dialog, and then restart the
+        // camera and encoder gracefully. For now, we just delete the file and bail out.
+        if (fileName != null && new File(fileName).exists()) {
+            deleteVideoFile(fileName);
+        }
+        try {
+            if (Class.forName("android.filterpacks.videosink.MediaRecorderStopException")
+                    .isInstance(exception)) {
+                Log.w(TAG, "Problem recoding video file. Removing incomplete file.");
+                return;
+            }
+        } catch (ClassNotFoundException ex) {
+            Log.w(TAG, ex);
+        }
+        throw new RuntimeException("Error during recording!", exception);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        Log.v(TAG, "onConfigurationChanged");
+        setDisplayOrientation();
+    }
+
+    @Override
+    public void onOverriddenPreferencesClicked() {
+    }
+
+    @Override
+    // TODO: Delete this after old camera code is removed
+    public void onRestorePreferencesClicked() {
+    }
+
+    private boolean effectsActive() {
+        return (mEffectType != EffectsRecorder.EFFECT_NONE);
+    }
+
+    @Override
+    public void onSharedPreferenceChanged() {
+        // ignore the events after "onPause()" or preview has not started yet
+        if (mPaused) return;
+        synchronized (mPreferences) {
+            // If mCameraDevice is not ready then we can set the parameter in
+            // startPreview().
+            if (mCameraDevice == null) return;
+
+            boolean recordLocation = RecordLocationPreference.get(
+                    mPreferences, mContentResolver);
+            mLocationManager.recordLocation(recordLocation);
+
+            // Check if the current effects selection has changed
+            if (updateEffectSelection()) return;
+
+            readVideoPreferences();
+            mUI.showTimeLapseUI(mCaptureTimeLapse);
+            // We need to restart the preview if preview size is changed.
+            Size size = mParameters.getPreviewSize();
+            if (size.width != mDesiredPreviewWidth
+                    || size.height != mDesiredPreviewHeight) {
+                if (!effectsActive()) {
+                    stopPreview();
+                } else {
+                    mEffectsRecorder.release();
+                    mEffectsRecorder = null;
+                }
+                resizeForPreviewAspectRatio();
+                startPreview(); // Parameters will be set in startPreview().
+            } else {
+                setCameraParameters();
+            }
+            mUI.updateOnScreenIndicators(mParameters);
+        }
+    }
+
+    protected void setCameraId(int cameraId) {
+        ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+        pref.setValue("" + cameraId);
+    }
+
+    private void switchCamera() {
+        if (mPaused) return;
+
+        Log.d(TAG, "Start to switch camera.");
+        mCameraId = mPendingSwitchCameraId;
+        mPendingSwitchCameraId = -1;
+        setCameraId(mCameraId);
+
+        closeCamera();
+        mUI.collapseCameraControls();
+        // Restart the camera and initialize the UI. From onCreate.
+        mPreferences.setLocalId(mActivity, mCameraId);
+        CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+        openCamera();
+        readVideoPreferences();
+        startPreview();
+        initializeVideoSnapshot();
+        resizeForPreviewAspectRatio();
+        initializeVideoControl();
+
+        // From onResume
+        mUI.initializeZoom(mParameters);
+        mUI.setOrientationIndicator(0, false);
+
+        // Start switch camera animation. Post a message because
+        // onFrameAvailable from the old camera may already exist.
+        mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
+        mUI.updateOnScreenIndicators(mParameters);
+    }
+
+    // Preview texture has been copied. Now camera can be released and the
+    // animation can be started.
+    @Override
+    public void onPreviewTextureCopied() {
+        mHandler.sendEmptyMessage(SWITCH_CAMERA);
+    }
+
+    @Override
+    public void onCaptureTextureCopied() {
+    }
+
+    private boolean updateEffectSelection() {
+        int previousEffectType = mEffectType;
+        Object previousEffectParameter = mEffectParameter;
+        mEffectType = CameraSettings.readEffectType(mPreferences);
+        mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
+
+        if (mEffectType == previousEffectType) {
+            if (mEffectType == EffectsRecorder.EFFECT_NONE) return false;
+            if (mEffectParameter.equals(previousEffectParameter)) return false;
+        }
+        Log.v(TAG, "New effect selection: " + mPreferences.getString(
+                CameraSettings.KEY_VIDEO_EFFECT, "none"));
+
+        if (mEffectType == EffectsRecorder.EFFECT_NONE) {
+            // Stop effects and return to normal preview
+            mEffectsRecorder.stopPreview();
+            mPreviewing = false;
+            return true;
+        }
+        if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
+            ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
+            // Request video from gallery to use for background
+            Intent i = new Intent(Intent.ACTION_PICK);
+            i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI,
+                             "video/*");
+            i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+            mActivity.startActivityForResult(i, REQUEST_EFFECT_BACKDROPPER);
+            return true;
+        }
+        if (previousEffectType == EffectsRecorder.EFFECT_NONE) {
+            // Stop regular preview and start effects.
+            stopPreview();
+            checkQualityAndStartPreview();
+        } else {
+            // Switch currently running effect
+            mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
+        }
+        return true;
+    }
+
+    // Verifies that the current preview view size is correct before starting
+    // preview. If not, resets the surface texture and resizes the view.
+    private void checkQualityAndStartPreview() {
+        readVideoPreferences();
+        mUI.showTimeLapseUI(mCaptureTimeLapse);
+        Size size = mParameters.getPreviewSize();
+        if (size.width != mDesiredPreviewWidth
+                || size.height != mDesiredPreviewHeight) {
+            resizeForPreviewAspectRatio();
+        }
+        // Start up preview again
+        startPreview();
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (mSwitchingCamera) return true;
+        return mUI.dispatchTouchEvent(m);
+    }
+
+    private void initializeVideoSnapshot() {
+        if (mParameters == null) return;
+        if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
+            // Show the tap to focus toast if this is the first start.
+            if (mPreferences.getBoolean(
+                        CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
+                // Delay the toast for one second to wait for orientation.
+                mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
+            }
+        }
+    }
+
+    void showVideoSnapshotUI(boolean enabled) {
+        if (mParameters == null) return;
+        if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
+            if (enabled) {
+             // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
+            } else {
+                mUI.showPreviewBorder(enabled);
+            }
+            mUI.enableShutter(!enabled);
+        }
+    }
+
+    @Override
+    public void updateCameraAppView() {
+        if (!mPreviewing || mParameters.getFlashMode() == null) return;
+
+        // When going to and back from gallery, we need to turn off/on the flash.
+        if (!mUI.isVisible()) {
+            if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) {
+                mRestoreFlash = false;
+                return;
+            }
+            mRestoreFlash = true;
+            setCameraParameters();
+        } else if (mRestoreFlash) {
+            mRestoreFlash = false;
+            setCameraParameters();
+        }
+    }
+
+    @Override
+    public void onFullScreenChanged(boolean full) {
+        mUI.onFullScreenChanged(full);
+    }
+
+    private final class JpegPictureCallback implements PictureCallback {
+        Location mLocation;
+
+        public JpegPictureCallback(Location loc) {
+            mLocation = loc;
+        }
+
+        @Override
+        public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) {
+            Log.v(TAG, "onPictureTaken");
+            mSnapshotInProgress = false;
+            showVideoSnapshotUI(false);
+            storeImage(jpegData, mLocation);
+        }
+    }
+
+    private void storeImage(final byte[] data, Location loc) {
+        long dateTaken = System.currentTimeMillis();
+        String title = Util.createJpegName(dateTaken);
+        ExifInterface exif = Exif.getExif(data);
+        int orientation = Exif.getOrientation(exif);
+        Size s = mParameters.getPictureSize();
+        mActivity.getMediaSaveService().addImage(
+                data, title, dateTaken, loc, s.width, s.height, orientation,
+                exif, mOnMediaSavedListener, mContentResolver);
+    }
+
+    private boolean resetEffect() {
+        if (mResetEffect) {
+            String value = mPreferences.getString(CameraSettings.KEY_VIDEO_EFFECT,
+                    mPrefVideoEffectDefault);
+            if (!mPrefVideoEffectDefault.equals(value)) {
+                writeDefaultEffectToPrefs();
+                return true;
+            }
+        }
+        mResetEffect = true;
+        return false;
+    }
+
+    private String convertOutputFormatToMimeType(int outputFileFormat) {
+        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
+            return "video/mp4";
+        }
+        return "video/3gpp";
+    }
+
+    private String convertOutputFormatToFileExt(int outputFileFormat) {
+        if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
+            return ".mp4";
+        }
+        return ".3gp";
+    }
+
+    private void closeVideoFileDescriptor() {
+        if (mVideoFileDescriptor != null) {
+            try {
+                mVideoFileDescriptor.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Fail to close fd", e);
+            }
+            mVideoFileDescriptor = null;
+        }
+    }
+
+    private void showTapToSnapshotToast() {
+        new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
+                .show();
+        // Clear the preference.
+        Editor editor = mPreferences.edit();
+        editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
+        editor.apply();
+    }
+
+    private void clearVideoNamer() {
+        if (mVideoNamer != null) {
+            mVideoNamer.finish();
+            mVideoNamer = null;
+        }
+    }
+
+    private static class VideoNamer extends Thread {
+        private boolean mRequestPending;
+        private ContentResolver mResolver;
+        private ContentValues mValues;
+        private boolean mStop;
+        private Uri mUri;
+
+        // Runs in main thread
+        public VideoNamer() {
+            start();
+        }
+
+        // Runs in main thread
+        public synchronized void prepareUri(
+                ContentResolver resolver, ContentValues values) {
+            mRequestPending = true;
+            mResolver = resolver;
+            mValues = new ContentValues(values);
+            notifyAll();
+        }
+
+        // Runs in main thread
+        public synchronized Uri getUri() {
+            // wait until the request is done.
+            while (mRequestPending) {
+                try {
+                    wait();
+                } catch (InterruptedException ex) {
+                    // ignore.
+                }
+            }
+            Uri uri = mUri;
+            mUri = null;
+            return uri;
+        }
+
+        // Runs in namer thread
+        @Override
+        public synchronized void run() {
+            while (true) {
+                if (mStop) break;
+                if (!mRequestPending) {
+                    try {
+                        wait();
+                    } catch (InterruptedException ex) {
+                        // ignore.
+                    }
+                    continue;
+                }
+                cleanOldUri();
+                generateUri();
+                mRequestPending = false;
+                notifyAll();
+            }
+            cleanOldUri();
+        }
+
+        // Runs in main thread
+        public synchronized void finish() {
+            mStop = true;
+            notifyAll();
+        }
+
+        // Runs in namer thread
+        private void generateUri() {
+            Uri videoTable = Uri.parse("content://media/external/video/media");
+            mUri = mResolver.insert(videoTable, mValues);
+        }
+
+        // Runs in namer thread
+        private void cleanOldUri() {
+            if (mUri == null) return;
+            mResolver.delete(mUri, null, null);
+            mUri = null;
+        }
+    }
+
+    @Override
+    public boolean updateStorageHintOnResume() {
+        return true;
+    }
+
+    // required by OnPreferenceChangedListener
+    @Override
+    public void onCameraPickerClicked(int cameraId) {
+        if (mPaused || mPendingSwitchCameraId != -1) return;
+
+        mPendingSwitchCameraId = cameraId;
+        Log.d(TAG, "Start to copy texture.");
+        // We need to keep a preview frame for the animation before
+        // releasing the camera. This will trigger onPreviewTextureCopied.
+        // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
+        // Disable all camera controls.
+        mSwitchingCamera = true;
+
+    }
+
+    @Override
+    public boolean needsSwitcher() {
+        return !mIsVideoCaptureIntent;
+    }
+
+    @Override
+    public boolean needsPieMenu() {
+        return true;
+    }
+
+    @Override
+    public void onShowSwitcherPopup() {
+        mUI.onShowSwitcherPopup();
+    }
+
+    @Override
+    public void onMediaSaveServiceConnected(MediaSaveService s) {
+        // do nothing.
+    }
+}
diff --git a/src/com/android/camera/NewVideoUI.java b/src/com/android/camera/NewVideoUI.java
new file mode 100644
index 0000000..6dada60
--- /dev/null
+++ b/src/com/android/camera/NewVideoUI.java
@@ -0,0 +1,716 @@
+/*
+ * 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.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.camera.CameraPreference.OnPreferenceChangedListener;
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.CameraSwitcher;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.RenderOverlay;
+import com.android.camera.ui.RotateLayout;
+import com.android.camera.ui.ZoomRenderer;
+import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.util.List;
+
+public class NewVideoUI implements PieRenderer.PieListener,
+        NewPreviewGestures.SingleTapListener,
+        NewPreviewGestures.SwipeListener, SurfaceTextureListener,
+        SurfaceHolder.Callback {
+    private final static String TAG = "CAM_VideoUI";
+    private static final int UPDATE_TRANSFORM_MATRIX = 1;
+    // module fields
+    private NewCameraActivity mActivity;
+    private View mRootView;
+    private TextureView mTextureView;
+    // An review image having same size as preview. It is displayed when
+    // recording is stopped in capture intent.
+    private ImageView mReviewImage;
+    private View mReviewCancelButton;
+    private View mReviewDoneButton;
+    private View mReviewPlayButton;
+    private ShutterButton mShutterButton;
+    private CameraSwitcher mSwitcher;
+    private TextView mRecordingTimeView;
+    private LinearLayout mLabelsLinearLayout;
+    private View mTimeLapseLabel;
+    private RenderOverlay mRenderOverlay;
+    private PieRenderer mPieRenderer;
+    private NewVideoMenu mVideoMenu;
+    private View mCameraControls;
+    private AbstractSettingPopup mPopup;
+    private ZoomRenderer mZoomRenderer;
+    private NewPreviewGestures mGestures;
+    private View mMenuButton;
+    private View mBlocker;
+    private View mOnScreenIndicators;
+    private ImageView mFlashIndicator;
+    private RotateLayout mRecordingTimeRect;
+    private final Object mLock = new Object();
+    private SurfaceTexture mSurfaceTexture;
+    private VideoController mController;
+    private int mZoomMax;
+    private List<Integer> mZoomRatios;
+
+    private SurfaceView mSurfaceView = null;
+    private int mPreviewWidth = 0;
+    private int mPreviewHeight = 0;
+    private float mSurfaceTextureUncroppedWidth;
+    private float mSurfaceTextureUncroppedHeight;
+    private float mAspectRatio = 4f / 3f;
+    private Matrix mMatrix = null;
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case UPDATE_TRANSFORM_MATRIX:
+                    setTransformMatrix(mPreviewWidth, mPreviewHeight);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+    private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
+        @Override
+        public void onLayoutChange(View v, int left, int top, int right,
+                int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+            int width = right - left;
+            int height = bottom - top;
+            // Full-screen screennail
+            int w = width;
+            int h = height;
+            if (Util.getDisplayRotation(mActivity) % 180 != 0) {
+                w = height;
+                h = width;
+            }
+            if (mPreviewWidth != width || mPreviewHeight != height) {
+                mPreviewWidth = width;
+                mPreviewHeight = height;
+                onScreenSizeChanged(width, height, w, h);
+            }
+        }
+    };
+
+    public NewVideoUI(NewCameraActivity activity, VideoController controller, View parent) {
+        mActivity = activity;
+        mController = controller;
+        mRootView = parent;
+        mActivity.getLayoutInflater().inflate(R.layout.new_video_module, (ViewGroup) mRootView, true);
+        mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);
+        mTextureView.setSurfaceTextureListener(this);
+        mTextureView.addOnLayoutChangeListener(mLayoutListener);
+        mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
+        mSwitcher = (CameraSwitcher) mRootView.findViewById(R.id.camera_switcher);
+        mSwitcher.setCurrentIndex(1);
+        mSwitcher.setSwitchListener((CameraSwitchListener) mActivity);
+        initializeMiscControls();
+        initializeControlByIntent();
+        initializeOverlay();
+    }
+
+
+    public void initializeSurfaceView() {
+        mSurfaceView = new SurfaceView(mActivity);
+        ((ViewGroup) mRootView).addView(mSurfaceView, 0);
+        mSurfaceView.getHolder().addCallback(this);
+    }
+
+    private void initializeControlByIntent() {
+        mBlocker = mActivity.findViewById(R.id.blocker);
+        mMenuButton = mActivity.findViewById(R.id.menu);
+        mMenuButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mPieRenderer != null) {
+                    mPieRenderer.showInCenter();
+                }
+            }
+        });
+
+        mCameraControls = mActivity.findViewById(R.id.camera_controls);
+        mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators);
+        mFlashIndicator = (ImageView) mActivity.findViewById(R.id.menu_flash_indicator);
+        if (mController.isVideoCaptureIntent()) {
+            hideSwitcher();
+            mActivity.getLayoutInflater().inflate(R.layout.review_module_control, (ViewGroup) mCameraControls);
+            // Cannot use RotateImageView for "done" and "cancel" button because
+            // the tablet layout uses RotateLayout, which cannot be cast to
+            // RotateImageView.
+            mReviewDoneButton = mActivity.findViewById(R.id.btn_done);
+            mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel);
+            mReviewPlayButton = mActivity.findViewById(R.id.btn_play);
+            mReviewCancelButton.setVisibility(View.VISIBLE);
+            mReviewDoneButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onReviewDoneClicked(v);
+                }
+            });
+            mReviewCancelButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onReviewCancelClicked(v);
+                }
+            });
+            mReviewPlayButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mController.onReviewPlayClicked(v);
+                }
+            });
+        }
+    }
+
+    public void setPreviewSize(int width, int height) {
+        if (width == 0 || height == 0) {
+            Log.w(TAG, "Preview size should not be 0.");
+            return;
+        }
+        if (width > height) {
+            mAspectRatio = (float) width / height;
+        } else {
+            mAspectRatio = (float) height / width;
+        }
+        mHandler.sendEmptyMessage(UPDATE_TRANSFORM_MATRIX);
+    }
+
+    public int getPreviewWidth() {
+        return mPreviewWidth;
+    }
+
+    public int getPreviewHeight() {
+        return mPreviewHeight;
+    }
+
+    public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) {
+        setTransformMatrix(width, height);
+    }
+
+    private void setTransformMatrix(int width, int height) {
+        mMatrix = mTextureView.getTransform(mMatrix);
+        int orientation = Util.getDisplayRotation(mActivity);
+        float scaleX = 1f, scaleY = 1f;
+        float scaledTextureWidth, scaledTextureHeight;
+        if (width > height) {
+            scaledTextureWidth = Math.max(width,
+                    (int) (height * mAspectRatio));
+            scaledTextureHeight = Math.max(height,
+                    (int)(width / mAspectRatio));
+        } else {
+            scaledTextureWidth = Math.max(width,
+                    (int) (height / mAspectRatio));
+            scaledTextureHeight = Math.max(height,
+                    (int) (width * mAspectRatio));
+        }
+
+        if (mSurfaceTextureUncroppedWidth != scaledTextureWidth ||
+                mSurfaceTextureUncroppedHeight != scaledTextureHeight) {
+            mSurfaceTextureUncroppedWidth = scaledTextureWidth;
+            mSurfaceTextureUncroppedHeight = scaledTextureHeight;
+        }
+        scaleX = scaledTextureWidth / width;
+        scaleY = scaledTextureHeight / height;
+        mMatrix.setScale(scaleX, scaleY, (float) width / 2, (float) height / 2);
+        mTextureView.setTransform(mMatrix);
+    }
+
+    public void hideUI() {
+        mCameraControls.setVisibility(View.INVISIBLE);
+        hideSwitcher();
+        mShutterButton.setVisibility(View.GONE);
+    }
+
+    public void showUI() {
+        mCameraControls.setVisibility(View.VISIBLE);
+        showSwitcher();
+        mShutterButton.setVisibility(View.VISIBLE);
+    }
+
+    public void hideSwitcher() {
+        mSwitcher.closePopup();
+        mSwitcher.setVisibility(View.INVISIBLE);
+    }
+
+    public void showSwitcher() {
+        mSwitcher.setVisibility(View.VISIBLE);
+    }
+
+    public boolean collapseCameraControls() {
+        boolean ret = false;
+        if (mPopup != null) {
+            dismissPopup(false);
+            ret = true;
+        }
+        return ret;
+    }
+
+    public boolean removeTopLevelPopup() {
+        if (mPopup != null) {
+            dismissPopup(true);
+            return true;
+        }
+        return false;
+    }
+
+    public void enableCameraControls(boolean enable) {
+        if (mGestures != null) {
+            mGestures.setZoomOnly(!enable);
+        }
+        if (mPieRenderer != null && mPieRenderer.showsItems()) {
+            mPieRenderer.hide();
+        }
+    }
+
+    public void overrideSettings(final String... keyvalues) {
+        mVideoMenu.overrideSettings(keyvalues);
+    }
+
+    public void setOrientationIndicator(int orientation, boolean animation) {
+        if (mGestures != null) {
+            mGestures.setOrientation(orientation);
+        }
+        // We change the orientation of the linearlayout only for phone UI
+        // because when in portrait the width is not enough.
+        if (mLabelsLinearLayout != null) {
+            if (((orientation / 90) & 1) == 0) {
+                mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL);
+            } else {
+                mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL);
+            }
+        }
+        mRecordingTimeRect.setOrientation(0, animation);
+    }
+
+    public SurfaceHolder getSurfaceHolder() {
+        return mSurfaceView.getHolder();
+    }
+
+    public void hideSurfaceView() {
+        mSurfaceView.setVisibility(View.GONE);
+        mTextureView.setVisibility(View.VISIBLE);
+        setTransformMatrix(mPreviewWidth, mPreviewHeight);
+    }
+
+    public void showSurfaceView() {
+        // TODO: Need to transform the surface view
+        mSurfaceView.setVisibility(View.VISIBLE);
+        mTextureView.setVisibility(View.GONE);
+    }
+
+    private void initializeOverlay() {
+        mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
+        if (mPieRenderer == null) {
+            mPieRenderer = new PieRenderer(mActivity);
+            mVideoMenu = new NewVideoMenu(mActivity, this, mPieRenderer);
+            mPieRenderer.setPieListener(this);
+        }
+        mRenderOverlay.addRenderer(mPieRenderer);
+        if (mZoomRenderer == null) {
+            mZoomRenderer = new ZoomRenderer(mActivity);
+        }
+        mRenderOverlay.addRenderer(mZoomRenderer);
+        if (mGestures == null) {
+            mGestures = new NewPreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, this);
+        }
+        mGestures.setRenderOverlay(mRenderOverlay);
+        mGestures.clearTouchReceivers();
+        mGestures.addTouchReceiver(mMenuButton);
+        mGestures.addTouchReceiver(mBlocker);
+        if (mController.isVideoCaptureIntent()) {
+            if (mReviewCancelButton != null) {
+                mGestures.addTouchReceiver(mReviewCancelButton);
+            }
+            if (mReviewDoneButton != null) {
+                mGestures.addTouchReceiver(mReviewDoneButton);
+            }
+            if (mReviewPlayButton != null) {
+                mGestures.addTouchReceiver(mReviewPlayButton);
+            }
+        }
+    }
+
+    public void setPrefChangedListener(OnPreferenceChangedListener listener) {
+        mVideoMenu.setListener(listener);
+    }
+
+    private void initializeMiscControls() {
+        mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image);
+        mShutterButton.setImageResource(R.drawable.btn_new_shutter_video);
+        mShutterButton.setOnShutterButtonListener(mController);
+        mShutterButton.setVisibility(View.VISIBLE);
+        mShutterButton.requestFocus();
+        mShutterButton.enableTouch(true);
+        mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time);
+        mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect);
+        mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label);
+        // The R.id.labels can only be found in phone layout.
+        // That is, mLabelsLinearLayout should be null in tablet layout.
+        mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels);
+    }
+
+    public void updateOnScreenIndicators(Parameters param) {
+        if (param == null) return;
+        String value = param.getFlashMode();
+        if (mFlashIndicator == null) return;
+        if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) {
+            mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
+        } else {
+            if (Parameters.FLASH_MODE_AUTO.equals(value)) {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto);
+            } else if (Parameters.FLASH_MODE_ON.equals(value)
+                    || Parameters.FLASH_MODE_TORCH.equals(value)) {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on);
+            } else {
+                mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off);
+            }
+        }
+    }
+
+    public void setAspectRatio(double ratio) {
+      //  mPreviewFrameLayout.setAspectRatio(ratio);
+    }
+
+    public void showTimeLapseUI(boolean enable) {
+        if (mTimeLapseLabel != null) {
+            mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    private void openMenu() {
+        if (mPieRenderer != null) {
+            mPieRenderer.showInCenter();
+        }
+    }
+
+    public void showPopup(AbstractSettingPopup popup) {
+        hideUI();
+        mBlocker.setVisibility(View.INVISIBLE);
+        setShowMenu(false);
+        mPopup = popup;
+        mPopup.setVisibility(View.VISIBLE);
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT,
+                LayoutParams.WRAP_CONTENT);
+        lp.gravity = Gravity.CENTER;
+        ((FrameLayout) mRootView).addView(mPopup, lp);
+    }
+
+    public void dismissPopup(boolean topLevelOnly) {
+        dismissPopup(topLevelOnly, true);
+    }
+
+    public void dismissPopup(boolean topLevelPopupOnly, boolean fullScreen) {
+        if (fullScreen) {
+            showUI();
+            mBlocker.setVisibility(View.VISIBLE);
+        }
+        setShowMenu(fullScreen);
+        if (mPopup != null) {
+            ((FrameLayout) mRootView).removeView(mPopup);
+            mPopup = null;
+        }
+        mVideoMenu.popupDismissed(topLevelPopupOnly);
+    }
+
+    public void onShowSwitcherPopup() {
+        hidePieRenderer();
+    }
+
+    public boolean hidePieRenderer() {
+        if (mPieRenderer != null && mPieRenderer.showsItems()) {
+            mPieRenderer.hide();
+            return true;
+        }
+        return false;
+    }
+
+    // disable preview gestures after shutter is pressed
+    public void setShutterPressed(boolean pressed) {
+        if (mGestures == null) return;
+        mGestures.setEnabled(!pressed);
+    }
+
+    public void enableShutter(boolean enable) {
+        if (mShutterButton != null) {
+            mShutterButton.setEnabled(enable);
+        }
+    }
+
+    // PieListener
+    @Override
+    public void onPieOpened(int centerX, int centerY) {
+        // TODO: mActivity.cancelActivityTouchHandling();
+        // mActivity.setSwipingEnabled(false);
+    }
+
+    @Override
+    public void onPieClosed() {
+        // TODO: mActivity.setSwipingEnabled(true);
+    }
+
+    public void showPreviewBorder(boolean enable) {
+       // TODO: mPreviewFrameLayout.showBorder(enable);
+    }
+
+    // SingleTapListener
+    // Preview area is touched. Take a picture.
+    @Override
+    public void onSingleTapUp(View view, int x, int y) {
+        mController.onSingleTapUp(view, x, y);
+    }
+
+    // SurfaceView callback
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        Log.v(TAG, "Surface changed. width=" + width + ". height=" + height);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        Log.v(TAG, "Surface created");
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        Log.v(TAG, "Surface destroyed");
+        mController.stopPreview();
+    }
+
+    public void showRecordingUI(boolean recording, boolean zoomSupported) {
+        mMenuButton.setVisibility(recording ? View.GONE : View.VISIBLE);
+        mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE);
+        if (recording) {
+            mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording);
+            hideSwitcher();
+            mRecordingTimeView.setText("");
+            mRecordingTimeView.setVisibility(View.VISIBLE);
+            // The camera is not allowed to be accessed in older api levels during
+            // recording. It is therefore necessary to hide the zoom UI on older
+            // platforms.
+            // See the documentation of android.media.MediaRecorder.start() for
+            // further explanation.
+            if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) {
+                // TODO: disable zoom UI here.
+            }
+        } else {
+            mShutterButton.setImageResource(R.drawable.btn_new_shutter_video);
+            showSwitcher();
+            mRecordingTimeView.setVisibility(View.GONE);
+            if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) {
+                // TODO: enable zoom UI here.
+            }
+        }
+    }
+
+    public void showReviewImage(Bitmap bitmap) {
+        mReviewImage.setImageBitmap(bitmap);
+        mReviewImage.setVisibility(View.VISIBLE);
+    }
+
+    public void showReviewControls() {
+        Util.fadeOut(mShutterButton);
+        Util.fadeIn(mReviewDoneButton);
+        Util.fadeIn(mReviewPlayButton);
+        mReviewImage.setVisibility(View.VISIBLE);
+        mMenuButton.setVisibility(View.GONE);
+        mOnScreenIndicators.setVisibility(View.GONE);
+    }
+
+    public void hideReviewUI() {
+        mReviewImage.setVisibility(View.GONE);
+        mShutterButton.setEnabled(true);
+        mMenuButton.setVisibility(View.VISIBLE);
+        mOnScreenIndicators.setVisibility(View.VISIBLE);
+        Util.fadeOut(mReviewDoneButton);
+        Util.fadeOut(mReviewPlayButton);
+        Util.fadeIn(mShutterButton);
+    }
+
+    private void setShowMenu(boolean show) {
+        if (mOnScreenIndicators != null) {
+            mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+        if (mMenuButton != null) {
+            mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    public void onFullScreenChanged(boolean full) {
+        if (mGestures != null) {
+            mGestures.setEnabled(full);
+        }
+        if (mPopup != null) {
+            dismissPopup(false, full);
+        }
+        if (mRenderOverlay != null) {
+            // this can not happen in capture mode
+            mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
+        }
+        setShowMenu(full);
+        if (mBlocker != null) {
+            // this can not happen in capture mode
+            mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    public void initializePopup(PreferenceGroup pref) {
+        mVideoMenu.initialize(pref);
+    }
+
+    public void initializeZoom(Parameters param) {
+        if (param == null || !param.isZoomSupported()) return;
+        mZoomMax = param.getMaxZoom();
+        mZoomRatios = param.getZoomRatios();
+        // Currently we use immediate zoom for fast zooming to get better UX and
+        // there is no plan to take advantage of the smooth zoom.
+        mZoomRenderer.setZoomMax(mZoomMax);
+        mZoomRenderer.setZoom(param.getZoom());
+        mZoomRenderer.setZoomValue(mZoomRatios.get(param.getZoom()));
+        mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
+    }
+
+    public void clickShutter() {
+        mShutterButton.performClick();
+    }
+
+    public void pressShutter(boolean pressed) {
+        mShutterButton.setPressed(pressed);
+    }
+
+    public View getShutterButton() {
+        return mShutterButton;
+    }
+
+    // Gestures and touch events
+
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        if (mPopup != null || mSwitcher.showsPopup()) {
+            boolean handled = mRootView.dispatchTouchEvent(m);
+            if (!handled && mPopup != null) {
+                dismissPopup(false);
+            }
+            return handled;
+        } else if (mGestures != null && mRenderOverlay != null) {
+            if (mGestures.dispatchTouch(m)) {
+                return true;
+            } else {
+                return mRootView.dispatchTouchEvent(m);
+            }
+        }
+        return true;
+    }
+    public void setRecordingTime(String text) {
+        mRecordingTimeView.setText(text);
+    }
+
+    public void setRecordingTimeTextColor(int color) {
+        mRecordingTimeView.setTextColor(color);
+    }
+
+    public boolean isVisible() {
+        return mTextureView.getVisibility() == View.VISIBLE;
+    }
+
+    private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
+        @Override
+        public void onZoomValueChanged(int index) {
+            int newZoom = mController.onZoomChanged(index);
+            if (mZoomRenderer != null) {
+                mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
+            }
+        }
+
+        @Override
+        public void onZoomStart() {
+        }
+
+        @Override
+        public void onZoomEnd() {
+        }
+    }
+
+    @Override
+    public void onSwipe(int direction) {
+        if (direction == PreviewGestures.DIR_UP) {
+            openMenu();
+        }
+    }
+
+    public SurfaceTexture getSurfaceTexture() {
+        synchronized (mLock) {
+            if (mSurfaceTexture == null) {
+                try {
+                    mLock.wait();
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "Unexpected interruption when waiting to get surface texture");
+                }
+            }
+        }
+        return mSurfaceTexture;
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+        synchronized (mLock) {
+            mSurfaceTexture = surface;
+            mLock.notifyAll();
+        }
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        mSurfaceTexture = null;
+        mController.stopPreview();
+        Log.d(TAG, "surfaceTexture is destroyed");
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+    }
+
+}
diff --git a/src/com/android/camera/PhotoMenu.java b/src/com/android/camera/PhotoMenu.java
index f9400fc..f5ba733 100644
--- a/src/com/android/camera/PhotoMenu.java
+++ b/src/com/android/camera/PhotoMenu.java
@@ -51,11 +51,13 @@
     private MoreSettingPopup mPopup;
     // Second level popup
     private AbstractSettingPopup mSecondPopup;
+    private CameraActivity mActivity;
 
     public PhotoMenu(CameraActivity activity, PhotoUI ui, PieRenderer pie) {
         super(activity, pie);
         mUI = ui;
         mSettingOff = activity.getString(R.string.setting_off_value);
+        mActivity = activity;
     }
 
     public void initialize(PreferenceGroup group) {
diff --git a/src/com/android/camera/PieController.java b/src/com/android/camera/PieController.java
index c5d1b8b..a9b8575 100644
--- a/src/com/android/camera/PieController.java
+++ b/src/com/android/camera/PieController.java
@@ -16,6 +16,7 @@
 
 package com.android.camera;
 
+import android.app.Activity;
 import android.graphics.drawable.Drawable;
 import android.util.Log;
 
@@ -37,7 +38,10 @@
     protected static final int MODE_PHOTO = 0;
     protected static final int MODE_VIDEO = 1;
 
-    protected CameraActivity mActivity;
+    protected static float CENTER = (float) Math.PI / 2;
+    protected static final float SWEEP = 0.06f;
+
+    protected Activity mActivity;
     protected PreferenceGroup mPreferenceGroup;
     protected OnPreferenceChangedListener mListener;
     protected PieRenderer mRenderer;
@@ -49,7 +53,7 @@
         mListener = listener;
     }
 
-    public PieController(CameraActivity activity, PieRenderer pie) {
+    public PieController(Activity activity, PieRenderer pie) {
         mActivity = activity;
         mRenderer = pie;
         mPreferences = new ArrayList<IconListPreference>();
diff --git a/src/com/android/camera/VideoMenu.java b/src/com/android/camera/VideoMenu.java
index d9629d3..c9f2932 100644
--- a/src/com/android/camera/VideoMenu.java
+++ b/src/com/android/camera/VideoMenu.java
@@ -47,10 +47,12 @@
     private static final int POPUP_FIRST_LEVEL = 1;
     private static final int POPUP_SECOND_LEVEL = 2;
     private int mPopupStatus;
+    private CameraActivity mActivity;
 
     public VideoMenu(CameraActivity activity, VideoUI ui, PieRenderer pie) {
         super(activity, pie);
         mUI = ui;
+        mActivity = activity;
     }
 
     public void initialize(PreferenceGroup group) {
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 09a406c..2b30ba4 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -1532,6 +1532,9 @@
         AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(),
                 mActivity.getString(R.string.video_recording_started));
 
+        // The parameters might have been altered by MediaRecorder already.
+        // We need to force mCameraDevice to refresh before getting it.
+        mActivity.mCameraDevice.refreshParameters();
         // The parameters may have been changed by MediaRecorder upon starting
         // recording. We need to alter the parameters if we support camcorder
         // zoom. To reduce latency when setting the parameters during zoom, we
diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java
index bb615e9..43822e7 100644
--- a/src/com/android/camera/VideoUI.java
+++ b/src/com/android/camera/VideoUI.java
@@ -52,7 +52,6 @@
     private View mRootView;
     private PreviewFrameLayout mPreviewFrameLayout;
     private boolean mSurfaceViewReady;
-    private SurfaceHolder.Callback mSurfaceViewCallback;
     private PreviewSurfaceView mPreviewSurfaceView;
     // An review image having same size as preview. It is displayed when
     // recording is stopped in capture intent.
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
index 5d10953..7a75a6d 100644
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -33,6 +33,7 @@
 
 import com.android.camera.Storage;
 import com.android.camera.ui.FilmStripView;
+import com.android.camera.ui.FilmStripView.ImageData;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -79,7 +80,7 @@
 
     private List<LocalImageData> mImages;
 
-    private FilmStripView mFilmStripView;
+    private Listener mListener;
     private View mCameraPreviewView;
     private ColorDrawable mPlaceHolder;
 
@@ -101,7 +102,7 @@
     }
 
     @Override
-    public FilmStripView.ImageData getImageData(int id) {
+    public ImageData getImageData(int id) {
         if (id >= mImages.size()) return null;
         return mImages.get(id);
     }
@@ -142,8 +143,8 @@
     }
 
     @Override
-    public void setDataListener(FilmStripView v) {
-        mFilmStripView = v;
+    public void setListener(Listener listener) {
+        mListener = listener;
     }
 
     private LocalImageData buildCameraImageData(int width, int height, int orientation) {
@@ -152,6 +153,7 @@
         d.height = height;
         d.orientation = orientation;
         d.isCameraData = true;
+        d.supportedAction = ImageData.ACTION_NONE;
         return d;
     }
 
@@ -179,6 +181,7 @@
         d.orientation = c.getInt(COL_ORIENTATION);
         d.width = c.getInt(COL_WIDTH);
         d.height = c.getInt(COL_HEIGHT);
+        d.supportedAction = ImageData.ACTION_PROMOTE | ImageData.ACTION_DEMOTE;
         if (d.width <= 0 || d.height <= 0) {
             Log.v(TAG, "warning! zero dimension for "
                     + d.path + ":" + d.width + "x" + d.height);
@@ -229,6 +232,7 @@
         // width and height should be adjusted according to orientation.
         public int width;
         public int height;
+        public int supportedAction;
 
         @Override
         public int getWidth() {
@@ -241,6 +245,17 @@
         }
 
         @Override
+        public int getType() {
+            if (isCameraData) return ImageData.TYPE_CAMERA_PREVIEW;
+            return ImageData.TYPE_PHOTO;
+        }
+
+        @Override
+        public boolean isActionSupported(int action) {
+            return ((action & supportedAction) != 0);
+        }
+
+        @Override
         public String toString() {
             return "LocalImageData:" + ",data=" + path + ",mimeType=" + mimeType
                     + "," + width + "x" + height + ",orientation=" + orientation;
@@ -281,7 +296,7 @@
             mImages = l;
             if (first != null) addOrReplaceCameraData(first);
             // both might be null.
-            if (changed) mFilmStripView.onDataChanged();
+            if (changed && mListener != null) mListener.onDataLoaded();
         }
     }
 
diff --git a/src/com/android/camera/ui/CameraSwitcher.java b/src/com/android/camera/ui/CameraSwitcher.java
index b046ff7..fee28a3 100644
--- a/src/com/android/camera/ui/CameraSwitcher.java
+++ b/src/com/android/camera/ui/CameraSwitcher.java
@@ -38,6 +38,7 @@
 import com.android.camera.Util;
 import com.android.gallery3d.R;
 import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.util.LightCycleHelper;
 import com.android.gallery3d.util.UsageStatistics;
 
 public class CameraSwitcher extends RotateImageView
@@ -46,6 +47,16 @@
     private static final String TAG = "CAM_Switcher";
     private static final int SWITCHER_POPUP_ANIM_DURATION = 200;
 
+    public static final int PHOTO_MODULE_INDEX = 0;
+    public static final int VIDEO_MODULE_INDEX = 1;
+    public static final int PANORAMA_MODULE_INDEX = 2;
+    public static final int LIGHTCYCLE_MODULE_INDEX = 3;
+    private static final int[] DRAW_IDS = {
+            R.drawable.ic_switch_camera,
+            R.drawable.ic_switch_video,
+            R.drawable.ic_switch_pan,
+            R.drawable.ic_switch_photosphere
+    };
     public interface CameraSwitchListener {
         public void onCameraSelected(int i);
         public void onShowSwitcherPopup();
@@ -82,6 +93,28 @@
         mItemSize = context.getResources().getDimensionPixelSize(R.dimen.switcher_size);
         setOnClickListener(this);
         mIndicator = context.getResources().getDrawable(R.drawable.ic_switcher_menu_indicator);
+        initializeDrawables(context);
+    }
+
+    public void initializeDrawables(Context context) {
+        int totaldrawid = (LightCycleHelper.hasLightCycleCapture(context)
+                ? DRAW_IDS.length : DRAW_IDS.length - 1);
+        if (!ApiHelper.HAS_OLD_PANORAMA) totaldrawid--;
+
+        int[] drawids = new int[totaldrawid];
+        int[] moduleids = new int[totaldrawid];
+        int ix = 0;
+        for (int i = 0; i < DRAW_IDS.length; i++) {
+            if (i == PANORAMA_MODULE_INDEX && !ApiHelper.HAS_OLD_PANORAMA) {
+            continue; // not enabled, so don't add to UI
+            }
+            if (i == LIGHTCYCLE_MODULE_INDEX && !LightCycleHelper.hasLightCycleCapture(context)) {
+            continue; // not enabled, so don't add to UI
+            }
+            moduleids[ix] = i;
+            drawids[ix++] = DRAW_IDS[i];
+        }
+        setIds(moduleids, drawids);
     }
 
     public void setIds(int[] moduleids, int[] drawids) {
diff --git a/src/com/android/camera/ui/FaceView.java b/src/com/android/camera/ui/FaceView.java
index 099f3bf..9840b15 100644
--- a/src/com/android/camera/ui/FaceView.java
+++ b/src/com/android/camera/ui/FaceView.java
@@ -33,12 +33,15 @@
 
 import com.android.camera.CameraActivity;
 import com.android.camera.CameraScreenNail;
+import com.android.camera.NewPhotoUI;
 import com.android.camera.Util;
 import com.android.gallery3d.R;
 import com.android.gallery3d.common.ApiHelper;
 
 @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
-public class FaceView extends View implements FocusIndicator, Rotatable {
+public class FaceView extends View
+    implements FocusIndicator, Rotatable,
+    NewPhotoUI.SurfaceTextureSizeChangedListener {
     private static final String TAG = "CAM FaceView";
     private final boolean LOGV = false;
     // The value for android.hardware.Camera.setDisplayOrientation.
@@ -63,6 +66,8 @@
     private Paint mPaint;
     private volatile boolean mBlocked;
 
+    private int mUncroppedWidth;
+    private int mUncroppedHeight;
     private static final int MSG_SWITCH_FACES = 1;
     private static final int SWITCH_DELAY = 70;
     private boolean mStateSwitchPending = false;
@@ -92,6 +97,11 @@
         mPaint.setStrokeWidth(res.getDimension(R.dimen.face_circle_stroke));
     }
 
+    public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight) {
+        mUncroppedWidth = uncroppedWidth;
+        mUncroppedHeight = uncroppedHeight;
+    }
+
     public void setFaces(Face[] faces) {
         if (LOGV) Log.v(TAG, "Num of faces=" + faces.length);
         if (mPause) return;
@@ -178,9 +188,17 @@
     @Override
     protected void onDraw(Canvas canvas) {
         if (!mBlocked && (mFaces != null) && (mFaces.length > 0)) {
-            final CameraScreenNail sn = ((CameraActivity) getContext()).getCameraScreenNail();
-            int rw = sn.getUncroppedRenderWidth();
-            int rh = sn.getUncroppedRenderHeight();
+            int rw, rh;
+            if (mUncroppedWidth == 0) {
+                // TODO: This check is temporary. It needs to be removed after the
+                // refactoring is fully functioning.
+                final CameraScreenNail sn = ((CameraActivity) getContext()).getCameraScreenNail();
+                rw = sn.getUncroppedRenderWidth();
+                rh = sn.getUncroppedRenderHeight();
+            } else {
+                rw = mUncroppedWidth;
+                rh = mUncroppedHeight;
+            }
             // Prepare the matrix.
             if (((rh > rw) && ((mDisplayOrientation == 0) || (mDisplayOrientation == 180)))
                     || ((rw > rh) && ((mDisplayOrientation == 90) || (mDisplayOrientation == 270)))) {
diff --git a/src/com/android/camera/ui/FilmStripGestureRecognizer.java b/src/com/android/camera/ui/FilmStripGestureRecognizer.java
new file mode 100644
index 0000000..f0e2534
--- /dev/null
+++ b/src/com/android/camera/ui/FilmStripGestureRecognizer.java
@@ -0,0 +1,107 @@
+/*
+ * 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.content.Context;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+// This class aggregates three gesture detectors: GestureDetector,
+// ScaleGestureDetector.
+public class FilmStripGestureRecognizer {
+    @SuppressWarnings("unused")
+    private static final String TAG = "FilmStripGestureRecognizer";
+
+    public interface Listener {
+        boolean onSingleTapUp(float x, float y);
+        boolean onDoubleTap(float x, float y);
+        boolean onScroll(float x, float y, float dx, float dy);
+        boolean onFling(float velocityX, float velocityY);
+        boolean onScaleBegin(float focusX, float focusY);
+        boolean onScale(float focusX, float focusY, float scale);
+        boolean onDown(float x, float y);
+        void onScaleEnd();
+    }
+
+    private final GestureDetector mGestureDetector;
+    private final ScaleGestureDetector mScaleDetector;
+    private final Listener mListener;
+
+    public FilmStripGestureRecognizer(Context context, Listener listener) {
+        mListener = listener;
+        mGestureDetector = new GestureDetector(context, new MyGestureListener(),
+                null, true /* ignoreMultitouch */);
+        mScaleDetector = new ScaleGestureDetector(
+                context, new MyScaleListener());
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        return mGestureDetector.onTouchEvent(event) || mScaleDetector.onTouchEvent(event);
+    }
+
+    private class MyGestureListener
+                extends GestureDetector.SimpleOnGestureListener {
+        @Override
+        public boolean onSingleTapUp(MotionEvent e) {
+            return mListener.onSingleTapUp(e.getX(), e.getY());
+        }
+
+        @Override
+        public boolean onDoubleTap(MotionEvent e) {
+            return mListener.onDoubleTap(e.getX(), e.getY());
+        }
+
+        @Override
+        public boolean onScroll(
+                MotionEvent e1, MotionEvent e2, float dx, float dy) {
+            return mListener.onScroll(e2.getX(), e2.getY(), dx, dy);
+        }
+
+        @Override
+        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+                float velocityY) {
+            return mListener.onFling(velocityX, velocityY);
+        }
+
+        @Override
+        public boolean onDown(MotionEvent e) {
+            mListener.onDown(e.getX(), e.getY());
+            return super.onDown(e);
+        }
+    }
+
+    private class MyScaleListener
+            extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+        @Override
+        public boolean onScaleBegin(ScaleGestureDetector detector) {
+            return mListener.onScaleBegin(
+                    detector.getFocusX(), detector.getFocusY());
+        }
+
+        @Override
+        public boolean onScale(ScaleGestureDetector detector) {
+            return mListener.onScale(detector.getFocusX(),
+                    detector.getFocusY(), detector.getScaleFactor());
+        }
+
+        @Override
+        public void onScaleEnd(ScaleGestureDetector detector) {
+            mListener.onScaleEnd();
+        }
+    }
+}
diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java
index 326e969..f2ffa87 100644
--- a/src/com/android/camera/ui/FilmStripView.java
+++ b/src/com/android/camera/ui/FilmStripView.java
@@ -16,46 +16,72 @@
 
 package com.android.camera.ui;
 
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
-import android.util.Log;
-import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
 import android.widget.Scroller;
 
 public class FilmStripView extends ViewGroup {
-
     private static final String TAG = "FilmStripView";
     private static final int BUFFER_SIZE = 5;
     // Horizontal padding of children.
     private static final int H_PADDING = 50;
     // Duration to go back to the first.
-    private static final int BACK_SCROLL_DURATION = 500;
-    private static final float MIN_SCALE = 0.7f;
+    private static final int DURATION_BACK_ANIM = 500;
+    private static final int DURATION_SCROLL_TO_FILMSTRIP = 350;
+    private static final int DURATION_GEOMETRY_ADJUST = 200;
+    private static final float FILM_STRIP_SCALE = 0.6f;
+    private static final float MAX_SCALE = 1f;
 
     private Context mContext;
-    private GestureDetector mGestureDetector;
+    private FilmStripGestureRecognizer mGestureRecognizer;
     private DataAdapter mDataAdapter;
     private final Rect mDrawArea = new Rect();
 
     private int mCurrentInfo;
-    private Scroller mScroller;
-    private boolean mIsScrolling;
+    private float mScale;
+    private GeometryAnimator mGeometryAnimator;
     private int mCenterPosition = -1;
     private ViewInfo[] mViewInfo = new ViewInfo[BUFFER_SIZE];
 
     public interface ImageData {
+        public static final int TYPE_NONE = 0;
+        public static final int TYPE_CAMERA_PREVIEW = 1;
+        public static final int TYPE_PHOTO = 2;
+        public static final int TYPE_VIDEO = 3;
+        public static final int TYPE_PHOTOSPHERE = 4;
+
+        // The actions are defined bit-wise so we can use bit operations like
+        // | and &.
+        public static final int ACTION_NONE = 0;
+        public static final int ACTION_PROMOTE = 1;
+        public static final int ACTION_DEMOTE = 2;
+
+        // SIZE_FULL means disgard the width or height when deciding the view size
+        // of this ImageData, just use full screen size.
+        public static final int SIZE_FULL = -2;
+
         // The values returned by getWidth() and getHeight() will be used for layout.
         public int getWidth();
         public int getHeight();
+        public int getType();
+        public boolean isActionSupported(int action);
     }
 
     public interface DataAdapter {
+        public interface Listener {
+            public void onDataLoaded();
+            public void onDataInserted(int dataID);
+            public void onDataRemoved(int dataID);
+        }
 
         public int getTotalNumber();
         public View getView(Context context, int id);
@@ -63,22 +89,27 @@
         public void suggestSize(int w, int h);
 
         public void requestLoad(ContentResolver r);
-        public void setDataListener(FilmStripView v);
+        public void setListener(Listener listener);
     }
 
+    // A helper class to tract and calculate the view coordination.
     private static class ViewInfo {
         private int mDataID;
         // the position of the left of the view in the whole filmstrip.
         private int mLeftPosition;
-        private  View mView;
+        private View mView;
+        private float mOffsetY;
 
         public ViewInfo(int id, View v) {
+            v.setPivotX(0f);
+            v.setPivotY(0f);
             mDataID = id;
             mView = v;
             mLeftPosition = -1;
+            mOffsetY = 0;
         }
 
-        public int getId() {
+        public int getID() {
             return mDataID;
         }
 
@@ -90,7 +121,15 @@
             return mLeftPosition;
         }
 
-        public int getCenterPosition() {
+        public float getOffsetY() {
+            return mOffsetY;
+        }
+
+        public void setOffsetY(float offset) {
+            mOffsetY = offset;
+        }
+
+        public int getCenterX() {
             return mLeftPosition + mView.getWidth() / 2;
         }
 
@@ -98,15 +137,20 @@
             return mView;
         }
 
-        private void layoutAt(int l, int t) {
-            mView.layout(l, t, l + mView.getMeasuredWidth(), t + mView.getMeasuredHeight());
+        private void layoutAt(int left, int top) {
+            mView.layout(left, top, left + mView.getMeasuredWidth(),
+                    top + mView.getMeasuredHeight());
         }
 
-        public void layoutIn(Rect drawArea, int refCenter) {
+        public void layoutIn(Rect drawArea, int refCenter, float scale) {
             // drawArea is where to layout in.
             // refCenter is the absolute horizontal position of the center of drawArea.
-            layoutAt(drawArea.centerX() + mLeftPosition - refCenter,
-                     drawArea.centerY() - mView.getMeasuredHeight() / 2);
+            int left = (int) (drawArea.centerX() + (mLeftPosition - refCenter) * scale);
+            int top = (int) (drawArea.centerY() - (mView.getMeasuredHeight() / 2) * scale
+                    + mOffsetY);
+            layoutAt(left, top);
+            mView.setScaleX(scale);
+            mView.setScaleY(scale);
         }
     }
 
@@ -129,15 +173,33 @@
         mCurrentInfo = (BUFFER_SIZE - 1) / 2;
         setWillNotDraw(false);
         mContext = context;
-        mScroller = new Scroller(context);
-        mGestureDetector =
-                new GestureDetector(context, new MyGestureListener(),
-                        null, true /* ignoreMultitouch */);
+        mScale = 1.0f;
+        mGeometryAnimator = new GeometryAnimator(context);
+        mGestureRecognizer =
+                new FilmStripGestureRecognizer(context, new MyGestureReceiver());
+    }
+
+    public float getScale() {
+        return mScale;
+    }
+
+    public boolean isAnchoredTo(int id) {
+        if (mViewInfo[mCurrentInfo].getID() == id
+                && mViewInfo[mCurrentInfo].getCenterX() == mCenterPosition) {
+            return true;
+        }
+        return false;
+    }
+
+    public int getCurrentType() {
+        ViewInfo curr = mViewInfo[mCurrentInfo];
+        if (curr == null) return ImageData.TYPE_NONE;
+        return mDataAdapter.getImageData(curr.getID()).getType();
     }
 
     @Override
     public void onDraw(Canvas c) {
-        if (mIsScrolling) {
+        if (mGeometryAnimator.hasNewGeometry()) {
             layoutChildren();
         }
     }
@@ -146,13 +208,11 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-        int w = MeasureSpec.getSize(widthMeasureSpec);
-        int h = MeasureSpec.getSize(heightMeasureSpec);
-        float scale = MIN_SCALE;
-        if (mDataAdapter != null) mDataAdapter.suggestSize(w / 2, h / 2);
-
-        int boundWidth = (int) (w * scale);
-        int boundHeight = (int) (h * scale);
+        int boundWidth = MeasureSpec.getSize(widthMeasureSpec);
+        int boundHeight = MeasureSpec.getSize(heightMeasureSpec);
+        if (mDataAdapter != null) {
+            mDataAdapter.suggestSize(boundWidth / 2, boundHeight / 2);
+        }
 
         int wMode = View.MeasureSpec.EXACTLY;
         int hMode = View.MeasureSpec.EXACTLY;
@@ -161,22 +221,25 @@
             ViewInfo info = mViewInfo[i];
             if (mViewInfo[i] == null) continue;
 
-            int imageWidth = mDataAdapter.getImageData(info.getId()).getWidth();
-            int imageHeight = mDataAdapter.getImageData(info.getId()).getHeight();
+            int imageWidth = mDataAdapter.getImageData(info.getID()).getWidth();
+            int imageHeight = mDataAdapter.getImageData(info.getID()).getHeight();
+            if (imageWidth == ImageData.SIZE_FULL) imageWidth = boundWidth;
+            if (imageHeight == ImageData.SIZE_FULL) imageHeight = boundHeight;
 
             int scaledWidth = boundWidth;
             int scaledHeight = boundHeight;
+
             if (imageWidth * scaledHeight > scaledWidth * imageHeight) {
                 scaledHeight = imageHeight * scaledWidth / imageWidth;
             } else {
                 scaledWidth = imageWidth * scaledHeight / imageHeight;
             }
-            scaledWidth += H_PADDING * 2 * scale;
+            scaledWidth += H_PADDING * 2;
             mViewInfo[i].getView().measure(
                     View.MeasureSpec.makeMeasureSpec(scaledWidth, wMode)
                     , View.MeasureSpec.makeMeasureSpec(scaledHeight, hMode));
         }
-        setMeasuredDimension(w, h);
+        setMeasuredDimension(boundWidth, boundHeight);
     }
 
     private int findTheNearestView(int pointX) {
@@ -188,14 +251,14 @@
                 nearest++);
         // no existing available ViewInfo
         if (nearest == BUFFER_SIZE) return -1;
-        int min = Math.abs(pointX - mViewInfo[nearest].getCenterPosition());
+        int min = Math.abs(pointX - mViewInfo[nearest].getCenterX());
 
         for (int infoID = nearest + 1;
                 infoID < BUFFER_SIZE && mViewInfo[infoID] != null; infoID++) {
             // not measured yet.
             if  (mViewInfo[infoID].getLeftPosition() == -1) continue;
 
-            int c = mViewInfo[infoID].getCenterPosition();
+            int c = mViewInfo[infoID].getCenterX();
             int dist = Math.abs(pointX - c);
             if (dist < min) {
                 min = dist;
@@ -205,6 +268,14 @@
         return nearest;
     }
 
+    private ViewInfo buildInfoFromData(int dataID) {
+        View v = mDataAdapter.getView(mContext, dataID);
+        if (v == null) return null;
+        v.setPadding(H_PADDING, 0, H_PADDING, 0);
+        addView(v);
+        return new ViewInfo(dataID, v);
+    }
+
     // We try to keep the one closest to the center of the screen at position mCurrentInfo.
     private void stepIfNeeded() {
         int nearest = findTheNearestView(mCenterPosition);
@@ -223,7 +294,8 @@
             }
             for (int k = BUFFER_SIZE - adjust; k < BUFFER_SIZE; k++) {
                 mViewInfo[k] = null;
-                if (mViewInfo[k - 1] != null) getInfo(k, mViewInfo[k - 1].getId() + 1);
+                if (mViewInfo[k - 1] != null)
+                        mViewInfo[k] = buildInfoFromData(mViewInfo[k - 1].getID() + 1);
             }
         } else {
             for (int k = BUFFER_SIZE - 1; k >= BUFFER_SIZE + adjust; k--) {
@@ -236,47 +308,46 @@
             }
             for (int k = -1 - adjust; k >= 0; k--) {
                 mViewInfo[k] = null;
-                if (mViewInfo[k + 1] != null) getInfo(k, mViewInfo[k + 1].getId() - 1);
+                if (mViewInfo[k + 1] != null)
+                        mViewInfo[k] = buildInfoFromData(mViewInfo[k + 1].getID() - 1);
             }
         }
     }
 
-    private void stopScroll() {
-        mScroller.forceFinished(true);
-        mIsScrolling = false;
-    }
-
+    // Don't go out of bound.
     private void adjustCenterPosition() {
         ViewInfo curr = mViewInfo[mCurrentInfo];
         if (curr == null) return;
 
-        if (curr.getId() == 0 && mCenterPosition < curr.getCenterPosition()) {
-            mCenterPosition = curr.getCenterPosition();
-            if (mIsScrolling) stopScroll();
+        if (curr.getID() == 0 && mCenterPosition < curr.getCenterX()) {
+            mCenterPosition = curr.getCenterX();
+            mGeometryAnimator.stopScroll();
         }
-        if (curr.getId() == mDataAdapter.getTotalNumber() - 1
-                && mCenterPosition > curr.getCenterPosition()) {
-            mCenterPosition = curr.getCenterPosition();
-            if (mIsScrolling) stopScroll();
+        if (curr.getID() == mDataAdapter.getTotalNumber() - 1
+                && mCenterPosition > curr.getCenterX()) {
+            mCenterPosition = curr.getCenterX();
+            mGeometryAnimator.stopScroll();
         }
     }
 
     private void layoutChildren() {
-        mIsScrolling = mScroller.computeScrollOffset();
-
-        if (mIsScrolling) mCenterPosition = mScroller.getCurrX();
+        if (mGeometryAnimator.hasNewGeometry()) {
+            mCenterPosition = mGeometryAnimator.getNewPosition();
+            mScale = mGeometryAnimator.getNewScale();
+        }
 
         adjustCenterPosition();
 
-        mViewInfo[mCurrentInfo].layoutIn(mDrawArea, mCenterPosition);
+        mViewInfo[mCurrentInfo].layoutIn(mDrawArea, mCenterPosition, mScale);
 
         // images on the left
         for (int infoID = mCurrentInfo - 1; infoID >= 0; infoID--) {
             ViewInfo curr = mViewInfo[infoID];
             if (curr != null) {
                 ViewInfo next = mViewInfo[infoID + 1];
-                curr.setLeftPosition(next.getLeftPosition() - curr.getView().getMeasuredWidth());
-                curr.layoutIn(mDrawArea, mCenterPosition);
+                curr.setLeftPosition(
+                        next.getLeftPosition() - curr.getView().getMeasuredWidth());
+                curr.layoutIn(mDrawArea, mCenterPosition, mScale);
             }
         }
 
@@ -285,8 +356,9 @@
             ViewInfo curr = mViewInfo[infoID];
             if (curr != null) {
                 ViewInfo prev = mViewInfo[infoID - 1];
-                curr.setLeftPosition(prev.getLeftPosition() + prev.getView().getMeasuredWidth());
-                curr.layoutIn(mDrawArea, mCenterPosition);
+                curr.setLeftPosition(
+                        prev.getLeftPosition() + prev.getView().getMeasuredWidth());
+                curr.layoutIn(mDrawArea, mCenterPosition, mScale);
             }
         }
 
@@ -306,24 +378,43 @@
         layoutChildren();
     }
 
-    public void setDataAdapter(
-            DataAdapter adapter, ContentResolver resolver) {
+    public void setDataAdapter(DataAdapter adapter) {
         mDataAdapter = adapter;
         mDataAdapter.suggestSize(getMeasuredWidth(), getMeasuredHeight());
-        mDataAdapter.setDataListener(this);
-        mDataAdapter.requestLoad(resolver);
+        mDataAdapter.setListener(new DataAdapter.Listener() {
+            @Override
+            public void onDataLoaded() {
+                reload();
+            }
+
+            @Override
+            public void onDataInserted(int dataID) {
+            }
+
+            @Override
+            public void onDataRemoved(int dataID) {
+            }
+        });
     }
 
-    private void getInfo(int infoID, int dataID) {
-        View v = mDataAdapter.getView(mContext, dataID);
-        if (v == null) return;
-        v.setPadding(H_PADDING, 0, H_PADDING, 0);
-        addView(v);
-        ViewInfo info = new ViewInfo(dataID, v);
-        mViewInfo[infoID] = info;
+    public boolean isInCameraFullscreen() {
+        return (isAnchoredTo(0) && mScale == 1f
+                && getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW);
     }
 
-    public void onDataChanged() {
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (isInCameraFullscreen()) return false;
+        return true;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        mGestureRecognizer.onTouchEvent(ev);
+        return true;
+    }
+
+    public void reload() {
         removeAllViews();
         int dataNumber = mDataAdapter.getTotalNumber();
         if (dataNumber == 0) return;
@@ -333,89 +424,315 @@
         // previous data exists.
         if (mViewInfo[mCurrentInfo] != null) {
             currentLeft = mViewInfo[mCurrentInfo].getLeftPosition();
-            currentData = mViewInfo[mCurrentInfo].getId();
+            currentData = mViewInfo[mCurrentInfo].getID();
         }
-        getInfo(mCurrentInfo, currentData);
+        mViewInfo[mCurrentInfo] = buildInfoFromData(currentData);
         mViewInfo[mCurrentInfo].setLeftPosition(currentLeft);
+        if (getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW
+                && currentLeft == 0) {
+            // we are in camera mode by default.
+            mGeometryAnimator.lockPosition(currentLeft);
+        }
         for (int i = 1; mCurrentInfo + i < BUFFER_SIZE || mCurrentInfo - i >= 0; i++) {
             int infoID = mCurrentInfo + i;
             if (infoID < BUFFER_SIZE && mViewInfo[infoID - 1] != null) {
-                getInfo(infoID, mViewInfo[infoID - 1].getId() + 1);
+                mViewInfo[infoID] = buildInfoFromData(mViewInfo[infoID - 1].getID() + 1);
             }
             infoID = mCurrentInfo - i;
             if (infoID >= 0 && mViewInfo[infoID + 1] != null) {
-                getInfo(infoID, mViewInfo[infoID + 1].getId() - 1);
+                mViewInfo[infoID] = buildInfoFromData(mViewInfo[infoID + 1].getID() - 1);
             }
         }
         layoutChildren();
     }
 
-    private void movePositionTo(int position) {
-        mScroller.startScroll(mCenterPosition, 0, position - mCenterPosition,
-                0, BACK_SCROLL_DURATION);
-        layoutChildren();
-    }
+    // GeometryAnimator controls all the geometry animations. It passively
+    // tells the geometry information on demand.
+    private class GeometryAnimator implements
+            ValueAnimator.AnimatorUpdateListener,
+            Animator.AnimatorListener {
 
-    public void goToFirst() {
-        movePositionTo(0);
-    }
+        private ValueAnimator mScaleAnimator;
+        private boolean mHasNewScale;
+        private float mNewScale;
 
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return mGestureDetector.onTouchEvent(ev);
-    }
+        private Scroller mScroller;
+        private boolean mHasNewPosition;
+        private DecelerateInterpolator mDecelerateInterpolator;
 
-    private class MyGestureListener
-                extends GestureDetector.SimpleOnGestureListener {
+        private boolean mCanStopScroll;
+        private boolean mCanStopScale;
+
+        private boolean mIsPositionLocked;
+        private int mLockedPosition;
+
+        private Runnable mPostAction;
+
+        GeometryAnimator(Context context) {
+            mScroller = new Scroller(context);
+            mHasNewPosition = false;
+            mScaleAnimator = new ValueAnimator();
+            mScaleAnimator.addUpdateListener(GeometryAnimator.this);
+            mScaleAnimator.addListener(GeometryAnimator.this);
+            mDecelerateInterpolator = new DecelerateInterpolator();
+            mCanStopScroll = true;
+            mCanStopScale = true;
+            mHasNewScale = false;
+        }
+
+        boolean hasNewGeometry() {
+            mHasNewPosition = mScroller.computeScrollOffset();
+            if (!mHasNewPosition) {
+                mCanStopScroll = true;
+            }
+            // If the position is locked, then we always return true to force
+            // the position value to use the locked value.
+            return (mHasNewPosition || mHasNewScale || mIsPositionLocked);
+        }
+
+        // Always call hasNewGeometry() before getting the new scale value.
+        float getNewScale() {
+            if (!mHasNewScale) return mScale;
+            mHasNewScale = false;
+            return mNewScale;
+        }
+
+        // Always call hasNewGeometry() before getting the new position value.
+        int getNewPosition() {
+            if (mIsPositionLocked) return mLockedPosition;
+            if (!mHasNewPosition) return mCenterPosition;
+            return mScroller.getCurrX();
+        }
+
+        void lockPosition(int pos) {
+            mIsPositionLocked = true;
+            mLockedPosition = pos;
+        }
+
+        void unlockPosition() {
+            if (mIsPositionLocked) {
+                // only when the position is previously locked we set the current
+                // position to make it consistent.
+                mCenterPosition = mLockedPosition;
+                mIsPositionLocked = false;
+            }
+        }
+
+        void fling(int velocityX, int minX, int maxX) {
+            if (!stopScroll() || mIsPositionLocked) return;
+            mScroller.fling(mCenterPosition, 0, velocityX, 0, minX, maxX, 0, 0);
+        }
+
+        boolean stopScroll() {
+            if (!mCanStopScroll) return false;
+            mScroller.forceFinished(true);
+            mHasNewPosition = false;
+            return true;
+        }
+
+        boolean stopScale() {
+            if (!mCanStopScale) return false;
+            mScaleAnimator.cancel();
+            mHasNewScale = false;
+            return true;
+        }
+
+        void stop() {
+            stopScroll();
+            stopScale();
+        }
+
+        void scrollTo(int position, int duration, boolean interruptible) {
+            if (!stopScroll() || mIsPositionLocked) return;
+            mCanStopScroll = interruptible;
+            stopScroll();
+            mScroller.startScroll(mCenterPosition, 0, position - mCenterPosition,
+                    0, duration);
+        }
+
+        void scrollTo(int position, int duration) {
+            scrollTo(position, duration, true);
+        }
+
+        void scaleTo(float scale, int duration, boolean interruptible) {
+            if (!stopScale()) return;
+            mCanStopScale = interruptible;
+            mScaleAnimator.setDuration(duration);
+            mScaleAnimator.setFloatValues(mScale, scale);
+            mScaleAnimator.setInterpolator(mDecelerateInterpolator);
+            mScaleAnimator.start();
+            mHasNewScale = true;
+        }
+
+        void scaleTo(float scale, int duration) {
+            scaleTo(scale, duration, true);
+        }
+
+        void setPostAction(Runnable act) {
+            mPostAction = act;
+        }
 
         @Override
-        public boolean onDoubleTap(MotionEvent e) {
-            float x = (float) e.getX();
-            float y = (float) e.getY();
+        public void onAnimationUpdate(ValueAnimator animation) {
+            mHasNewScale = true;
+            mNewScale = (Float) animation.getAnimatedValue();
+            layoutChildren();
+        }
+
+        @Override
+        public void onAnimationStart(Animator anim) {
+        }
+
+        @Override
+        public void onAnimationEnd(Animator anim) {
+            if (mPostAction != null) {
+                mPostAction.run();
+                mPostAction = null;
+            }
+            mCanStopScale = true;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator anim) {
+            mPostAction = null;
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator anim) {
+        }
+    }
+
+    private class MyGestureReceiver implements FilmStripGestureRecognizer.Listener {
+        // Indicating the current trend of scaling is up (>1) or down (<1).
+        private float mScaleTrend;
+
+        @Override
+        public boolean onSingleTapUp(float x, float y) {
+            return false;
+        }
+
+        @Override
+        public boolean onDoubleTap(float x, float y) {
+            return false;
+        }
+
+        @Override
+        public boolean onDown(float x, float y) {
+            mGeometryAnimator.stop();
+            return true;
+        }
+
+        @Override
+        public boolean onScroll(float x, float y, float dx, float dy) {
+            int deltaX = (int) (dx / mScale);
+            if (deltaX > 0 && isInCameraFullscreen() ) {
+                mGeometryAnimator.unlockPosition();
+                mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+            }
+
+            mCenterPosition += deltaX;
+
+            // Vertical part. Promote or demote.
+            int scaledDeltaY = (int) (dy / mScale);
+
             for (int i = 0; i < BUFFER_SIZE; i++) {
                 if (mViewInfo[i] == null) continue;
+                Rect hitRect = new Rect();
                 View v = mViewInfo[i].getView();
-                if (x >= v.getLeft() && x < v.getRight()
-                        && y >= v.getTop() && y < v.getBottom()) {
-                    Log.v(TAG, "l, r, t, b " + v.getLeft() + ',' + v.getRight()
-                          + ',' + v.getTop() + ',' + v.getBottom());
-                    movePositionTo(mViewInfo[i].getCenterPosition());
+                v.getHitRect(hitRect);
+                if (hitRect.contains((int) x, (int) y)) {
+                    ImageData data = mDataAdapter.getImageData(mViewInfo[i].getID());
+                    if ((data.isActionSupported(ImageData.ACTION_DEMOTE) && dy > 0)
+                            || (data.isActionSupported(ImageData.ACTION_PROMOTE) && dy < 0)) {
+                        mViewInfo[i].setOffsetY(mViewInfo[i].getOffsetY() - dy);
+                    }
                     break;
                 }
             }
-            return true;
-        }
 
-        @Override
-        public boolean onDown(MotionEvent ev) {
-            if (mIsScrolling) stopScroll();
-            return true;
-        }
-
-        @Override
-        public boolean onScroll(
-                MotionEvent e1, MotionEvent e2, float dx, float dy) {
-            stopScroll();
-            mCenterPosition += dx;
             layoutChildren();
             return true;
         }
 
         @Override
-        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
-                float velocityY) {
+        public boolean onFling(float velocityX, float velocityY) {
+            float scaledVelocityX = velocityX / mScale;
+            if (isInCameraFullscreen() && scaledVelocityX < 0) {
+                mGeometryAnimator.unlockPosition();
+                mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+            }
             ViewInfo info = mViewInfo[mCurrentInfo];
             int w = getWidth();
             if (info == null) return true;
-            mScroller.fling(mCenterPosition, 0, (int) -velocityX, (int) velocityY,
+            mGeometryAnimator.fling((int) -scaledVelocityX,
                     // estimation of possible length on the left
-                    info.getLeftPosition() - info.getId() * w * 2,
+                    info.getLeftPosition() - info.getID() * w * 2,
                     // estimation of possible length on the right
                     info.getLeftPosition()
-                            + (mDataAdapter.getTotalNumber() - info.getId()) * w * 2,
-                    0, 0);
+                    + (mDataAdapter.getTotalNumber() - info.getID()) * w * 2);
             layoutChildren();
             return true;
         }
+
+        @Override
+        public boolean onScaleBegin(float focusX, float focusY) {
+            if (isInCameraFullscreen()) return false;
+            mScaleTrend = 1f;
+            return true;
+        }
+
+        @Override
+        public boolean onScale(float focusX, float focusY, float scale) {
+            if (isInCameraFullscreen()) return false;
+
+            mScaleTrend = mScaleTrend * 0.5f + scale * 0.5f;
+            mScale *= scale;
+            if (mScale <= FILM_STRIP_SCALE) mScale = FILM_STRIP_SCALE;
+            if (mScale >= MAX_SCALE) mScale = MAX_SCALE;
+            layoutChildren();
+            return true;
+        }
+
+        @Override
+        public void onScaleEnd() {
+            if (mScaleTrend >= 1f) {
+                if (mScale != 1f) {
+                    mGeometryAnimator.scaleTo(1f, DURATION_GEOMETRY_ADJUST, false);
+                }
+
+                if (getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW) {
+                    if (isAnchoredTo(0)) {
+                        mGeometryAnimator.lockPosition(mViewInfo[mCurrentInfo].getCenterX());
+                    } else {
+                        mGeometryAnimator.scrollTo(
+                                mViewInfo[mCurrentInfo].getCenterX(),
+                                DURATION_GEOMETRY_ADJUST, false);
+                        mGeometryAnimator.setPostAction(mLockPositionRunnable);
+                    }
+                }
+            } else {
+                // Scale down to film strip mode.
+                if (mScale == FILM_STRIP_SCALE) {
+                    mGeometryAnimator.unlockPosition();
+                    return;
+                }
+                mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+                mGeometryAnimator.setPostAction(mUnlockPositionRunnable);
+            }
+        }
+
+        private Runnable mLockPositionRunnable = new Runnable() {
+            @Override
+            public void run() {
+                mGeometryAnimator.lockPosition(mViewInfo[mCurrentInfo].getCenterX());
+            }
+        };
+
+        private Runnable mUnlockPositionRunnable = new Runnable() {
+            @Override
+            public void run() {
+                mGeometryAnimator.unlockPosition();
+            }
+        };
     }
 }
diff --git a/src/com/android/camera/ui/NewCameraRootView.java b/src/com/android/camera/ui/NewCameraRootView.java
new file mode 100644
index 0000000..2d683bc
--- /dev/null
+++ b/src/com/android/camera/ui/NewCameraRootView.java
@@ -0,0 +1,92 @@
+/*
+ * 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 com.android.camera.Util;
+import com.android.gallery3d.R;
+
+public class NewCameraRootView extends FrameLayout
+    implements RotatableLayout.RotationListener {
+
+    private int mOffset = 0;
+    public NewCameraRootView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+    }
+
+    @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;
+    }
+
+    public void cameraModuleChanged() {
+        CameraControls controls = (CameraControls) findViewById(R.id.camera_controls);
+        if (controls != null) {
+            controls.setRotationListener(this);
+            controls.adjustControlsToRightPosition();
+        }
+    }
+
+    @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/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index 8875f74..9b6f2b9 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -119,6 +119,7 @@
     public static final int MSG_ALBUMPAGE_PICKED = 4;
 
     public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit";
+    public static final String ACTION_SIMPLE_EDIT = "action_simple_edit";
 
     private GalleryApp mApplication;
     private SelectionManager mSelectionManager;
@@ -721,6 +722,28 @@
         overrideTransitionToEditor();
     }
 
+    private void launchSimpleEditor() {
+        MediaItem current = mModel.getMediaItem(0);
+        if (current == null || (current.getSupportedOperations()
+                & MediaObject.SUPPORT_EDIT) == 0) {
+            return;
+        }
+
+        Intent intent = new Intent(ACTION_SIMPLE_EDIT);
+
+        intent.setDataAndType(current.getContentUri(), current.getMimeType())
+                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        if (mActivity.getPackageManager()
+                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
+            intent.setAction(Intent.ACTION_EDIT);
+        }
+        intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN,
+                mActivity.isFullscreen());
+        ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
+                REQUEST_EDIT);
+        overrideTransitionToEditor();
+    }
+
     private void requestDeferredUpdate() {
         mDeferUpdateUntil = SystemClock.uptimeMillis() + DEFERRED_UPDATE_MS;
         if (!mDeferredUpdateWaiting) {
@@ -1085,6 +1108,10 @@
                 launchPhotoEditor();
                 return true;
             }
+            case R.id.action_simple_edit: {
+                launchSimpleEditor();
+                return true;
+            }
             case R.id.action_details: {
                 if (mShowDetails) {
                     hideDetails();
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index 1232d0b..874a7c9 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -134,6 +134,7 @@
     private FilterIconButton mNullBorderFilter;
     private int mIconSeedSize = 140;
 
+    private View mImageCategoryPanel = null;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -220,6 +221,16 @@
 
         setupHistoryPanel();
         setupStatePanel();
+
+        mImageCategoryPanel = findViewById(R.id.imageCategoryPanel);
+    }
+
+    public void hideCategoryPanel() {
+        mImageCategoryPanel.setVisibility(View.GONE);
+    }
+
+    public void showCategoryPanel() {
+        mImageCategoryPanel.setVisibility(View.VISIBLE);
     }
 
     public void setupHistoryPanel() {
@@ -506,6 +517,7 @@
             }
             pipeline.turnOnPipeline(true);
             MasterImage.getImage().setOriginalGeometry(largeBitmap);
+            MasterImage.getImage().getHistory().setOriginalBitmap(mImageLoader.getOriginalBitmapSmall());
             mLoadBitmapTask = null;
 
             if (mAction == CROP_ACTION) {
@@ -549,6 +561,12 @@
 
     private int translateMainPanel(View viewPanel) {
         int accessoryPanelWidth = viewPanel.getWidth();
+        if (accessoryPanelWidth == 0) {
+            // TODO: fixes this by using a fragment. Currently,
+            // the first time we get called the panel hasn't been
+            // layed out yet, so we get a size zero.
+            accessoryPanelWidth = (int) getPixelsFromDip(200);
+        }
         int mainViewWidth = findViewById(R.id.mainView).getWidth();
         int mainPanelWidth = mImageShow.getDisplayedImageBounds().width();
         if (mainPanelWidth == 0) {
@@ -760,8 +778,8 @@
 
         int[] drawid = {
                 R.drawable.filtershow_fx_0005_punch,
-                R.drawable.filtershow_fx_0000_vintage,
-                R.drawable.filtershow_fx_0004_bw_contrast,
+//                R.drawable.filtershow_fx_0000_vintage,
+//                R.drawable.filtershow_fx_0004_bw_contrast,
                 R.drawable.filtershow_fx_0002_bleach,
                 R.drawable.filtershow_fx_0001_instant,
                 R.drawable.filtershow_fx_0007_washout,
@@ -772,8 +790,8 @@
 
         int[] fxNameid = {
                 R.string.ffx_punch,
-                R.string.ffx_vintage,
-                R.string.ffx_bw_contrast,
+//                R.string.ffx_vintage,
+//                R.string.ffx_bw_contrast,
                 R.string.ffx_bleach,
                 R.string.ffx_instant,
                 R.string.ffx_washout,
diff --git a/src/com/android/gallery3d/filtershow/HistoryAdapter.java b/src/com/android/gallery3d/filtershow/HistoryAdapter.java
index 2afb7d2..8d68246 100644
--- a/src/com/android/gallery3d/filtershow/HistoryAdapter.java
+++ b/src/com/android/gallery3d/filtershow/HistoryAdapter.java
@@ -44,6 +44,8 @@
     private MenuItem mRedoMenuItem = null;
     private MenuItem mResetMenuItem = null;
 
+    private Bitmap mOriginalBitmap = null;
+
     public HistoryAdapter(Context context, int resource, int textViewResourceId) {
         super(context, resource, textViewResourceId);
         FilterShowActivity activity = (FilterShowActivity) context;
@@ -199,6 +201,9 @@
             }
             ImageView preview = (ImageView) view.findViewById(R.id.preview);
             Bitmap bmp = item.getPreviewImage();
+            if (position == getCount()-1 && mOriginalBitmap != null) {
+                bmp = mOriginalBitmap;
+            }
             if (bmp != null) {
                 preview.setImageBitmap(bmp);
             } else {
@@ -214,4 +219,8 @@
         return view;
     }
 
+
+    public void setOriginalBitmap(Bitmap originalBitmap) {
+        mOriginalBitmap = originalBitmap;
+    }
 }
diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java
index 6b20fe1..f29765a 100644
--- a/src/com/android/gallery3d/filtershow/PanelController.java
+++ b/src/com/android/gallery3d/filtershow/PanelController.java
@@ -31,6 +31,7 @@
 import com.android.gallery3d.R;
 import com.android.gallery3d.filtershow.editors.Editor;
 import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
+import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
 import com.android.gallery3d.filtershow.filters.ImageFilter;
 import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet;
@@ -369,6 +370,7 @@
         HistoryAdapter adapter = MasterImage.getImage().getHistory();
         int position = adapter.undo();
         MasterImage.getImage().onHistoryItemClick(position);
+        mActivity.showCategoryPanel();
         showPanel(mCurrentPanel);
         mCurrentImage.select();
         if (mCurrentEditor != null) {
@@ -586,6 +588,7 @@
         }
         mUtilityPanel.hideAccessoryViews();
         mUtilityPanel.showMenu(false);
+
         if (view instanceof FilterIconButton) {
             mCurrentEditor = null;
             FilterIconButton component = (FilterIconButton) view;
@@ -595,6 +598,9 @@
                 mUtilityPanel.setShowParameter(representation.showParameterValue());
 
                 if (representation.getEditorId() != 0) {
+                    if (representation.getEditorId() != ImageOnlyEditor.ID) {
+                        mActivity.hideCategoryPanel();
+                    }
                     if (mEditorPlaceHolder.contains(representation.getEditorId())) {
                         mCurrentEditor = mEditorPlaceHolder.showEditor(
                                 representation.getEditorId());
@@ -625,6 +631,7 @@
             return;
         }
 
+        mActivity.showCategoryPanel();
         int id = view.getId();
         if (id == EditorTinyPlanet.ID) {
             mCurrentImage = showImageView(R.id.imageTinyPlanet);
diff --git a/src/com/android/gallery3d/filtershow/controller/BasicSlider.java b/src/com/android/gallery3d/filtershow/controller/BasicSlider.java
index c54fe77..df5b6ae 100644
--- a/src/com/android/gallery3d/filtershow/controller/BasicSlider.java
+++ b/src/com/android/gallery3d/filtershow/controller/BasicSlider.java
@@ -34,6 +34,7 @@
 
     @Override
     public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+        container.removeAllViews();
         mEditor = editor;
         Context context = container.getContext();
         mParameter = (ParameterInteger) parameter;
diff --git a/src/com/android/gallery3d/filtershow/controller/ParameterStyles.java b/src/com/android/gallery3d/filtershow/controller/ParameterStyles.java
new file mode 100644
index 0000000..c267d3d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/ParameterStyles.java
@@ -0,0 +1,37 @@
+/*
+ * 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.controller;
+
+import android.content.Context;
+
+import com.android.gallery3d.filtershow.cache.RenderingRequestCaller;
+
+public interface ParameterStyles extends Parameter {
+    public static String sParameterType = "ParameterStyles";
+
+    int getNumberOfStyles();
+
+    int getDefaultSelected();
+
+    int getSelected();
+
+    void setSelected(int value);
+
+    void getIcon(int index, RenderingRequestCaller caller);
+
+    String getStyleTitle(int index, Context context);
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/StyleChooser.java b/src/com/android/gallery3d/filtershow/controller/StyleChooser.java
new file mode 100644
index 0000000..3f75839
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/controller/StyleChooser.java
@@ -0,0 +1,100 @@
+package com.android.gallery3d.filtershow.controller;
+
+import android.app.ActionBar.LayoutParams;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.ImageView.ScaleType;
+import android.widget.LinearLayout;
+
+import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.cache.RenderingRequest;
+import com.android.gallery3d.filtershow.cache.RenderingRequestCaller;
+import com.android.gallery3d.filtershow.editors.Editor;
+
+import java.util.Vector;
+
+public class StyleChooser implements Control, RenderingRequestCaller {
+    private final String LOGTAG = "StyleChooser";
+    protected ParameterStyles mParameter;
+    protected LinearLayout mLinearLayout;
+    protected Editor mEditor;
+    private View mTopView;
+    private int mProcessingButton = 0;
+    private Vector<ImageButton> mIconButton = new Vector<ImageButton>();
+    protected int mLayoutID = R.layout.filtershow_control_style_chooser;
+
+    @Override
+    public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+        container.removeAllViews();
+        mEditor = editor;
+        Context context = container.getContext();
+        mParameter = (ParameterStyles) parameter;
+        LayoutInflater inflater =
+                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        mTopView = inflater.inflate(mLayoutID, container, true);
+        mLinearLayout = (LinearLayout) mTopView.findViewById(R.id.listStyles);
+        mTopView.setVisibility(View.VISIBLE);
+        int n = mParameter.getNumberOfStyles();
+        mIconButton.clear();
+        LayoutParams lp = new LayoutParams(120, 120);
+        for (int i = 0; i < n; i++) {
+            ImageButton button = new ImageButton(context);
+            button.setScaleType(ScaleType.CENTER_CROP);
+            button.setLayoutParams(lp);
+            button.setBackgroundResource(android.R.color.transparent);
+            mIconButton.add(button);
+            final int buttonNo = i;
+            button.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View arg0) {
+                    mParameter.setSelected(buttonNo);
+                }
+            });
+            mLinearLayout.addView(button);
+        }
+        mProcessingButton = 0;
+        mParameter.getIcon(mProcessingButton, this);
+    }
+
+    @Override
+    public View getTopView() {
+        return mTopView;
+    }
+
+    @Override
+    public void setPrameter(Parameter parameter) {
+        mParameter = (ParameterStyles) parameter;
+        updateUI();
+    }
+
+    @Override
+    public void updateUI() {
+        if (mParameter == null) {
+            return;
+        }
+    }
+
+    @Override
+    public void available(RenderingRequest request) {
+        Bitmap bmap = request.getBitmap();
+        if (bmap == null) {
+            return;
+        }
+
+        try {
+            ImageButton button = mIconButton.get(mProcessingButton);
+            button.setImageBitmap(bmap);
+        } catch (Exception e) {
+            return;
+        }
+
+        mProcessingButton++;
+        if (mProcessingButton < mParameter.getNumberOfStyles())
+            mParameter.getIcon(mProcessingButton, this);
+    }
+
+}
diff --git a/src/com/android/gallery3d/filtershow/controller/TitledSlider.java b/src/com/android/gallery3d/filtershow/controller/TitledSlider.java
index 30b6fdb..cbaa489 100644
--- a/src/com/android/gallery3d/filtershow/controller/TitledSlider.java
+++ b/src/com/android/gallery3d/filtershow/controller/TitledSlider.java
@@ -40,6 +40,7 @@
 
     @Override
     public void setUp(ViewGroup container, Parameter parameter, Editor editor) {
+        container.removeAllViews();
         mEditor = editor;
         Context context = container.getContext();
         mParameter = (ParameterInteger) parameter;
diff --git a/src/com/android/gallery3d/filtershow/editors/BasicEditor.java b/src/com/android/gallery3d/filtershow/editors/BasicEditor.java
index 2ff9c04..d9e9724 100644
--- a/src/com/android/gallery3d/filtershow/editors/BasicEditor.java
+++ b/src/com/android/gallery3d/filtershow/editors/BasicEditor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * 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.
@@ -17,42 +17,31 @@
 package com.android.gallery3d.filtershow.editors;
 
 import android.content.Context;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.SeekBar;
-import android.widget.SeekBar.OnSeekBarChangeListener;
 
 import com.android.gallery3d.R;
+import com.android.gallery3d.filtershow.controller.Control;
+import com.android.gallery3d.filtershow.controller.ParameterInteger;
 import com.android.gallery3d.filtershow.filters.FilterBasicRepresentation;
+import com.android.gallery3d.filtershow.filters.FilterRepresentation;
+
 
 /**
  * The basic editor that all the one parameter filters
  */
-public class BasicEditor extends Editor {
+public class BasicEditor extends ParametricEditor implements ParameterInteger {
     public static int ID = R.id.basicEditor;
-
-    private final String LOGTAG = "Editor";
-    private int mLayoutID = R.layout.filtershow_default_editor;
-    private int mViewID = R.id.basicEditor;
+    private final String LOGTAG = "BasicEditor";
 
     public BasicEditor() {
-        super(ID);
+        super(ID, R.layout.filtershow_default_editor, R.id.basicEditor);
     }
 
     protected BasicEditor(int id) {
-        super(id);
+        super(id, R.layout.filtershow_default_editor, R.id.basicEditor);
     }
 
     protected BasicEditor(int id, int layoutID, int viewID) {
-        super(id);
-        mLayoutID = layoutID;
-        mViewID = viewID;
-    }
-
-    @Override
-    public void createEditor(Context context, FrameLayout frameLayout) {
-        super.createEditor(context, frameLayout);
-        unpack(mViewID, mLayoutID);
+        super(id, layoutID, viewID);
     }
 
     @Override
@@ -60,36 +49,81 @@
         super.reflectCurrentFilter();
         if (getLocalRepresentation() != null && getLocalRepresentation() instanceof FilterBasicRepresentation) {
             FilterBasicRepresentation interval = (FilterBasicRepresentation) getLocalRepresentation();
-            boolean f = interval.showParameterValue();
-            mSeekBar.setVisibility((f) ? View.VISIBLE : View.GONE);
-            int value = interval.getValue();
-            int min = interval.getMinimum();
-            int max = interval.getMaximum();
-            mSeekBar.setMax(max - min);
-            mSeekBar.setProgress(value - min);
+            Context context = mContext;
+            interval.getTextId();
+
         }
     }
 
-    @Override
-    public void onProgressChanged(SeekBar sbar, int progress, boolean arg2) {
-        if (getLocalRepresentation() != null && getLocalRepresentation() instanceof FilterBasicRepresentation) {
-            FilterBasicRepresentation interval = (FilterBasicRepresentation) getLocalRepresentation();
-            int value = progress + interval.getMinimum();
-            interval.setValue(value);
-            mImageShow.onNewValue(value);
-            mView.invalidate();
-            if (interval.showParameterValue()) {
-                mPanelController.onNewValue(value);
-            }
-            commitLocalRepresentation();
+    private FilterBasicRepresentation getBasicRepresentation() {
+        FilterRepresentation tmpRep = getLocalRepresentation();
+        if (tmpRep != null && tmpRep instanceof FilterBasicRepresentation) {
+            return (FilterBasicRepresentation) tmpRep;
+
         }
+        return null;
     }
 
     @Override
-    public void onStartTrackingTouch(SeekBar arg0) {
+    public int getMaximum() {
+        FilterBasicRepresentation rep = getBasicRepresentation();
+        if (rep == null) {
+            return 0;
+        }
+        return rep.getMaximum();
     }
 
     @Override
-    public void onStopTrackingTouch(SeekBar arg0) {
+    public int getMinimum() {
+        FilterBasicRepresentation rep = getBasicRepresentation();
+        if (rep == null) {
+            return 0;
+        }
+        return rep.getMinimum();
     }
+
+    @Override
+    public int getDefaultValue() {
+        return 0;
+    }
+
+    @Override
+    public int getValue() {
+        FilterBasicRepresentation rep = getBasicRepresentation();
+        if (rep == null) {
+            return 0;
+        }
+        return rep.getValue();
+    }
+
+    @Override
+    public String getValueString() {
+        return null;
+    }
+
+    @Override
+    public void setValue(int value) {
+        FilterBasicRepresentation rep = getBasicRepresentation();
+        if (rep == null) {
+            return;
+        }
+        rep.setValue(value);
+        commitLocalRepresentation();
+    }
+
+    @Override
+    public String getParameterName() {
+        FilterBasicRepresentation rep = getBasicRepresentation();
+        return mContext.getString(rep.getTextId());
+    }
+
+    @Override
+    public String getParameterType() {
+        return sParameterType;
+    }
+
+    @Override
+    public void setController(Control c) {
+    }
+
 }
diff --git a/src/com/android/gallery3d/filtershow/editors/ParametricEditor.java b/src/com/android/gallery3d/filtershow/editors/ParametricEditor.java
index cf00f4a..9c275d4 100644
--- a/src/com/android/gallery3d/filtershow/editors/ParametricEditor.java
+++ b/src/com/android/gallery3d/filtershow/editors/ParametricEditor.java
@@ -34,6 +34,8 @@
 import com.android.gallery3d.filtershow.controller.Parameter;
 import com.android.gallery3d.filtershow.controller.ParameterActionAndInt;
 import com.android.gallery3d.filtershow.controller.ParameterInteger;
+import com.android.gallery3d.filtershow.controller.ParameterStyles;
+import com.android.gallery3d.filtershow.controller.StyleChooser;
 import com.android.gallery3d.filtershow.controller.TitledSlider;
 import com.android.gallery3d.filtershow.filters.FilterBasicRepresentation;
 import com.android.gallery3d.filtershow.filters.FilterRepresentation;
@@ -49,7 +51,8 @@
     protected Control mControl;
     public static final int MINIMUM_WIDTH = 600;
     public static final int MINIMUM_HEIGHT = 800;
-
+    View mActionButton;
+    View mEditControl;
     static HashMap<String, Class> portraitMap = new HashMap<String, Class>();
     static HashMap<String, Class> landscapeMap = new HashMap<String, Class>();
     static {
@@ -57,6 +60,8 @@
         landscapeMap.put(ParameterInteger.sParameterType, TitledSlider.class);
         portraitMap.put(ParameterActionAndInt.sParameterType, ActionSlider.class);
         landscapeMap.put(ParameterActionAndInt.sParameterType, ActionSlider.class);
+        portraitMap.put(ParameterStyles.sParameterType, StyleChooser.class);
+        landscapeMap.put(ParameterStyles.sParameterType, StyleChooser.class);
     }
 
     static Constructor getConstructor(Class cl) {
@@ -122,7 +127,7 @@
         };
     }
 
-    // TODO: need a better way to decide when which representation
+    // TODO: need a better way to decide which representation
     static boolean useCompact(Context context) {
         WindowManager w = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE));
         Point size = new Point();
@@ -139,13 +144,23 @@
         return false;
     }
 
+    protected Parameter getParameterToEdit(FilterRepresentation rep) {
+        if (this instanceof Parameter) {
+            return (Parameter) this;
+        } else if (rep instanceof Parameter) {
+            return ((Parameter) rep);
+        }
+        return null;
+    }
+
     @Override
     public void setUtilityPanelUI(View actionButton, View editControl) {
+        mActionButton = actionButton;
+        mEditControl = editControl;
         FilterRepresentation rep = getLocalRepresentation();
-        if (this instanceof Parameter) {
-            control((Parameter) this, editControl);
-        } else if (rep instanceof Parameter) {
-            control((Parameter) rep, editControl);
+        Parameter param = getParameterToEdit(rep);
+        if (param != null) {
+            control(param, editControl);
         } else {
             mSeekBar = new SeekBar(editControl.getContext());
             LayoutParams lp = new LinearLayout.LayoutParams(
@@ -164,8 +179,10 @@
         if (c != null) {
             try {
                 mControl = (Control) c.newInstance();
+                p.setController(mControl);
                 mControl.setUp((ViewGroup) editControl, p, this);
 
+
             } catch (Exception e) {
                 Log.e(LOGTAG, "Error in loading Control ", e);
             }
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
index a0523c1..0137768 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
@@ -30,6 +30,9 @@
     private int mLastInputWidth = 0;
     private int mLastInputHeight = 0;
 
+    private static ScriptC_grey mGreyConvert = null;
+    private static RenderScript mRScache = null;
+
     private volatile boolean mResourcesLoaded = false;
 
     protected abstract void createFilter(android.content.res.Resources res,
@@ -101,14 +104,19 @@
 
     private static Allocation convertBitmap(Bitmap bitmap) {
         return Allocation.createFromBitmap(CachingPipeline.getRenderScriptContext(), bitmap,
-                Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
+                Allocation.MipmapControl.MIPMAP_NONE,
+                Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_TEXTURE);
     }
 
     private static Allocation convertRGBAtoA(Bitmap bitmap) {
         RenderScript RS = CachingPipeline.getRenderScriptContext();
+        if (RS != mRScache || mGreyConvert == null) {
+            mGreyConvert = new ScriptC_grey(RS, RS.getApplicationContext().getResources(),
+                                            R.raw.grey);
+            mRScache = RS;
+        }
+
         Type.Builder tb_a8 = new Type.Builder(RS, Element.A_8(RS));
-        ScriptC_grey greyConvert = new ScriptC_grey(RS,
-                RS.getApplicationContext().getResources(), R.raw.grey);
 
         Allocation bitmapTemp = convertBitmap(bitmap);
         if (bitmapTemp.getType().getElement().isCompatible(Element.A_8(RS))) {
@@ -117,9 +125,11 @@
 
         tb_a8.setX(bitmapTemp.getType().getX());
         tb_a8.setY(bitmapTemp.getType().getY());
-        Allocation bitmapAlloc = Allocation.createTyped(RS, tb_a8.create());
-        greyConvert.forEach_RGBAtoA(bitmapTemp, bitmapAlloc);
-
+        Allocation bitmapAlloc = Allocation.createTyped(RS, tb_a8.create(),
+                                                        Allocation.MipmapControl.MIPMAP_NONE,
+                                                        Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_TEXTURE);
+        mGreyConvert.forEach_RGBAtoA(bitmapTemp, bitmapAlloc);
+        bitmapTemp.destroy();
         return bitmapAlloc;
     }
 
diff --git a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
index e5ffd8b..7b881c7 100644
--- a/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
+++ b/src/com/android/gallery3d/filtershow/ui/ImageCurves.java
@@ -260,15 +260,22 @@
             return true;
         }
 
-        float posX = e.getX() / getWidth();
-        float posY = e.getY();
         float margin = Spline.curveHandleSize() / 2;
+        float posX = e.getX();
+        if (posX < margin) {
+            posX = margin;
+        }
+        float posY = e.getY();
         if (posY < margin) {
             posY = margin;
         }
+        if (posX > getWidth() - margin) {
+            posX = getWidth() - margin;
+        }
         if (posY > getHeight() - margin) {
             posY = getHeight() - margin;
         }
+        posX = (posX - margin) / (getWidth() - 2 * margin);
         posY = (posY - margin) / (getHeight() - 2 * margin);
 
         if (e.getActionMasked() == MotionEvent.ACTION_UP) {
diff --git a/src/com/android/gallery3d/ui/MenuExecutor.java b/src/com/android/gallery3d/ui/MenuExecutor.java
index 497632f..f5d3dc3 100644
--- a/src/com/android/gallery3d/ui/MenuExecutor.java
+++ b/src/com/android/gallery3d/ui/MenuExecutor.java
@@ -190,6 +190,7 @@
         setMenuItemVisible(menu, R.id.action_setas, supportSetAs);
         setMenuItemVisible(menu, R.id.action_show_on_map, supportShowOnMap);
         setMenuItemVisible(menu, R.id.action_edit, supportEdit);
+        setMenuItemVisible(menu, R.id.action_simple_edit, supportEdit);
         setMenuItemVisible(menu, R.id.action_details, supportInfo);
     }