New mode drawer.

-Changed the entire look of the mode drawer by using a live preview
 as background, and use circle icons rather than block shaped icons
-Fade in the blurred preview frame during mode switch
-Add highlighted and selected states to mode icons
-Center the mode drawer in preview rather than whole screen
-Swapped mode order in the drawer

TODO:
-Add settings access point
-Refine swipe in/out behavior of the drawer

Change-Id: Ibab0fe960bcfbb9635ca7f45d50178cb1ef2941f
diff --git a/res/drawable/mode_icon_background.xml b/res/drawable/mode_icon_background.xml
new file mode 100644
index 0000000..305216c
--- /dev/null
+++ b/res/drawable/mode_icon_background.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+
+    <solid
+        android:color="@color/mode_selector_icon_background"/>
+
+    <size
+        android:width="@dimen/mode_selector_icon_block_width"
+        android:height="@dimen/mode_selector_icon_block_width"/>
+</shape>
\ No newline at end of file
diff --git a/res/drawable/mode_icon_highlight.xml b/res/drawable/mode_icon_highlight.xml
new file mode 100644
index 0000000..f9e3d72
--- /dev/null
+++ b/res/drawable/mode_icon_highlight.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="ring"
+    android:thickness="3dp"
+    android:useLevel="false"
+    android:innerRadius="21dp" >
+
+    <solid
+        android:color="@color/mode_selector_icon_background"/>
+
+    <size
+        android:width="@dimen/mode_selector_icon_block_width"
+        android:height="@dimen/mode_selector_icon_block_width" />
+</shape>
\ No newline at end of file
diff --git a/res/layout/mode_list_layout.xml b/res/layout/mode_list_layout.xml
index 24ddf95..6925acc 100644
--- a/res/layout/mode_list_layout.xml
+++ b/res/layout/mode_list_layout.xml
@@ -26,6 +26,7 @@
     <LinearLayout
         android:id="@+id/mode_list"
         android:orientation="vertical"
-        android:layout_width="match_parent"
+        android:layout_gravity="center_vertical"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content" />
 </com.android.camera.ui.ModeListView>
\ No newline at end of file
diff --git a/res/layout/mode_selector.xml b/res/layout/mode_selector.xml
index c9d9375..939ed07 100644
--- a/res/layout/mode_selector.xml
+++ b/res/layout/mode_selector.xml
@@ -17,21 +17,23 @@
 <com.android.camera.ui.ModeSelectorItem
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="@dimen/mode_selector_item_height"
+    android:paddingLeft="16dp"
+    android:paddingTop="6dp"
+    android:paddingBottom="6dp">
     <TextView
         android:id="@+id/selector_text"
-        android:layout_gravity="left"
-        android:paddingLeft="24dp"
-        android:paddingRight="24dp"
+        android:layout_gravity="left|center_vertical"
+        android:paddingLeft="16dp"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:gravity="center_vertical"
-        android:textSize="21sp"
+        android:textSize="14sp"
         android:textColor="@color/mode_selector_text_color"
         android:layout_marginLeft="@dimen/mode_selector_icon_block_width" />
-    <ImageView
+    <com.android.camera.ui.ModeIconView
         android:id="@+id/selector_icon"
         android:scaleType="centerInside"
         android:layout_width="@dimen/mode_selector_icon_block_width"
-        android:layout_height="match_parent" />
+        android:layout_height="@dimen/mode_selector_icon_block_width" />
 </com.android.camera.ui.ModeSelectorItem>
\ No newline at end of file
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index 16b831b..3f48450 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -583,25 +583,32 @@
     <integer name="camera_mode_photosphere">3</integer>
     <integer name="camera_mode_panorama">4</integer>
     <integer name="camera_mode_timelapse">5</integer>
-    <integer name="camera_mode_setting">6</integer>
-    <integer name="camera_mode_gcam">7</integer>
+    <integer name="camera_mode_gcam">6</integer>
 
     <!-- An array of camera mode indices in the sequence of their appearance
     in the mode drawer. -->
     <integer-array name="camera_modes_in_nav_drawer_if_supported">
+        <item>@integer/camera_mode_panorama</item>
+        <item>@integer/camera_mode_photosphere</item>
+        <item>@integer/camera_mode_refocus</item>
+        <item>@integer/camera_mode_photo</item>
+        <item>@integer/camera_mode_video</item>
+    </integer-array>
+
+    <!-- Camera modes that each supported mode is nested in in nav drawer. -->
+    <integer-array name="camera_mode_nested_in_nav_drawer">
         <item>@integer/camera_mode_photo</item>
         <item>@integer/camera_mode_video</item>
         <item>@integer/camera_mode_refocus</item>
-        <item>@integer/camera_mode_panorama</item>
         <item>@integer/camera_mode_photosphere</item>
-        <item>@integer/camera_mode_timelapse</item>
-        <item>@integer/camera_mode_setting</item>
+        <item>@integer/camera_mode_panorama</item>
+        <item>@integer/camera_mode_photo</item>
+        <item>@integer/camera_mode_photo</item>
     </integer-array>
 
     <!-- An array of camera mode indices that should always be visible in mode drawer. -->
     <integer-array name="camera_modes_always_visible">
         <item>@integer/camera_mode_photo</item>
-        <item>@integer/camera_mode_setting</item>
     </integer-array>
 
     <array name="camera_mode_theme_color">
@@ -611,7 +618,6 @@
         <item>@color/photosphere_mode_color</item>
         <item>@color/panorama_mode_color</item>
         <item>@color/timelapse_mode_color</item>
-        <item>@color/settings_mode_color</item>
         <item>@color/camera_mode_color</item>
     </array>
 
@@ -622,7 +628,6 @@
         <item>@string/mode_photosphere</item>
         <item>@string/mode_panorama</item>
         <item>@string/mode_timelapse</item>
-        <item>@string/mode_settings</item>
         <item>""</item>
     </string-array>
 
@@ -633,7 +638,6 @@
         <item>@string/mode_photosphere_desc</item>
         <item>@string/mode_panorama_desc</item>
         <item>@string/mode_timelapse_desc</item>
-        <item>@string/mode_settings_desc</item>
         <item>""</item>
     </string-array>
 
@@ -644,7 +648,6 @@
         <item>@drawable/ic_photo_sphere_normal</item>
         <item>@drawable/ic_panorama_normal</item>
         <item>@drawable/ic_timelapse_normal</item>
-        <item>@drawable/ic_settings_normal</item>
         <item>@drawable/ic_camera_normal</item>
     </array>
 
@@ -655,7 +658,6 @@
         <item>@null</item>
         <item>@drawable/ic_panorama_normal</item>
         <item>@null</item>
-        <item>@null</item>
         <item>@drawable/ic_camera_normal</item>
     </array>
 
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 08ebedb..c8b2abe 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -71,10 +71,11 @@
     <color name="gray">#FFAAAAAA</color>
 
     <!-- Camera mode switcher -->
-    <color name="mode_selector_text_color">#6d6d6d</color>
+    <color name="mode_selector_text_color">#fff</color>
     <color name="mode_selector_background_light">#f5f5f5</color>
     <color name="mode_selector_background_dark">#e7e7e7</color>
     <color name="mode_selector_text_highlight_color">#ffffffff</color>
+    <color name="mode_selector_icon_background">#4c000000</color>
     <color name="mode_list_background">#00000000</color>
 
     <color name="camera_mode_color">#76a7fa</color>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index b3a2bec..f3e5b01 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -154,8 +154,9 @@
     <dimen name="photoeditor_original_text_margin">4dp</dimen>
 
     <!-- Mode selector icon width -->
-    <dimen name="mode_selector_icon_block_width">84dp</dimen>
+    <dimen name="mode_selector_icon_block_width">48dp</dimen>
     <dimen name="mode_selector_icon_drawable_size">32dp</dimen>
+    <dimen name="mode_selector_item_height">60dp</dimen>
 
     <!-- Filmstrip bottom controls -->
     <dimen name="filmstrip_bottom_control_size">48dp</dimen>
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index bf5172d..b900aa7 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -1466,12 +1466,6 @@
             return;
         }
 
-        int settingsIndex = getResources().getInteger(R.integer.camera_mode_setting);
-        if (modeIndex == settingsIndex) {
-            onSettingsSelected();
-            return;
-        }
-
         CameraPerformanceTracker.onEvent(CameraPerformanceTracker.MODE_SWITCH_START);
         // Record last used camera mode for quick switching
         if (modeIndex == getResources().getInteger(R.integer.camera_mode_photo)
diff --git a/src/com/android/camera/TextureViewHelper.java b/src/com/android/camera/TextureViewHelper.java
index 2e1f5b3..80acbad 100644
--- a/src/com/android/camera/TextureViewHelper.java
+++ b/src/com/android/camera/TextureViewHelper.java
@@ -197,6 +197,14 @@
     }
 
     /**
+     * Returns a new copy of the preview area, to avoid internal data being modified
+     * from outside of the class.
+     */
+    public RectF getPreviewArea() {
+        return new RectF(mPreviewArea);
+    }
+
+    /**
      * Adds a listener that will get notified when the preview size changed. This
      * can be useful for UI elements or focus overlay to adjust themselves according
      * to the preview size change.
diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java
index 9848ae9..1438da3 100644
--- a/src/com/android/camera/app/CameraAppUI.java
+++ b/src/com/android/camera/app/CameraAppUI.java
@@ -17,7 +17,10 @@
 package com.android.camera.app;
 
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Matrix;
+import android.graphics.RectF;
 import android.graphics.SurfaceTexture;
 import android.hardware.display.DisplayManager;
 import android.util.Log;
@@ -427,6 +430,56 @@
         }
     };
 
+    /**
+     * Provides current preview frame and the controls/overlay from the module that
+     * are shown on top of the preview.
+     */
+    public interface CameraModuleScreenShotProvider {
+        /**
+         * Returns the current preview frame down-sampled using the given down-sample
+         * factor.
+         *
+         * @param downSampleFactor the down sample factor for down sampling the
+         *                         preview frame. (e.g. a down sample factor of
+         *                         2 means to scale down the preview frame to 1/2
+         *                         the width and height.)
+         * @return down-sampled preview frame
+         */
+        public Bitmap getPreviewFrame(int downSampleFactor);
+
+        /**
+         * @return the controls and overlays that are currently showing on top of
+         *         the preview drawn into a bitmap with no scaling applied.
+         */
+        public Bitmap getPreviewOverlayAndControls();
+    }
+
+    private final CameraModuleScreenShotProvider mCameraModuleScreenShotProvider =
+            new CameraModuleScreenShotProvider() {
+                @Override
+                public Bitmap getPreviewFrame(int downSampleFactor) {
+                    if (mCameraRootView == null || mTextureView == null) {
+                        return null;
+                    }
+
+                    RectF previewArea = mTextureViewHelper.getPreviewArea();
+                    // Gets the bitmap from the preview TextureView.
+                    Bitmap preview = mTextureView.getBitmap(
+                            (int) previewArea.width() / downSampleFactor,
+                            (int) previewArea.height() / downSampleFactor);
+                    return preview;
+                }
+
+                @Override
+                public Bitmap getPreviewOverlayAndControls() {
+                    Bitmap overlays = Bitmap.createBitmap(mCameraRootView.getWidth(),
+                            mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888);
+                    Canvas canvas = new Canvas(overlays);
+                    mCameraRootView.draw(canvas);
+                    return overlays;
+                }
+            };
+
     private long mCoverHiddenTime = -1; // System time when preview cover was hidden.
 
     public long getCoverHiddenTime() {
@@ -544,6 +597,7 @@
         if (mModeListView != null) {
             mModeListView.setModeSwitchListener(this);
             mModeListView.setModeListOpenListener(this);
+            mModeListView.setCameraModuleScreenShotProvider(mCameraModuleScreenShotProvider);
         } else {
             Log.e(TAG, "Cannot find mode list in the view hierarchy");
         }
@@ -865,6 +919,7 @@
                 mCameraRootView.findViewById(R.id.capture_overlay);
         mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
         mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
+        mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView);
 
         if (mIndicatorIconController == null) {
             mIndicatorIconController =
@@ -961,6 +1016,11 @@
         }
     }
 
+    @Override
+    public int getCurrentModeIndex() {
+        return mController.getCurrentModuleIndex();
+    }
+
     /********************** Capture animation **********************/
     /* TODO: This session is subject to UX changes. In addition to the generic
        flash animation and post capture animation, consider designating a parameter
diff --git a/src/com/android/camera/ui/ModeIconView.java b/src/com/android/camera/ui/ModeIconView.java
new file mode 100644
index 0000000..d70e095
--- /dev/null
+++ b/src/com/android/camera/ui/ModeIconView.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2014 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.graphics.Canvas;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.android.camera2.R;
+
+/**
+ * This class encapsulates the logic of drawing different states of the icon in
+ * mode drawer for when it is highlighted (to indicate the current module), or when
+ * it is selected by the user. It handles the internal state change like a state
+ * list drawable. The advantage over a state list drawable is that in the class
+ * multiple states can be rendered using the same drawable with some color modification,
+ * whereas a state list drawable would require a different drawable for each state.
+ */
+public class ModeIconView extends ImageView {
+
+    private boolean mHighlightIsOn = false;
+    private final GradientDrawable mBackground;
+    private final GradientDrawable mHighlightDrawable;
+    private final int mIconBackgroundSize;
+    private int mHighlightColor;
+    private final int mBackgroundDefaultColor;
+
+    public ModeIconView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mBackgroundDefaultColor = getResources().getColor(R.color.mode_selector_icon_background);
+        mIconBackgroundSize = getResources().getDimensionPixelSize(
+                R.dimen.mode_selector_icon_block_width);
+        mBackground = (GradientDrawable) getResources()
+                .getDrawable(R.drawable.mode_icon_background).mutate();
+        mBackground.setBounds(0, 0, mIconBackgroundSize, mIconBackgroundSize);
+        mHighlightDrawable = (GradientDrawable) getResources()
+                .getDrawable(R.drawable.mode_icon_highlight).mutate();
+        mHighlightDrawable.setBounds(0, 0, mIconBackgroundSize, mIconBackgroundSize);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        mBackground.draw(canvas);
+        if (mHighlightIsOn) {
+            mHighlightDrawable.draw(canvas);
+        }
+        super.draw(canvas);
+    }
+
+    /**
+     * This gets called when the selected state is changed. When selected, the background
+     * drawable will use a solid pre-defined color to indicate selection.
+     *
+     * @param selected true when selected, false otherwise.
+     */
+    public void setSelected(boolean selected) {
+        if (selected) {
+            mBackground.setColor(mHighlightColor);
+            mHighlightIsOn = false;
+        } else {
+            mBackground.setColor(mBackgroundDefaultColor);
+        }
+        invalidate();
+    }
+
+    /**
+     * This gets called when the highlighted state is changed. When highlighted,
+     * a ring shaped drawable of a solid pre-defined color will be drawn on top
+     * of the background drawable to indicate highlight state.
+     *
+     * @param highlighted true when highlighted, false otherwise.
+     */
+    public void setHighlighted(boolean highlighted) {
+        mHighlightIsOn = highlighted;
+        invalidate();
+    }
+
+    /**
+     * Sets the color that will be used in the drawable for highlight state.
+     *
+     * @param highlightColor color for the highlight state
+     */
+    public void setHighlightColor(int highlightColor) {
+        mHighlightColor = highlightColor;
+        mHighlightDrawable.setColor(highlightColor);
+    }
+}
diff --git a/src/com/android/camera/ui/ModeListView.java b/src/com/android/camera/ui/ModeListView.java
index 939c643..0fce66d 100644
--- a/src/com/android/camera/ui/ModeListView.java
+++ b/src/com/android/camera/ui/ModeListView.java
@@ -23,10 +23,13 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
+import android.graphics.RectF;
+import android.os.AsyncTask;
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -34,9 +37,11 @@
 import android.view.GestureDetector;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
 import android.widget.LinearLayout;
-import android.widget.ScrollView;
 
+import com.android.camera.app.CameraAppUI;
 import com.android.camera.util.CameraUtil;
 import com.android.camera.util.Gusterpolator;
 import com.android.camera.widget.AnimationEffects;
@@ -52,7 +57,8 @@
  * any of the items in the list will take the user to that corresponding mode
  * with an animation. To dismiss this list, simply swipe left or select a mode.
  */
-public class ModeListView extends ScrollView {
+public class ModeListView extends FrameLayout
+        implements PreviewStatusListener.PreviewAreaSizeChangedListener {
 
     private static final String TAG = "ModeListView";
 
@@ -76,13 +82,25 @@
     private static final int MODE_SELECTED = 4;
 
     // Scrolling delay between non-focused item and focused item
-    private static final int DELAY_MS = 25;
+    private static final int DELAY_MS = 30;
     // If the fling velocity exceeds this threshold, snap to full screen at a constant
     // speed. Unit: pixel/ms.
     private static final float VELOCITY_THRESHOLD = 2f;
 
+    /**
+     * A factor to change the UI responsiveness on a scroll.
+     * e.g. A scroll factor of 0.5 means UI will move half as fast as the finger.
+     */
+    private static final float SCROLL_FACTOR = 0.5f;
+    // 30% transparent black background.
+    private static final int BACKGROUND_TRANSPARENTCY = (int) (0.3f * 255);
+    private static final int PREVIEW_DOWN_SAMPLE_FACTOR = 4;
+    // Threshold, below which snap back will happen.
+    private static final float SNAP_BACK_THRESHOLD_RATIO = 0.33f;
+
     private final GestureDetector mGestureDetector;
     private final int mIconBlockWidth;
+    private final RectF mPreviewArea = new RectF();
 
     private int mListBackgroundColor;
     private LinearLayout mListView;
@@ -93,6 +111,9 @@
     private int mFocusItem = NO_ITEM_SELECTED;
     private AnimationEffects mCurrentEffect;
     private ModeListOpenListener mModeListOpenListener;
+    private CameraAppUI.CameraModuleScreenShotProvider mScreenShotProvider = null;
+    private int[] mInputPixels;
+    private int[] mOutputPixels;
 
     // Width and height of this view. They get updated in onLayout()
     // Unit for width and height are pixels.
@@ -134,8 +155,14 @@
         }
     };
 
+    @Override
+    public void onPreviewAreaSizeChanged(RectF previewArea) {
+        mPreviewArea.set(previewArea);
+    }
+
     public interface ModeSwitchListener {
         public void onModeSelected(int modeIndex);
+        public int getCurrentModeIndex();
     }
 
     public interface ModeListOpenListener {
@@ -231,7 +258,7 @@
             mState = SCROLLING;
             // Scroll based on the scrolling distance on the currently focused
             // item.
-            scroll(mFocusItem, distanceX, distanceY);
+            scroll(mFocusItem, distanceX * SCROLL_FACTOR, distanceY * SCROLL_FACTOR);
             return true;
         }
 
@@ -241,11 +268,24 @@
                 // Only allows tap to choose mode when the list is fully shown
                 return false;
             }
+
+            // Ignore the tap if it happens outside of the mode list linear layout.
+            int x = (int) ev.getX() - mListView.getLeft();
+            int y = (int) ev.getY() - mListView.getTop();
+            if (x < 0 || x > mListView.getWidth() || y < 0 || y > mListView.getHeight()) {
+                return false;
+            }
+
             int index = getFocusItem(ev.getX(), ev.getY());
             // Validate the selection
             if (index != NO_ITEM_SELECTED) {
                 final int modeId = getModeIndex(index);
-                mModeSelectorItems[index].highlight();
+                // Select the focused item.
+                mModeSelectorItems[index].setSelected(true);
+                // Un-highlight all the modes.
+                for (int i = 0; i < mModeSelectorItems.length; i++) {
+                    mModeSelectorItems[i].setHighlighted(false);
+                }
                 mState = MODE_SELECTED;
                 PeepholeAnimationEffect effect = new PeepholeAnimationEffect();
                 effect.setSize(mWidth, mHeight);
@@ -257,8 +297,28 @@
                         snapBack(false);
                     }
                 });
-                effect.setAnimationStartingPosition((int) ev.getX(), (int) ev.getY());
+
+                // Calculate the position of the icon in the selected item, and
+                // start animation from that position.
+                int[] location = new int[2];
+                // Gets icon's center position in relative to the window.
+                mModeSelectorItems[index].getIconCenterLocationInWindow(location);
+                int iconX = location[0];
+                int iconY = location[1];
+                // Gets current view's top left position relative to the window.
+                getLocationInWindow(location);
+                // Calculate icon location relative to this view
+                iconX -= location[0];
+                iconY -= location[1];
+
+                effect.setAnimationStartingPosition(iconX, iconY);
+                if (mScreenShotProvider != null) {
+                    effect.setBackground(mScreenShotProvider
+                            .getPreviewFrame(PREVIEW_DOWN_SAMPLE_FACTOR), mPreviewArea);
+                    effect.setBackgroundOverlay(mScreenShotProvider.getPreviewOverlayAndControls());
+                }
                 mCurrentEffect = effect;
+                invalidate();
 
                 // Post mode selection runnable to the end of the message queue
                 // so that current UI changes can finish before mode initialization
@@ -276,7 +336,7 @@
         @Override
         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
             // Cache velocity in the unit pixel/ms.
-            mVelocityX = velocityX / 1000f;
+            mVelocityX = velocityX / 1000f * SCROLL_FACTOR;
             return true;
         }
     };
@@ -302,7 +362,7 @@
         mListBackgroundColor = mListBackgroundColor & 0xFFFFFF;
         mListBackgroundColor = mListBackgroundColor | (alpha << 24);
         // Set new color to list background.
-        mListView.setBackgroundColor(mListBackgroundColor);
+        setBackgroundColor(mListBackgroundColor);
     }
 
     /**
@@ -341,6 +401,15 @@
         initializeModeSelectorItems();
     }
 
+    /**
+     * Sets the screen shot provider for getting a preview frame and a bitmap
+     * of the controls and overlay.
+     */
+    public void setCameraModuleScreenShotProvider(
+            CameraAppUI.CameraModuleScreenShotProvider provider) {
+        mScreenShotProvider = provider;
+    }
+
     private void initializeModeSelectorItems() {
         mModeSelectorItems = new ModeSelectorItem[mTotalModes];
         // Inflate the mode selector items and add them to a linear layout
@@ -351,16 +420,9 @@
             ModeSelectorItem selectorItem =
                     (ModeSelectorItem) inflater.inflate(R.layout.mode_selector, null);
             mListView.addView(selectorItem);
-            // Set alternating background color for each mode selector in the list
-            if (i % 2 == 0) {
-                selectorItem.setDefaultBackgroundColor(getResources()
-                        .getColor(R.color.mode_selector_background_light));
-            } else {
-                selectorItem.setDefaultBackgroundColor(getResources()
-                        .getColor(R.color.mode_selector_background_dark));
-            }
+
             int modeId = getModeIndex(i);
-            selectorItem.setIconBackgroundColor(getResources()
+            selectorItem.setHighlightColor(getResources()
                     .getColor(CameraUtil.getCameraThemeColorId(modeId, getContext())));
 
             // Set image
@@ -491,26 +553,8 @@
      */
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        float height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingTop()
-                - getPaddingBottom();
-
-        Configuration config = getResources().getConfiguration();
-        if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
-            height = height / ROWS_TO_SHOW_IN_LANDSCAPE;
-            setVerticalScrollBarEnabled(true);
-        } else {
-            height = height / mTotalModes;
-            setVerticalScrollBarEnabled(false);
-        }
-        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mWidth, 0);
-        lp.width = LayoutParams.MATCH_PARENT;
-        for (int i = 0; i < mTotalModes; i++) {
-            // This is to avoid rounding that would cause the total height of the
-            // list a few pixels off the height of the screen.
-            int itemHeight = (int) (height * (i + 1)) - (int) (height * i);
-            lp.height = itemHeight;
-            mModeSelectorItems[i].setLayoutParams(lp);
-        }
+        centerModeDrawerInPreview(MeasureSpec.getSize(widthMeasureSpec),
+                MeasureSpec.getSize(heightMeasureSpec));
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
@@ -562,7 +606,6 @@
     private void resetModeSelectors() {
         for (int i = 0; i < mModeSelectorItems.length; i++) {
             mModeSelectorItems[i].setVisibleWidth(0);
-            mModeSelectorItems[i].unHighlight();
         }
         // Visible width has been changed to 0
         onVisibleWidthChanged(0);
@@ -574,22 +617,68 @@
 
     /**
      * Calculate the mode selector item in the list that is at position (x, y).
+     * If the position is above the top item or below the bottom item, return
+     * the top item or bottom item respectively.
      *
      * @param x horizontal position
      * @param y vertical position
      * @return index of the item that is at position (x, y)
      */
     private int getFocusItem(float x, float y) {
-        // Take into account the scrolling offset
-        x += getScrollX();
-        y += getScrollY();
+        // Convert coordinates into child view's coordinates.
+        x -= mListView.getLeft();
+        y -= mListView.getTop();
 
         for (int i = 0; i < mModeSelectorItems.length; i++) {
-            if (mModeSelectorItems[i].getTop() <= y && mModeSelectorItems[i].getBottom() >= y) {
+            if (y <= mModeSelectorItems[i].getBottom()) {
                 return i;
             }
         }
-        return NO_ITEM_SELECTED;
+        return mModeSelectorItems.length - 1;
+    }
+
+    @Override
+    public void onVisibilityChanged(View v, int visibility) {
+        super.onVisibilityChanged(v, visibility);
+        if (visibility == VISIBLE) {
+            centerModeDrawerInPreview(getMeasuredWidth(), getMeasuredHeight());
+            // Highlight current module
+            if (mModeSwitchListener != null) {
+                int modeId = mModeSwitchListener.getCurrentModeIndex();
+                int parentMode = CameraUtil.getCameraModeParentModeId(modeId, getContext());
+                // Find parent mode in the nav drawer.
+                for (int i = 0; i < mSupportedModes.size(); i++) {
+                    if (mSupportedModes.get(i) == parentMode) {
+                        mModeSelectorItems[i].setHighlighted(true);
+                    }
+                }
+            }
+        } else if (mModeSelectorItems != null) {
+            // When becoming invisible/gone after initializing mode selector items.
+            for (int i = 0; i < mModeSelectorItems.length; i++) {
+                mModeSelectorItems[i].setHighlighted(false);
+                mModeSelectorItems[i].setSelected(false);
+            }
+        }
+    }
+
+    /**
+     * Center mode drawer in camera preview.
+     */
+    private void centerModeDrawerInPreview(int measuredWidth, int measuredHeight) {
+
+        // Assuming the preview is centered in the space aside from bottom bar.
+        float previewAreaWidth = mPreviewArea.right + mPreviewArea.left;
+        float previewAreaHeight = mPreviewArea.top + mPreviewArea.bottom;
+        if (measuredWidth > measuredHeight) {
+            // Landscape.
+            int previewWidth = (int) Math.max(previewAreaHeight, previewAreaWidth);
+            setPadding(0, 0, measuredWidth - previewWidth, 0);
+        } else {
+            // Portrait.
+            int previewHeight = (int) Math.max(previewAreaHeight, previewAreaWidth);
+            setPadding(0, 0, 0, measuredHeight - previewHeight);
+        }
     }
 
     private void scroll(int itemId, float deltaX, float deltaY) {
@@ -712,17 +801,11 @@
      * to darken/lighten correspondingly.
      */
     private void onVisibleWidthChanged(int focusItemWidth) {
-        // Background alpha should be 0 before the icon block is entirely visible,
-        // and when the longest mode item is entirely shown (across the screen), the
+        // When the longest mode item is entirely shown (across the screen), the
         // background should be 50% transparent.
-        if (focusItemWidth <= mIconBlockWidth) {
-            setBackgroundAlpha(0);
-        } else {
-            // Alpha should increase linearly when mode item goes beyond the icon block
-            // till it reaches its max width
-            int alpha = 127 * (focusItemWidth - mIconBlockWidth) / (mWidth - mIconBlockWidth);
-            setBackgroundAlpha(alpha);
-        }
+        int maxVisibleWidth = mModeSelectorItems[0].getMaxVisibleWidth();
+        focusItemWidth = Math.min(maxVisibleWidth, focusItemWidth);
+        setBackgroundAlpha(BACKGROUND_TRANSPARENTCY * focusItemWidth / maxVisibleWidth);
     }
 
     @Override
@@ -744,7 +827,8 @@
     private void snap() {
         if (mState == SCROLLING) {
             int itemId = Math.max(0, mFocusItem);
-            if (mModeSelectorItems[itemId].getVisibleWidth() < mIconBlockWidth) {
+            if (mModeSelectorItems[itemId].getVisibleWidth()
+                    < mModeSelectorItems[itemId].getMaxVisibleWidth() * SNAP_BACK_THRESHOLD_RATIO) {
                 snapBack();
             } else if (Math.abs(mScrollTrendX) > Math.abs(mScrollTrendY) && mScrollTrendX > 0) {
                 snapBack();
@@ -761,7 +845,11 @@
      */
     public void snapBack(boolean withAnimation) {
         if (withAnimation) {
-            animateListToWidth(0);
+            if (mVelocityX > -VELOCITY_THRESHOLD * SCROLL_FACTOR) {
+                animateListToWidth(0);
+            } else {
+                animateListToWidthAtVelocity(mVelocityX, 0);
+            }
             mState = IDLE;
         } else {
             setVisibility(INVISIBLE);
@@ -778,12 +866,14 @@
     }
 
     private void snapToFullScreen() {
-        if (mVelocityX <= VELOCITY_THRESHOLD) {
-            animateListToWidth(mWidth);
+        int focusItem = mFocusItem == NO_ITEM_SELECTED ? 0 : mFocusItem;
+        int fullWidth = mModeSelectorItems[focusItem].getMaxVisibleWidth();
+        if (mVelocityX <= VELOCITY_THRESHOLD * SCROLL_FACTOR) {
+            animateListToWidth(fullWidth);
         } else {
             // If the fling velocity exceeds this threshold, snap to full screen
             // at a constant speed.
-            animateListToWidthAtVelocity(mVelocityX, mWidth);
+            animateListToWidthAtVelocity(mVelocityX, fullWidth);
         }
         mState = FULLY_SHOWN;
         if (mModeListOpenListener != null) {
@@ -905,17 +995,22 @@
     private class PeepholeAnimationEffect extends AnimationEffects {
 
         private final static int UNSET = -1;
-        private final static int PEEP_HOLE_ANIMATION_DURATION_MS = 650;
+        private final static int PEEP_HOLE_ANIMATION_DURATION_MS = 300;
+
+        private final Paint mMaskPaint = new Paint();
+        private final Paint mBackgroundPaint = new Paint();
+        private final RectF mBackgroundDrawArea = new RectF();
 
         private int mWidth;
         private int mHeight;
-
         private int mPeepHoleCenterX = UNSET;
         private int mPeepHoleCenterY = UNSET;
         private float mRadius = 0f;
         private ValueAnimator mPeepHoleAnimator;
         private Runnable mEndAction;
-        private final Paint mMaskPaint = new Paint();
+        private Bitmap mBackground;
+        private Bitmap mBlurredBackground;
+        private Bitmap mBackgroundOverlay;
 
         public PeepholeAnimationEffect() {
             mMaskPaint.setAlpha(0);
@@ -942,6 +1037,62 @@
             mPeepHoleCenterY = y;
         }
 
+        /**
+         * Sets the bitmap to be drawn in the background and the drawArea to draw
+         * the bitmap. In the meantime, start processing the image in a background
+         * thread to get a blurred background image.
+         *
+         * @param background image to be drawn in the background
+         * @param drawArea area to draw the background image
+         */
+        public void setBackground(Bitmap background, RectF drawArea) {
+            mBackground = background;
+            mBackgroundDrawArea.set(drawArea);
+            new BlurTask().execute(Bitmap.createScaledBitmap(background, background.getWidth(),
+                    background.getHeight(), true));
+        }
+
+        /**
+         * Sets the overlay image to be drawn on top of the background.
+         */
+        public void setBackgroundOverlay(Bitmap overlay) {
+            mBackgroundOverlay = overlay;
+        }
+
+        /**
+         * This gets called when a blurred image of the background is generated.
+         * Start an animation to fade in the blur.
+         *
+         * @param blur blurred image of the background.
+         */
+        public void setBlurredBackground(Bitmap blur) {
+            mBlurredBackground = blur;
+            // Start fade in.
+            ObjectAnimator alpha = ObjectAnimator.ofInt(mBackgroundPaint, "alpha", 80, 255);
+            alpha.setDuration(250);
+            alpha.setInterpolator(Gusterpolator.INSTANCE);
+            alpha.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                @Override
+                public void onAnimationUpdate(ValueAnimator animation) {
+                    invalidate();
+                }
+            });
+            alpha.start();
+            invalidate();
+        }
+
+        @Override
+        public void drawBackground(Canvas canvas) {
+            if (mBackground != null && mBackgroundOverlay != null) {
+                canvas.drawARGB(255, 0, 0, 0);
+                canvas.drawBitmap(mBackground, null, mBackgroundDrawArea, null);
+                if (mBlurredBackground != null) {
+                    canvas.drawBitmap(mBlurredBackground, null, mBackgroundDrawArea, mBackgroundPaint);
+                }
+                canvas.drawBitmap(mBackgroundOverlay, 0, 0, null);
+            }
+        }
+
         @Override
         public void startAnimation() {
             if (mPeepHoleAnimator != null && mPeepHoleAnimator.isRunning()) {
@@ -956,6 +1107,8 @@
             int verticalDistanceToFarEdge = Math.max(mPeepHoleCenterY, mHeight - mPeepHoleCenterY);
             int endRadius = (int) (Math.sqrt(horizontalDistanceToFarEdge * horizontalDistanceToFarEdge
                     + verticalDistanceToFarEdge * verticalDistanceToFarEdge));
+            int startRadius = getResources().getDimensionPixelSize(
+                    R.dimen.mode_selector_icon_block_width) / 2;
 
             mPeepHoleAnimator = ValueAnimator.ofFloat(0, endRadius);
             mPeepHoleAnimator.setDuration(PEEP_HOLE_ANIMATION_DURATION_MS);
@@ -1013,5 +1166,40 @@
         public void setAnimationEndAction(Runnable runnable) {
             mEndAction = runnable;
         }
+
+        private class BlurTask extends AsyncTask<Bitmap, Integer, Bitmap> {
+
+            // Gaussian blur mask size.
+            private static final int MASK_SIZE = 7;
+            @Override
+            protected Bitmap doInBackground(Bitmap... params) {
+
+                Bitmap intermediateBitmap = params[0];
+                int factor = 4;
+                Bitmap lowResPreview = Bitmap.createScaledBitmap(intermediateBitmap,
+                        intermediateBitmap.getWidth() / factor,
+                        intermediateBitmap.getHeight() / factor, true);
+
+                int width = lowResPreview.getWidth();
+                int height = lowResPreview.getHeight();
+
+                if (mInputPixels == null || mInputPixels.length < width * height) {
+                    mInputPixels = new int[width * height];
+                    mOutputPixels = new int[width * height];
+                }
+                lowResPreview.getPixels(mInputPixels, 0, width, 0, 0, width, height);
+                CameraUtil.blur(mInputPixels, mOutputPixels, width, height, MASK_SIZE);
+                lowResPreview.setPixels(mOutputPixels, 0, width, 0, 0, width, height);
+
+                intermediateBitmap.recycle();
+                return Bitmap.createScaledBitmap(lowResPreview, width * factor,
+                        height * factor, true);
+            }
+
+            @Override
+            protected void onPostExecute(Bitmap bitmap) {
+                setBlurredBackground(bitmap);
+            }
+        };
     }
 }
diff --git a/src/com/android/camera/ui/ModeSelectorItem.java b/src/com/android/camera/ui/ModeSelectorItem.java
index 44c567c..836c3d9 100644
--- a/src/com/android/camera/ui/ModeSelectorItem.java
+++ b/src/com/android/camera/ui/ModeSelectorItem.java
@@ -17,14 +17,11 @@
 package com.android.camera.ui;
 
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.GradientDrawable;
 import android.util.AttributeSet;
 import android.widget.FrameLayout;
-import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.android.camera.util.ApiHelper;
@@ -39,41 +36,39 @@
  * and a GradientDrawable at the end of the TextView.
  *
  * The purpose of this class is to encapsulate different drawing logic into
- * its own class. There are two drawing mode, <code>TRUNCATE_TEXT_END</code>
- * and <code>TRUNCATE_TEXT_FRONT</code>. They define how we draw the view when
+ * its own class. There are two drawing mode, <code>FLY_IN</code>
+ * and <code>FLY_OUT</code>. They define how we draw the view when
  * we display the view partially.
  */
 class ModeSelectorItem extends FrameLayout {
     // Drawing modes that defines how the TextView should be drawn when there
     // is not enough space to draw the whole TextView.
-    public static final int TRUNCATE_TEXT_END = 1;
-    public static final int TRUNCATE_TEXT_FRONT = 2;
+    public static final int FLY_IN = 1;
+    public static final int FLY_OUT = 2;
 
     private static final int SHADE_WIDTH_PIX = 100;
 
     private TextView mText;
-    private ImageView mIcon;
+    private ModeIconView mIcon;
     private int mVisibleWidth;
-    private int mMinVisibleWidth;
-    private GradientDrawable mGradientShade;
+    private final int mMinVisibleWidth;
 
-    private int mDrawingMode = TRUNCATE_TEXT_END;
+    private int mDrawingMode = FLY_IN;
     private int mHeight;
     private int mWidth;
     private int mDefaultBackgroundColor;
     private int mDefaultTextColor;
-    private int mIconBlockColor;
-    private final int mHighlightTextColor;
 
     public ModeSelectorItem(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mHighlightTextColor = context.getResources()
-                .getColor(R.color.mode_selector_text_highlight_color);
+        setWillNotDraw(false);
+        mMinVisibleWidth = getResources()
+                .getDimensionPixelSize(R.dimen.mode_selector_icon_block_width);
     }
 
     @Override
     public void onFinishInflate() {
-        mIcon = (ImageView) findViewById(R.id.selector_icon);
+        mIcon = (ModeIconView) findViewById(R.id.selector_icon);
         mText = (TextView) findViewById(R.id.selector_text);
         Typeface typeface;
         if (ApiHelper.HAS_ROBOTO_LIGHT_FONT) {
@@ -84,8 +79,6 @@
                     "Roboto-Light.ttf");
         }
         mText.setTypeface(typeface);
-        mMinVisibleWidth = getResources()
-                .getDimensionPixelSize(R.dimen.mode_selector_icon_block_width);
         mDefaultTextColor = mText.getCurrentTextColor();
     }
 
@@ -94,23 +87,12 @@
         setBackgroundColor(color);
     }
 
-    @Override
-    public void setBackgroundColor(int color) {
-        super.setBackgroundColor(color);
-        int startColor = 0x00FFFFFF & color;
-        // Gradient shade will draw at the end of the item
-        mGradientShade = new GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT,
-                new int[] {startColor, color});
+    public void setHighlighted(boolean highlighted) {
+        mIcon.setHighlighted(highlighted);
     }
 
-    public void highlight() {
-        mText.setTextColor(mHighlightTextColor);
-        setBackgroundColor(mIconBlockColor);
-    }
-
-    public void unHighlight() {
-        setBackgroundColor(mDefaultBackgroundColor);
-        mText.setTextColor(mDefaultTextColor);
+    public void setSelected(boolean selected) {
+        mIcon.setSelected(selected);
     }
 
     /**
@@ -122,15 +104,10 @@
      *                to right)
      */
     public void onSwipeModeChanged(boolean swipeIn) {
-        mDrawingMode = swipeIn ? TRUNCATE_TEXT_END : TRUNCATE_TEXT_FRONT;
+        mDrawingMode = swipeIn ? FLY_IN : FLY_OUT;
         mText.setTranslationX(0);
     }
 
-    public void setIconBackgroundColor(int color) {
-        mIconBlockColor = color;
-        mIcon.setBackgroundColor(color);
-    }
-
     public void setText(CharSequence text) {
         mText.setText(text);
     }
@@ -143,7 +120,7 @@
         if (changed && mVisibleWidth > 0) {
             // Reset mode list to full screen
             setVisibleWidth(mWidth);
-            mDrawingMode = TRUNCATE_TEXT_FRONT;
+            mDrawingMode = FLY_OUT;
         }
     }
 
@@ -174,16 +151,34 @@
      * @param newWidth new visible width
      */
     public void setVisibleWidth(int newWidth) {
+        int fullyShownIconWidth = getMaxVisibleWidth();
         newWidth = Math.max(newWidth, 0);
         // Visible width should not be greater than view width
-        newWidth = Math.min(newWidth, mWidth);
+        newWidth = Math.min(newWidth, fullyShownIconWidth);
         mVisibleWidth = newWidth;
         float transX = 0f;
         // If the given width is less than the icon width, we need to translate icon
-        if (mVisibleWidth < mMinVisibleWidth) {
-            transX = mMinVisibleWidth - mVisibleWidth;
+        if (mVisibleWidth < mMinVisibleWidth + mIcon.getLeft()) {
+            transX = mMinVisibleWidth + mIcon.getLeft() - mVisibleWidth;
         }
         setTranslationX(-transX);
+
+        if (mDrawingMode == FLY_IN) {
+            // Swipe open.
+            int width = Math.min(mVisibleWidth, fullyShownIconWidth);
+            // Linear interpolate text opacity.
+            float alpha = (float) width / (float) fullyShownIconWidth;
+            mText.setAlpha(alpha);
+        } else {
+            // Swipe back.
+            int width = Math.max(mVisibleWidth, mMinVisibleWidth / 2);
+            width = Math.min(width, fullyShownIconWidth);
+            // Linear interpolate text opacity.
+            float alpha = (float) (width - mMinVisibleWidth / 2)
+                    / (float) (fullyShownIconWidth - mMinVisibleWidth);
+            mText.setAlpha(alpha);
+        }
+
         invalidate();
     }
 
@@ -204,27 +199,34 @@
      */
     @Override
     public void draw(Canvas canvas) {
-        int width = Math.max(mVisibleWidth, mMinVisibleWidth);
-        int height = canvas.getHeight();
-        int shadeStart = -1;
-
-        if (mDrawingMode == TRUNCATE_TEXT_END) {
-            if (mVisibleWidth > mMinVisibleWidth) {
-                shadeStart = Math.max(mMinVisibleWidth, mVisibleWidth - SHADE_WIDTH_PIX);
-            }
-        } else {
-            if (mVisibleWidth <= mWidth) {
-                mText.setTranslationX(mVisibleWidth - mWidth);
-            }
-        }
-
-        if (width < mWidth) {
-            canvas.clipRect(0, 0, width, height);
-        }
         super.draw(canvas);
-        if (shadeStart > 0 && width < mWidth) {
-            mGradientShade.setBounds(shadeStart, 0, width, height);
-            mGradientShade.draw(canvas);
-        }
+    }
+
+    /**
+     * Sets the color that will be used in the drawable for highlight state.
+     *
+     * @param highlightColor color for the highlight state
+     */
+    public void setHighlightColor(int highlightColor) {
+        mIcon.setHighlightColor(highlightColor);
+    }
+
+    /**
+     * Gets the maximum visible width of the mode icon. The mode item will be
+     * full shown when the mode icon has max visible width.
+     */
+    public int getMaxVisibleWidth() {
+        return mIcon.getLeft() + mMinVisibleWidth;
+    }
+
+    /**
+     * Gets the position of the icon center relative to the window.
+     *
+     * @param loc integer array of size 2, to hold the position x and y
+     */
+    public void getIconCenterLocationInWindow(int[] loc) {
+        mIcon.getLocationInWindow(loc);
+        loc[0] += mMinVisibleWidth / 2;
+        loc[1] += mMinVisibleWidth / 2;
     }
 }
diff --git a/src/com/android/camera/ui/ModeTransitionView.java b/src/com/android/camera/ui/ModeTransitionView.java
index b5a2c42..0d0f465 100644
--- a/src/com/android/camera/ui/ModeTransitionView.java
+++ b/src/com/android/camera/ui/ModeTransitionView.java
@@ -46,7 +46,7 @@
 public class ModeTransitionView extends View {
     private static final String TAG = "ModeTransitionView";
 
-    private static final int PEEP_HOLE_ANIMATION_DURATION_MS = 550;
+    private static final int PEEP_HOLE_ANIMATION_DURATION_MS = 300;
     private static final int ICON_FADE_OUT_DURATION_MS = 850;
     private static final int FADE_OUT_DURATION_MS = 100;
 
diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java
index fc324af..707a3e2 100644
--- a/src/com/android/camera/util/CameraUtil.java
+++ b/src/com/android/camera/util/CameraUtil.java
@@ -41,6 +41,7 @@
 import android.os.ParcelFileDescriptor;
 import android.telephony.TelephonyManager;
 import android.util.DisplayMetrics;
+import android.util.FloatMath;
 import android.util.Log;
 import android.util.TypedValue;
 import android.view.Display;
@@ -903,6 +904,104 @@
         }
     }
 
+    /**
+     * Generates a 1d Gaussian mask of the input array size, and store the mask
+     * in the input array.
+     *
+     * @param mask empty array of size n, where n will be used as the size of the
+     *             Gaussian mask, and the array will be populated with the values
+     *             of the mask.
+     */
+    private static void getGaussianMask(float[] mask) {
+        int len = mask.length;
+        int mid = len / 2;
+        float sigma = len;
+        float sum = 0;
+        for (int i = 0; i <= mid; i++) {
+            float ex = FloatMath.exp(-(i - mid) * (i - mid) / (mid * mid))
+                    / (2 * sigma * sigma);
+            int symmetricIndex = len - 1 - i;
+            mask[i] = ex;
+            mask[symmetricIndex] = ex;
+            sum += mask[i];
+            if (i != symmetricIndex) {
+                sum += mask[symmetricIndex];
+            }
+        }
+
+        for (int i = 0; i < mask.length; i++) {
+            mask[i] /= sum;
+        }
+
+    }
+
+    /**
+     * Add two pixels together where the second pixel will be applied with a weight.
+     *
+     * @param pixel pixel color value of weight 1
+     * @param newPixel second pixel color value where the weight will be applied
+     * @param weight a float weight that will be applied to the second pixel color
+     * @return the weighted addition of the two pixels
+     */
+    public static int addPixel(int pixel, int newPixel, float weight) {
+        // TODO: scale weight to [0, 1024] to avoid casting to float and back to int.
+        int r = ((pixel & 0x00ff0000) + (int) (((float) (newPixel & 0x00ff0000)) * weight)) & 0x00ff0000;
+        int g = ((pixel & 0x0000ff00) + (int) (((float) (newPixel & 0x0000ff00)) * weight)) & 0x0000ff00;
+        int b = ((pixel & 0x000000ff) + (int) (((float) (newPixel & 0x000000ff)) * weight)) & 0x000000ff;
+        return 0xff000000 | r | g | b;
+    }
+
+    /**
+     * Apply blur to the input image represented in an array of colors and put the
+     * output image, in the form of an array of colors, into the output array.
+     *
+     * @param src source array of colors
+     * @param out output array of colors after the blur
+     * @param w width of the image
+     * @param h height of the image
+     * @param size size of the Gaussian blur mask
+     */
+    public static void blur(int[] src, int[] out, int w, int h, int size) {
+        float[] k = new float[size];
+        int off = size / 2;
+
+        getGaussianMask(k);
+
+        int[] tmp = new int[src.length];
+
+        // Apply the 1d Gaussian mask horizontally to the image and put the intermediate
+        // results in a temporary array.
+        int rowPointer = 0;
+        for (int y = 0; y < h; y++) {
+            for (int x = 0; x < w; x++) {
+                int sum = 0;
+                for (int i = 0; i < k.length; i++) {
+                    int dx = x + i - off;
+                    dx = clamp(dx, 0,  w - 1);
+                    sum = addPixel(sum, src[rowPointer + dx], k[i]);
+                }
+                tmp[x + rowPointer] = sum;
+            }
+            rowPointer += w;
+        }
+
+        // Apply the 1d Gaussian mask vertically to the intermediate array, and
+        // the final results will be stored in the output array.
+        for (int x = 0; x < w; x++) {
+            rowPointer = 0;
+            for (int y = 0; y < h; y++) {
+                int sum = 0;
+                for (int i = 0; i < k.length; i++) {
+                    int dy = y + i - off;
+                    dy = clamp(dy, 0, h - 1);
+                    sum = addPixel(sum, tmp[dy * w + x], k[i]);
+                }
+                out[x + rowPointer] = sum;
+                rowPointer += w;
+            }
+        }
+    }
+
     private static class ImageFileNamer {
         private final SimpleDateFormat mFormat;
 
@@ -1094,4 +1193,22 @@
         }
         return shutterIcons.getResourceId(modeIndex, 0);
     }
+
+    /**
+     * Gets the parent mode that hosts a specific mode in nav drawer.
+     *
+     * @param modeIndex index of the mode
+     * @param context current context
+     * @return mode id if the index is valid, otherwise 0
+     */
+    public static int getCameraModeParentModeId(int modeIndex, Context context) {
+        // Find the camera mode icon using id
+        int[] cameraModeParent = context.getResources()
+                .getIntArray(R.array.camera_mode_nested_in_nav_drawer);
+        if (modeIndex < 0 || modeIndex >= cameraModeParent.length) {
+            Log.e(TAG, "Invalid mode index: " + modeIndex);
+            return 0;
+        }
+        return cameraModeParent[modeIndex];
+    }
 }