Merge "Make RenderOverlay only respond to tap listener events." into gb-ub-photos-denali
diff --git a/res/layout-land/keyguard_widget.xml b/res/layout-land/keyguard_widget.xml
deleted file mode 100644
index f0f4362..0000000
--- a/res/layout-land/keyguard_widget.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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.
--->
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/camera_controls"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@color/default_background" >
-
-    <ImageView
-        android:id="@+id/shutter_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_centerVertical="true"
-        android:layout_alignParentRight="true"
-        android:layout_marginRight="@dimen/shutter_offset"
-        android:contentDescription="@string/accessibility_shutter_button"
-        android:scaleType="center"
-        android:src="@drawable/btn_new_shutter" />
-
-    <include
-        android:layout_width="64dip"
-        android:layout_height="64dip"
-        android:layout_above="@id/shutter_button"
-        android:layout_alignParentRight="true"
-        android:layout_marginRight="6dip"
-        android:layout_marginTop="-5dip"
-        layout="@layout/menu_indicators_keyguard" />
-
-    <ImageView
-        android:id="@+id/camera_switcher"
-        style="@style/SwitcherButton"
-        android:layout_below="@id/shutter_button"
-        android:layout_alignParentRight="true"
-        android:layout_marginRight="2dip"
-        android:contentDescription="@string/accessibility_mode_picker"
-        android:scaleType="center"
-        android:src="@drawable/ic_switch_camera" />
-
-    <ImageView
-        android:id="@+id/camera_switcher_ind"
-        style="@style/SwitcherButton"
-        android:layout_below="@id/shutter_button"
-        android:layout_alignParentRight="true"
-        android:layout_marginRight="2dip"
-        android:contentDescription="@string/accessibility_mode_picker"
-        android:scaleType="center"
-        android:src="@drawable/ic_switcher_menu_indicator" />
-
-</RelativeLayout>
diff --git a/res/layout-port/keyguard_widget.xml b/res/layout-port/keyguard_widget.xml
deleted file mode 100644
index 5e073f4..0000000
--- a/res/layout-port/keyguard_widget.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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.
--->
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/camera_controls"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@color/default_background" >
-
-    <ImageView
-        android:id="@+id/shutter"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        android:layout_centerHorizontal="true"
-        android:layout_marginBottom="@dimen/shutter_offset"
-        android:src="@drawable/btn_new_shutter" />
-
-    <include layout="@layout/menu_indicators_keyguard"
-        android:layout_width="64dip"
-        android:layout_height="64dip"
-        android:layout_toRightOf="@id/shutter"
-        android:layout_alignParentBottom="true"
-        android:layout_marginBottom="9dip"
-        android:layout_marginRight="-5dip" />
-
-    <ImageView
-        android:id="@+id/camera_switcher"
-        style="@style/SwitcherButton"
-        android:layout_toLeftOf="@id/shutter"
-        android:layout_alignParentBottom="true"
-        android:layout_marginBottom="3dip"
-        android:scaleType="center"
-        android:contentDescription="@string/accessibility_mode_picker"
-        android:src="@drawable/ic_switch_camera" />
-
-    <ImageView
-        android:id="@+id/camera_switcher_ind"
-        style="@style/SwitcherButton"
-        android:layout_toLeftOf="@id/shutter"
-        android:layout_alignParentBottom="true"
-        android:layout_marginBottom="3dip"
-        android:scaleType="center"
-        android:contentDescription="@string/accessibility_mode_picker"
-        android:src="@drawable/ic_switcher_menu_indicator" />
-
-</RelativeLayout>
diff --git a/res/layout/keyguard_widget.xml b/res/layout/keyguard_widget.xml
new file mode 100644
index 0000000..4a6f62d
--- /dev/null
+++ b/res/layout/keyguard_widget.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/camera_mode_color" >
+
+    <ImageView
+        android:layout_gravity="center"
+        android:layout_width="@dimen/mode_transition_view_icon_size"
+        android:layout_height="@dimen/mode_transition_view_icon_size"
+        android:scaleType="fitCenter"
+        android:src="@drawable/ic_camera_normal" />
+
+</FrameLayout>
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 89eb715..84457d0 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -83,8 +83,6 @@
 import com.android.camera.app.PlaceholderManager;
 import com.android.camera.crop.CropActivity;
 import com.android.camera.data.CameraDataAdapter;
-import com.android.camera.data.CameraPreviewData;
-import com.android.camera.data.FixedFirstDataAdapter;
 import com.android.camera.data.FixedLastDataAdapter;
 import com.android.camera.data.InProgressDataWrapper;
 import com.android.camera.data.LocalData;
@@ -93,14 +91,13 @@
 import com.android.camera.data.MediaDetails;
 import com.android.camera.data.SimpleViewData;
 import com.android.camera.filmstrip.FilmstripController;
-import com.android.camera.filmstrip.FilmstripImageData;
 import com.android.camera.filmstrip.FilmstripListener;
 import com.android.camera.module.ModulesInfo;
 import com.android.camera.settings.SettingsManager;
 import com.android.camera.settings.SettingsManager.SettingsCapabilities;
 import com.android.camera.tinyplanet.TinyPlanetFragment;
-import com.android.camera.ui.CameraControls;
 import com.android.camera.ui.DetailsDialog;
+import com.android.camera.ui.FilmstripLayout;
 import com.android.camera.ui.FilmstripView;
 import com.android.camera.ui.MainActivityLayout;
 import com.android.camera.ui.ModeListView;
@@ -147,7 +144,6 @@
 
     private static final int MSG_HIDE_ACTION_BAR = 1;
     private static final int MSG_CLEAR_SCREEN_ON_FLAG = 2;
-    private static final long SHOW_ACTION_BAR_TIMEOUT_MS = 3000;
     private static final long SCREEN_DELAY_MS = 2 * 60 * 1000;  // 2 mins.
     private static final int SHIMMY_DELAY_MS = 1000;
 
@@ -185,6 +181,7 @@
     private ModuleManagerImpl mModuleManager;
     private FrameLayout mAboveFilmstripControlLayout;
     private FilmstripController mFilmstripController;
+    private boolean mFilmstripVisible;
     private ProgressBar mBottomProgress;
     private View mPanoStitchingPanel;
     private int mResultCodeForTesting;
@@ -283,11 +280,6 @@
         CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
     }
 
-    @Override
-    public void onFilmstripHidden() {
-        setSystemBarsVisibility(false);
-    }
-
     private class MainHandler extends Handler {
         public MainHandler(Looper looper) {
             super(looper);
@@ -329,8 +321,32 @@
         return localFile.getName();
     }
 
-    private final FilmstripListener mFilmStripListener =
-            new FilmstripListener() {
+    private final FilmstripLayout.Listener mFilmstripListener =
+            new FilmstripLayout.Listener() {
+
+                @Override
+                public void onFilmstripHidden() {
+                    mFilmstripVisible = false;
+                    CameraActivity.this.setSystemBarsVisibility(false);
+                    // When the user hide the filmstrip (either swipe out or
+                    // tap on back key) we move to the first item so next time
+                    // when the user swipe in the filmstrip, the most recent
+                    // one is shown.
+                    mFilmstripController.goToFirstItem();
+                    if (mCurrentModule != null) {
+                        mCurrentModule.onPreviewVisibilityChanged(true);
+                    }
+                }
+
+                @Override
+                public void onFilmstripShown() {
+                    mFilmstripVisible = true;
+                    updateUiByData(mFilmstripController.getCurrentId());
+                    if (mCurrentModule != null) {
+                        mCurrentModule.onPreviewVisibilityChanged(false);
+                    }
+                }
+
                 @Override
                 public void onDataPromoted(int dataID) {
                     UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
@@ -350,75 +366,48 @@
                 }
 
                 @Override
-                public void onDataFullScreenChange(int dataID, boolean full) {
-                    boolean isCameraID = isCameraPreview(dataID);
-                    if (!isCameraID) {
-                        if (!full) {
-                            // Always show action bar in filmstrip mode
-                            CameraActivity.this.setSystemBarsVisibility(true, false);
-                        } else if (mActionBar.isShowing()) {
-                            // Hide action bar after time out in full screen mode
-                            mMainHandler.sendEmptyMessageDelayed(MSG_HIDE_ACTION_BAR,
-                                    SHOW_ACTION_BAR_TIMEOUT_MS);
-                        }
-                    }
-                }
-
-                /**
-                 * Check if the local data corresponding to dataID is the camera
-                 * preview.
-                 *
-                 * @param dataID the ID of the local data
-                 * @return true if the local data is not null and it is the
-                 *         camera preview.
-                 */
-                private boolean isCameraPreview(int dataID) {
-                    LocalData localData = mDataAdapter.getLocalData(dataID);
-                    if (localData == null) {
-                        Log.w(TAG, "Current data ID not found.");
-                        return false;
-                    }
-                    return localData.getLocalDataType() == LocalData.LOCAL_CAMERA_PREVIEW;
-                }
-
-                @Override
-                public void onDataReloaded() {
-                    setPreviewControlsVisibility(true);
-                    CameraActivity.this.setSystemBarsVisibility(false);
-                }
-
-                @Override
-                public void onCurrentDataCentered(int dataID) {
-                    if (dataID != 0 && !mFilmstripController.isCameraPreview()) {
-                        // For now, We ignore all items that are not the camera preview.
-                        return;
-                    }
-
-                    if (!arePreviewControlsVisible()) {
-                        setPreviewControlsVisibility(true);
+                public void onEnterFullScreen(int dataId) {
+                    if (mFilmstripVisible) {
                         CameraActivity.this.setSystemBarsVisibility(false);
                     }
                 }
 
                 @Override
-                public void onCurrentDataOffCentered(int dataID) {
-                    if (dataID != 0 && !mFilmstripController.isCameraPreview()) {
-                        // For now, We ignore all items that are not the camera preview.
-                        return;
-                    }
+                public void onLeaveFullScreen(int dataId) {
+                    // Do nothing.
+                }
 
-                    if (arePreviewControlsVisible()) {
-                        setPreviewControlsVisibility(false);
+                @Override
+                public void onEnterFilmstrip(int dataId) {
+                    if (mFilmstripVisible) {
+                        CameraActivity.this.setSystemBarsVisibility(true);
                     }
                 }
 
                 @Override
-                public void onDataFocusChanged(final int dataID, final boolean focused) {
-                    // Delay hiding action bar if there is any user interaction
-                    if (mMainHandler.hasMessages(MSG_HIDE_ACTION_BAR)) {
-                        mMainHandler.removeMessages(MSG_HIDE_ACTION_BAR);
-                        mMainHandler.sendEmptyMessageDelayed(MSG_HIDE_ACTION_BAR,
-                                SHOW_ACTION_BAR_TIMEOUT_MS);
+                public void onLeaveFilmstrip(int dataId) {
+                    // Do nothing.
+                }
+
+                @Override
+                public void onDataReloaded() {
+                    if (!mFilmstripVisible) {
+                        return;
+                    }
+                    updateUiByData(mFilmstripController.getCurrentId());
+                }
+
+                @Override
+                public void onEnterZoomView(int dataID) {
+                    if (mFilmstripVisible) {
+                        CameraActivity.this.setSystemBarsVisibility(false);
+                    }
+                }
+
+                @Override
+                public void onDataFocusChanged(final int prevDataId, final int newDataId) {
+                    if (!mFilmstripVisible) {
+                        return;
                     }
                     // TODO: This callback is UI event callback, should always
                     // happen on UI thread. Find the reason for this
@@ -426,68 +415,33 @@
                     runOnUiThread(new Runnable() {
                         @Override
                         public void run() {
-                            LocalData currentData = mDataAdapter.getLocalData(dataID);
-                            if (currentData == null) {
-                                Log.w(TAG, "Current data ID not found.");
-                                hidePanoStitchingProgress();
-                                return;
-                            }
-                            boolean isCameraID = currentData.getLocalDataType() ==
-                                    LocalData.LOCAL_CAMERA_PREVIEW;
-                            if (!focused) {
-                                if (isCameraID) {
-                                    mCurrentModule.onPreviewFocusChanged(false);
-                                    CameraActivity.this.setSystemBarsVisibility(true);
-                                }
-                                hidePanoStitchingProgress();
-                            } else {
-                                if (isCameraID) {
-                                    // Don't show the action bar in Camera
-                                    // preview.
-                                    CameraActivity.this.setSystemBarsVisibility(false);
-
-                                    if (mPendingDeletion) {
-                                        performDeletion();
-                                    }
-                                } else {
-                                    updateActionBarMenu(dataID);
-                                }
-
-                                Uri contentUri = currentData.getContentUri();
-                                if (contentUri == null) {
-                                    hidePanoStitchingProgress();
-                                    return;
-                                }
-                                int panoStitchingProgress = mPanoramaManager.getTaskProgress(contentUri);
-                                if (panoStitchingProgress < 0) {
-                                    hidePanoStitchingProgress();
-                                    return;
-                                }
-                                showPanoStitchingProgress();
-                                updateStitchingProgress(panoStitchingProgress);
-                            }
+                            updateUiByData(newDataId);
                         }
                     });
                 }
 
-                @Override
-                public void onToggleSystemDecorsVisibility(int dataID) {
-                    // If action bar is showing, hide it immediately, otherwise
-                    // show action bar and hide it later
-                    if (mActionBar.isShowing()) {
-                        CameraActivity.this.setSystemBarsVisibility(false);
-                    } else {
-                        // Don't show the action bar if that is the camera preview.
-                        boolean isCameraID = isCameraPreview(dataID);
-                        if (!isCameraID) {
-                            CameraActivity.this.setSystemBarsVisibility(true, true);
-                        }
+                private void updateUiByData(int dataId) {
+                    LocalData currentData = mDataAdapter.getLocalData(dataId);
+                    if (currentData == null) {
+                        Log.w(TAG, "Current data ID not found.");
+                        hidePanoStitchingProgress();
+                        return;
                     }
-                }
+                    updateActionBarMenu(dataId);
 
-                @Override
-                public void setSystemDecorsVisibility(boolean visible) {
-                    CameraActivity.this.setSystemBarsVisibility(visible);
+                    Uri contentUri = currentData.getContentUri();
+                    if (contentUri == null) {
+                        hidePanoStitchingProgress();
+                        return;
+                    }
+                    int panoStitchingProgress =
+                            mPanoramaManager.getTaskProgress(contentUri);
+                    if (panoStitchingProgress < 0) {
+                        hidePanoStitchingProgress();
+                        return;
+                    }
+                    showPanoStitchingProgress();
+                    updateStitchingProgress(panoStitchingProgress);
                 }
             };
 
@@ -499,20 +453,11 @@
     }
 
     /**
-     * If {@param visible} is false, this hides the action bar and switches the system UI
-     * to lights-out mode.
+     * If {@param visible} is false, this hides the action bar and switches the
+     * system UI to lights-out mode.
      */
     // TODO: This should not be called outside of the activity.
     public void setSystemBarsVisibility(boolean visible) {
-        setSystemBarsVisibility(visible, false);
-    }
-
-    /**
-     * If {@param visible} is false, this hides the action bar and switches the
-     * system UI to lights-out mode. If {@param hideLater} is true, a delayed message
-     * will be sent after a timeout to hide the action bar.
-     */
-    private void setSystemBarsVisibility(boolean visible, boolean hideLater) {
         mMainHandler.removeMessages(MSG_HIDE_ACTION_BAR);
 
         int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility();
@@ -533,11 +478,6 @@
                 mOnActionBarVisibilityListener.onActionBarVisibilityChanged(visible);
             }
         }
-
-        // Now delay hiding the bars
-        if (visible && hideLater) {
-            mMainHandler.sendEmptyMessageDelayed(MSG_HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS);
-        }
     }
 
     private void hidePanoStitchingProgress() {
@@ -627,11 +567,8 @@
 
     @Override
     public void onMenuVisibilityChanged(boolean isVisible) {
-        // If menu is showing, we need to make sure action bar does not go away.
-        mMainHandler.removeMessages(MSG_HIDE_ACTION_BAR);
-        if (!isVisible) {
-            mMainHandler.sendEmptyMessageDelayed(MSG_HIDE_ACTION_BAR, SHOW_ACTION_BAR_TIMEOUT_MS);
-        }
+        // TODO: Remove this or bring back the original implementation: cancel
+        // auto-hide actionbar.
     }
 
     @Override
@@ -1136,6 +1073,7 @@
         ModulesInfo.setupModules(this, mModuleManager);
 
         mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
+        mModeListView.init(mModuleManager.getSupportedModeIndexList());
         if (ApiHelper.HAS_ROTATION_ANIMATION) {
             setRotationAnimation();
         }
@@ -1176,7 +1114,7 @@
         mPanoStitchingPanel = findViewById(R.id.pano_stitching_progress_panel);
         mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
         mFilmstripController = ((FilmstripView) findViewById(R.id.filmstrip_view)).getController();
-        mFilmstripController.setViewGap(
+        mFilmstripController.setImageGap(
                 getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap));
         mPanoramaViewHelper = new PanoramaViewHelper(this);
         mPanoramaViewHelper.onCreate();
@@ -1184,7 +1122,9 @@
         // Set up the camera preview first so the preview shows up ASAP.
         mDataAdapter = new CameraDataAdapter(
                 new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
-        mFilmstripController.setListener(mFilmStripListener);
+        ((FilmstripLayout) findViewById(R.id.filmstrip_layout))
+                .setFilmstripListener(mFilmstripListener);
+
 
         mCameraAppUI = new CameraAppUI(this,
                 (MainActivityLayout) findViewById(R.id.activity_root_view),
@@ -1329,8 +1269,6 @@
 
         // Delete photos that are pending deletion
         performDeletion();
-        // TODO: call mCurrentModule.pause() instead after all the modules
-        // support pause().
         mCurrentModule.pause();
         mOrientationManager.pause();
         // Close the camera and wait for the operation done.
@@ -1696,6 +1634,7 @@
     private void openModule(CameraModule module) {
         module.init(this, isSecureCamera(), isCaptureIntent());
         module.resume();
+        module.onPreviewVisibilityChanged(!mFilmstripVisible);
     }
 
     private void closeModule(CameraModule module) {
@@ -1711,7 +1650,6 @@
 
         int currentId = mFilmstripController.getCurrentId();
         updateActionBarMenu(currentId);
-        mFilmStripListener.onCurrentDataCentered(currentId);
     }
 
     public void showUndoDeletionBar() {
@@ -1816,26 +1754,6 @@
         }
     }
 
-
-    /**
-     * Check whether camera controls are visible.
-     *
-     * @return whether controls are visible.
-     */
-    private boolean arePreviewControlsVisible() {
-        return mCurrentModule.arePreviewControlsVisible();
-    }
-
-    /**
-     * Show or hide the {@link CameraControls} using the current module's
-     * implementation of {@link #onPreviewFocusChanged}.
-     *
-     * @param showControls whether to show camera controls.
-     */
-    private void setPreviewControlsVisibility(boolean showControls) {
-        mCurrentModule.onPreviewFocusChanged(showControls);
-    }
-
     // Accessor methods for getting latency times used in performance testing
     public long getAutoFocusTime() {
         return (mCurrentModule instanceof PhotoModule) ?
diff --git a/src/com/android/camera/CameraModule.java b/src/com/android/camera/CameraModule.java
index f89c997..b6be301 100644
--- a/src/com/android/camera/CameraModule.java
+++ b/src/com/android/camera/CameraModule.java
@@ -38,14 +38,16 @@
         mCameraProvider = app.getCameraProvider();
     }
 
-    @Deprecated
-    public abstract void onPreviewFocusChanged(boolean previewFocused);
-
     @Override
     public boolean onBackPressed() {
         return false;
     }
 
+    @Override
+    public void onPreviewVisibilityChanged(boolean visible) {
+        // Do nothing.
+    }
+
     @Deprecated
     public abstract boolean onKeyDown(int keyCode, KeyEvent event);
 
@@ -58,9 +60,6 @@
     @Deprecated
     public abstract void onMediaSaverAvailable(MediaSaver s);
 
-    @Deprecated
-    public abstract boolean arePreviewControlsVisible();
-
     /**
      * @return An instance containing common services to be used by the module.
      */
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 4b610f5..6cf27d2 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -1822,16 +1822,6 @@
         }
     }
 
-    @Override
-    public void onPreviewFocusChanged(boolean previewFocused) {
-        mUI.onPreviewFocusChanged(previewFocused);
-    }
-
-    @Override
-    public boolean arePreviewControlsVisible() {
-        return false;
-    }
-
     // For debugging only.
     public void setDebugUri(Uri uri) {
         mDebugUri = uri;
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 95c6db7..03d67f1 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -1552,6 +1552,7 @@
             if (flashMode == null) {
                 flashMode = mActivity.getString(
                         R.string.pref_camera_flashmode_no_flash);
+                mParameters.setFlashMode(flashMode);
             }
         }
     }
@@ -1573,13 +1574,8 @@
     }
 
     @Override
-    public void onPreviewFocusChanged(boolean previewFocused) {
-        forceFlashOff(!previewFocused);
-    }
-
-    @Override
-    public boolean arePreviewControlsVisible() {
-        return false;
+    public void onPreviewVisibilityChanged(boolean visible) {
+        forceFlashOff(!visible);
     }
 
     private final class JpegPictureCallback implements CameraPictureCallback {
diff --git a/src/com/android/camera/app/AppController.java b/src/com/android/camera/app/AppController.java
index 19af2d3..d2eba9d 100644
--- a/src/com/android/camera/app/AppController.java
+++ b/src/com/android/camera/app/AppController.java
@@ -31,7 +31,7 @@
 /**
  * The controller at app level.
  */
-public interface AppController extends FilmstripLayout.Listener {
+public interface AppController {
 
     /**
      * An interface which defines the shutter events listener.
diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java
index 56ef8b6..136330a 100644
--- a/src/com/android/camera/app/CameraAppUI.java
+++ b/src/com/android/camera/app/CameraAppUI.java
@@ -161,7 +161,6 @@
 
         mAppRootView = appRootView;
         mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout);
-        mFilmstripLayout.setListener(controller);
         mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root);
         mModeTransitionView = (ModeTransitionView)
                 mAppRootView.findViewById(R.id.mode_transition_view);
diff --git a/src/com/android/camera/app/ModuleManager.java b/src/com/android/camera/app/ModuleManager.java
index b134d49..c5824d5 100644
--- a/src/com/android/camera/app/ModuleManager.java
+++ b/src/com/android/camera/app/ModuleManager.java
@@ -82,6 +82,13 @@
     List<ModuleAgent> getRegisteredModuleAgents();
 
     /**
+     * @return A {@link java.util.List} of the
+     * {@link com.android.camera.app.ModuleManager.ModuleAgent} of all the
+     * registered modules' indices.
+     */
+    List<Integer> getSupportedModeIndexList();
+
+    /**
      * Sets the default module index. No-op if the module index does not exist.
      *
      * @param moduleId The ID of the default module.
diff --git a/src/com/android/camera/app/ModuleManagerImpl.java b/src/com/android/camera/app/ModuleManagerImpl.java
index 3521319..666d029 100644
--- a/src/com/android/camera/app/ModuleManagerImpl.java
+++ b/src/com/android/camera/app/ModuleManagerImpl.java
@@ -72,6 +72,15 @@
     }
 
     @Override
+    public List<Integer> getSupportedModeIndexList() {
+        List<Integer> modeIndexList = new ArrayList<Integer>();
+        for (int i = 0; i < mRegisteredModuleAgents.size(); i++) {
+            modeIndexList.add(mRegisteredModuleAgents.keyAt(i));
+        }
+        return modeIndexList;
+    }
+
+    @Override
     public boolean setDefaultModuleIndex(int moduleId) {
         if (mRegisteredModuleAgents.get(moduleId) != null) {
             mDefaultModuleId = moduleId;
diff --git a/src/com/android/camera/filmstrip/FilmstripController.java b/src/com/android/camera/filmstrip/FilmstripController.java
index d34345a..3ed2ed1 100644
--- a/src/com/android/camera/filmstrip/FilmstripController.java
+++ b/src/com/android/camera/filmstrip/FilmstripController.java
@@ -20,11 +20,44 @@
 
 /**
  * An interface which defines the controller of filmstrip.
+ * A filmstrip has 4 states:
+ * <ol>
+ *     <li>Filmstrip</li>
+ *     Images are scaled down and the user can navigate quickly by swiping.
+ *     Action bar and controls are shown.
+ *     <li>Full-screen</li>
+ *     One single image occupies the whole screen. Action bar and controls are
+ *     hidden.
+ *     <li>Zoom view</li>
+ *     Zoom in to view the details of one single image.
+ * </ol>
+ * Only the following state transitions can happen:
+ * <ol>
+ * <li>filmstrip --> full-screen</li>
+ * <li>full-screen --> filmstrip</li>
+ * <li>full-screen --> full-screen with UIs</li>
+ * <li>full-screen --> zoom view</li>
+ * <li>zoom view --> full-screen</li>
+ * </ol>
+ *
+ * Upon entering/leaving each of the states, the
+ * {@link com.android.camera.filmstrip.FilmstripListener} will be notified.
  */
 public interface FilmstripController {
+
+    /**
+     * Sets the listener for filmstrip events.
+     *
+     * @param l
+     */
     public void setListener(FilmstripListener l);
 
-    public void setViewGap(int viewGap);
+    /**
+     * Sets the gap width between each images on the filmstrip.
+     *
+     * @param imageGap The gap width in pixels.
+     */
+    public void setImageGap(int imageGap);
 
     /**
      * Sets the helper that's to be used to open photo sphere panoramas.
@@ -42,28 +75,27 @@
     public void setDataAdapter(FilmstripDataAdapter adapter);
 
     /**
-     * Returns whether the filmstrip is in filmstrip view.
+     * Returns whether the filmstrip is in filmstrip mode.
      */
     public boolean inFilmstrip();
 
     /**
-     * Returns whether the filmstrip is in full-screen view.
+     * @return Whether the filmstrip is in full-screen mode.
      */
     public boolean inFullScreen();
 
     /**
-     * Returns whether the current view in filmstrip is camera preview.
+     * @return Whether the current view in filmstrip is camera preview.
      */
     public boolean isCameraPreview();
 
     /**
-     * Returns whether the filmstrip is in full-screen camrea preview.
+     * @return Whether the filmstrip is in full-screen camrea preview.
      */
     public boolean inCameraFullscreen();
 
     /**
-     * Returns whether the filmstrip is in scaling animation.
-     * @return
+     * @return Whether the filmstrip is in scaling animation.
      */
     public boolean isScaling();
 
@@ -121,7 +153,7 @@
      * Scales down to filmstrip mode. If the current item is camera preview,
      * scrolls to the next item.
      */
-    public void goToFilmStrip();
+    public void goToFilmstrip();
 
     /**
      * Scales up to full-screen mode.
diff --git a/src/com/android/camera/filmstrip/FilmstripListener.java b/src/com/android/camera/filmstrip/FilmstripListener.java
index e4986b9..8a69a09 100644
--- a/src/com/android/camera/filmstrip/FilmstripListener.java
+++ b/src/com/android/camera/filmstrip/FilmstripListener.java
@@ -20,70 +20,70 @@
  * An interface which defines the FilmStripView UI action listener.
  */
 public interface FilmstripListener {
+
     /**
-     * Callback when the data item is promoted. A data is promoted if the user swipe
-     * up a data vertically.
+     * Callback when the data item is promoted. A data is promoted if the user
+     * swipe up a data vertically.
      *
      * @param dataID The ID of the promoted data.
      */
     public void onDataPromoted(int dataID);
 
     /**
-     * Callback when the data item is demoted. A data is promoted if the user swipe
-     * down a data vertically.
+     * Callback when the data item is demoted. A data is promoted if the user
+     * swipe down a data vertically.
      *
      * @param dataID The ID of the demoted data.
      */
     public void onDataDemoted(int dataID);
 
     /**
-     * The callback when the item enters/leaves full-screen. TODO: Call this
-     * function actually.
      *
-     * @param dataID The ID of the image data.
-     * @param fullScreen {@code true} if the data is entering full-screen.
-     *            {@code false} otherwise.
-     */
-    public void onDataFullScreenChange(int dataID, boolean fullScreen);
-
-    /**
      * Called when all the data has been reloaded.
      */
     public void onDataReloaded();
 
     /**
-     * Called when the data item is centered in the film strip.
+     * The callback when the item enters full-screen state.
      *
-     * @param dataID the ID of the local data
+     * @param dataId The ID of the current focused image data.
      */
-    public void onCurrentDataCentered(int dataID);
+    public void onEnterFullScreen(int dataId);
 
     /**
-     * Called when the data item is off centered in the film strip.
+     * The callback when the item leaves full-screen.
      *
-     * @param dataID the ID of the local data
+     * @param dataId The ID of the current focused image data.
      */
-    public void onCurrentDataOffCentered(int dataID);
+    public void onLeaveFullScreen(int dataId);
 
     /**
-     * The callback when the item is centered/off-centered.
+     * The callback when the item enters filmstrip.
      *
-     * @param dataID The ID of the image data.
-     * @param focused {@code true} if the data is focused.
-     *            {@code false} otherwise.
+     * @param dataId The ID of the current focused image data.
      */
-    public void onDataFocusChanged(int dataID, boolean focused);
+    public void onEnterFilmstrip(int dataId);
 
     /**
-     * Toggles the visibility of the ActionBar.
+     * The callback when the item leaves filmstrip.
      *
-     * @param dataID The ID of the image data.
+     * @param dataId The ID of the current focused image data.
      */
-    public void onToggleSystemDecorsVisibility(int dataID);
+    public void onLeaveFilmstrip(int dataId);
 
     /**
-     * Sets the visibility of system decors, including action bar and nav bar
-     * @param visible The visibility of the system decors
+     * The callback when the item enters zoom view.
+     *
+     * @param dataID
      */
-    public void setSystemDecorsVisibility(boolean visible);
+    public void onEnterZoomView(int dataID);
+
+    /**
+     * The callback when the data focus changed.
+     *
+     * @param prevDataId The ID of the previously focused data or {@code -1} if
+     *                   none.
+     * @param newDataId The ID of the focused data of {@code -1} if none.
+     */
+    public void onDataFocusChanged(int prevDataId, int newDataId);
 }
diff --git a/src/com/android/camera/module/ModuleController.java b/src/com/android/camera/module/ModuleController.java
index e32b8d7..ac53b3b 100644
--- a/src/com/android/camera/module/ModuleController.java
+++ b/src/com/android/camera/module/ModuleController.java
@@ -59,6 +59,13 @@
     /********************** UI / Camera preview **********************/
 
     /**
+     * Called when the preview becomes visible/invisible.
+     *
+     * @param visible Whether the preview is visible.
+     */
+    public void onPreviewVisibilityChanged(boolean visible);
+
+    /**
      * Called by the app when the preview size is changed.
      *
      * @param width The new width.
diff --git a/src/com/android/camera/ui/FilmstripLayout.java b/src/com/android/camera/ui/FilmstripLayout.java
index dff1417..e4d168a 100644
--- a/src/com/android/camera/ui/FilmstripLayout.java
+++ b/src/com/android/camera/ui/FilmstripLayout.java
@@ -30,6 +30,8 @@
 import android.view.View;
 import android.widget.FrameLayout;
 
+import com.android.camera.filmstrip.FilmstripController;
+import com.android.camera.filmstrip.FilmstripListener;
 import com.android.camera2.R;
 
 /**
@@ -39,11 +41,23 @@
  */
 public class FilmstripLayout extends FrameLayout {
 
-    // TODO: Remove this quick hack.
-    public interface Listener {
-        void onFilmstripHidden();
-    }
+    /**
+     * An listener interface extending {@link
+     * com.android.camera.filmstrip.FilmstripListener} defining extra callbacks
+     * for filmstrip being shown and hidden.
+     */
+    public interface Listener extends FilmstripListener {
 
+        /**
+         * Callback when the filmstrip becomes invisible or gone.
+         */
+        public void onFilmstripHidden();
+
+        /**
+         * Callback when the filmstrip is shown in full-screen.
+         */
+        public void onFilmstripShown();
+    }
     private static final long DEFAULT_DURATION_MS = 200;
     private static final int ANIM_DIRECTION_IN = 1;
     private static final int ANIM_DIRECTION_OUT = 2;
@@ -51,7 +65,6 @@
     private FilmstripGestureRecognizer mGestureRecognizer;
     private FilmstripGestureRecognizer.Listener mFilmstripGestureListener;
     private final ValueAnimator mFilmstripAnimator = ValueAnimator.ofFloat(null);
-    private Listener mListener;
     private int mSwipeTrend;
     private MyBackgroundDrawable mBackgroundDrawable;
     private int mAnimationDirection;
@@ -69,15 +82,12 @@
         public void onAnimationEnd(Animator animator) {
             if (!mCanceled) {
                 if (mFilmstripView.getTranslationX() != 0f) {
-                    mFilmstripView.getController().goToFilmStrip();
+                    mFilmstripView.getController().goToFilmstrip();
                     setVisibility(INVISIBLE);
-                    // TODO: Remove this quick hack.
-                    if (mListener != null) {
-                        mListener.onFilmstripHidden();
-                    }
                     setHiding(false);
                 } else {
                     setHiding(true);
+                    notifyShown();
                 }
             }
         }
@@ -104,6 +114,7 @@
                     invalidate();
                 }
             };
+    private Listener mListener;
 
     public FilmstripLayout(Context context) {
         super(context);
@@ -127,8 +138,43 @@
         mFilmstripAnimator.addListener(mFilmstripAnimatorListener);
     }
 
-    public void setListener(Listener l) {
-        mListener = l;
+    public void setFilmstripListener(Listener listener) {
+        mListener = listener;
+        if (getVisibility() == VISIBLE && mFilmstripView.getTranslationX() == 0) {
+            notifyShown();
+        } else {
+            notifyHidden(getVisibility());
+        }
+        mFilmstripView.getController().setListener(listener);
+    }
+
+    @Override
+    protected void onVisibilityChanged(View v, int visibility) {
+        super.onVisibilityChanged(v, visibility);
+        notifyHidden(visibility);
+    }
+
+    private void notifyHidden(int visibility) {
+        if (mListener == null) {
+            return;
+        }
+        if (visibility != VISIBLE) {
+            mListener.onFilmstripHidden();
+        }
+    }
+
+    private void notifyShown() {
+        if (mListener == null) {
+            return;
+        }
+        mListener.onFilmstripShown();
+        FilmstripController controller = mFilmstripView.getController();
+        int currentId = controller.getCurrentId();
+        if (controller.inFilmstrip()) {
+            mListener.onEnterFilmstrip(currentId);
+        } else if (controller.inFullScreen()) {
+            mListener.onEnterFullScreen(currentId);
+        }
     }
 
     @Override
diff --git a/src/com/android/camera/ui/FilmstripView.java b/src/com/android/camera/ui/FilmstripView.java
index ebc8395..5b55547 100644
--- a/src/com/android/camera/ui/FilmstripView.java
+++ b/src/com/android/camera/ui/FilmstripView.java
@@ -70,7 +70,7 @@
     private FilmstripGestureRecognizer mGestureRecognizer;
     private FilmstripGestureRecognizer.Listener mGestureListener;
     private FilmstripDataAdapter mDataAdapter;
-    private int mViewGap;
+    private int mViewGapInPixel;
     private final Rect mDrawArea = new Rect();
 
     private final int mCurrentItem = (BUFFER_SIZE - 1) / 2;
@@ -99,6 +99,7 @@
     private float mOverScaleFactor = 1f;
 
     private int mLastTotalNumber = 0;
+    private boolean mHasNoFullScreenUi;
 
     /**
      * A helper class to tract and calculate the view coordination.
@@ -419,7 +420,7 @@
     }
 
     private void setViewGap(int viewGap) {
-        mViewGap = viewGap;
+        mViewGapInPixel = viewGap;
     }
 
     private void setPanoramaViewHelper(PanoramaViewHelper helper) {
@@ -492,11 +493,8 @@
                 filmstripImageData.getHeight(),
                 filmstripImageData.getOrientation(), boundWidth, boundHeight);
 
-        item.getView().measure(
-                MeasureSpec.makeMeasureSpec(
-                        dim[0], MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(
-                        dim[1], MeasureSpec.EXACTLY));
+        item.getView().measure(MeasureSpec.makeMeasureSpec(dim[0], MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(dim[1], MeasureSpec.EXACTLY));
     }
 
     @Override
@@ -607,10 +605,7 @@
             return;
         }
 
-        // Going to change the current item, notify the listener.
-        if (mListener != null) {
-            mListener.onDataFocusChanged(mViewItem[mCurrentItem].getId(), false);
-        }
+        int prevDataId = (mViewItem[mCurrentItem] == null ? -1 : mViewItem[mCurrentItem].getId());
         final int adjust = nearest - mCurrentItem;
         if (adjust > 0) {
             for (int k = 0; k < adjust; k++) {
@@ -642,7 +637,7 @@
         }
         invalidate();
         if (mListener != null) {
-            mListener.onDataFocusChanged(mViewItem[mCurrentItem].getId(), true);
+            mListener.onDataFocusChanged(prevDataId, mViewItem[mCurrentItem].getId());
         }
     }
 
@@ -683,23 +678,6 @@
     }
 
     /**
-     * Checks if the item is centered in the film strip, and calls
-     * {@link #onCurrentDataCentered} or {@link #onCurrentDataOffCentered}.
-     * TODO: refactor.
-     *
-     * @param dataID the ID of the image data.
-     */
-    private void checkCurrentDataCentered(int dataID) {
-        if (mListener != null) {
-            if (isDataAtCenter(dataID)) {
-                mListener.onCurrentDataCentered(dataID);
-            } else {
-                mListener.onCurrentDataOffCentered(dataID);
-            }
-        }
-    }
-
-    /**
      * Reorders the child views to be consistent with their data ID. This method
      * should be called after adding/removing views.
      */
@@ -814,7 +792,7 @@
                     if (mListener != null) {
                         // TODO: Remove this hack since there is no data focus
                         // change actually.
-                        mListener.onDataFocusChanged(requestId, true);
+                        mListener.onDataFocusChanged(requestId, requestId);
                     }
                     mBottomControls.setTinyPlanetButtonVisibility(isPanorama360);
 
@@ -974,7 +952,7 @@
          */
         final float scaleFraction = mViewAnimInterpolator.getInterpolation(
                 (mScale - FILM_STRIP_SCALE) / (FULL_SCREEN_SCALE - FILM_STRIP_SCALE));
-        final int fullScreenWidth = mDrawArea.width() + mViewGap;
+        final int fullScreenWidth = mDrawArea.width() + mViewGapInPixel;
 
         // Decide the position for all view items on the left and the right
         // first.
@@ -988,7 +966,7 @@
 
             // First, layout relatively to the next one.
             final int currLeft = mViewItem[itemID + 1].getLeftPosition()
-                    - curr.getView().getMeasuredWidth() - mViewGap;
+                    - curr.getView().getMeasuredWidth() - mViewGapInPixel;
             curr.setLeftPosition(currLeft);
         }
         // Right items.
@@ -1002,7 +980,7 @@
             final ViewItem prev = mViewItem[itemID - 1];
             final int currLeft =
                     prev.getLeftPosition() + prev.getView().getMeasuredWidth()
-                            + mViewGap;
+                            + mViewGapInPixel;
             curr.setLeftPosition(currLeft);
         }
 
@@ -1204,7 +1182,7 @@
         }
 
         final View removedView = mViewItem[removedItem].getView();
-        final int offsetX = removedView.getMeasuredWidth() + mViewGap;
+        final int offsetX = removedView.getMeasuredWidth() + mViewGapInPixel;
 
         for (int i = removedItem + 1; i < BUFFER_SIZE; i++) {
             if (mViewItem[i] != null) {
@@ -1369,7 +1347,7 @@
         int[] dim = calculateChildDimension(
                 data.getWidth(), data.getHeight(), data.getOrientation(),
                 getMeasuredWidth(), getMeasuredHeight());
-        final int offsetX = dim[0] + mViewGap;
+        final int offsetX = dim[0] + mViewGapInPixel;
         ViewItem viewItem = buildItemFromData(dataID);
 
         if (insertedItem >= mCurrentItem) {
@@ -1431,18 +1409,24 @@
             }
 
             @Override
-            public void onDataInserted(int dataID, FilmstripImageData data) {
+            public void onDataInserted(int dataId, FilmstripImageData data) {
                 if (mViewItem[mCurrentItem] == null) {
                     // empty now, simply do a reload.
                     reload();
-                    return;
+                } else {
+                    updateInsertion(dataId);
                 }
-                updateInsertion(dataID);
+                if (mListener != null) {
+                    mListener.onDataFocusChanged(dataId, getCurrentId());
+                }
             }
 
             @Override
-            public void onDataRemoved(int dataID, FilmstripImageData data) {
-                animateItemRemoval(dataID, data);
+            public void onDataRemoved(int dataId, FilmstripImageData data) {
+                animateItemRemoval(dataId, data);
+                if (mListener != null) {
+                    mListener.onDataFocusChanged(dataId, getCurrentId());
+                }
             }
         });
     }
@@ -1541,7 +1525,6 @@
         mViewItem[itemID] = newItem;
 
         boolean stopScroll = clampCenterX();
-        checkCurrentDataCentered(getCurrentId());
         if (stopScroll) {
             mController.stopScrolling(true);
         }
@@ -1624,16 +1607,10 @@
         mController.stopScrolling(true);
         mController.stopScale();
         mDataIdOnUserScrolling = 0;
-        // Reload has a side effect that after this call, it will show the
-        // camera preview. So we want to know whether it starts from the camera
-        // preview to decide whether we need to call onDataFocusChanged.
-        boolean stayInPreview = false;
 
-        if (mListener != null && mViewItem[mCurrentItem] != null) {
-            stayInPreview = mViewItem[mCurrentItem].getId() == 0;
-            if (!stayInPreview) {
-                mListener.onDataFocusChanged(mViewItem[mCurrentItem].getId(), false);
-            }
+        int prevId = -1;
+        if (mViewItem[mCurrentItem] != null) {
+            prevId = mViewItem[mCurrentItem].getId();
         }
 
         // Remove all views from the mViewItem buffer, except the camera view.
@@ -1680,9 +1657,7 @@
 
         if (mListener != null) {
             mListener.onDataReloaded();
-            if (!stayInPreview) {
-                mListener.onDataFocusChanged(mViewItem[mCurrentItem].getId(), true);
-            }
+            mListener.onDataFocusChanged(prevId, mViewItem[mCurrentItem].getId());
         }
     }
 
@@ -1698,6 +1673,36 @@
         }
     }
 
+    private void onEnterFilmstrip() {
+        if (mListener != null) {
+            mListener.onEnterFilmstrip(getCurrentId());
+        }
+    }
+
+    private void onLeaveFilmstrip() {
+        if (mListener != null) {
+            mListener.onLeaveFilmstrip(getCurrentId());
+        }
+    }
+
+    private void onEnterFullScreen() {
+        if (mListener != null) {
+            mListener.onEnterFullScreen(getCurrentId());
+        }
+    }
+
+    private void onLeaveFullScreen() {
+        if (mListener != null) {
+            mListener.onLeaveFullScreen(getCurrentId());
+        }
+    }
+
+    private void onEnterZoomView() {
+        if (mListener != null) {
+            mListener.onEnterZoomView(getCurrentId());
+        }
+    }
+
     /**
      * MyController controls all the geometry animations. It passively tells the
      * geometry information on demand.
@@ -1718,7 +1723,6 @@
                         mCenterX = currX;
 
                         boolean stopScroll = clampCenterX();
-                        checkCurrentDataCentered(getCurrentId());
                         if (stopScroll) {
                             mController.stopScrolling(true);
                         }
@@ -1763,11 +1767,44 @@
             mScaleAnimator = new ValueAnimator();
             mScaleAnimator.addUpdateListener(mScaleAnimatorUpdateListener);
             mScaleAnimator.setInterpolator(decelerateInterpolator);
+            mScaleAnimator.addListener(new Animator.AnimatorListener() {
+                @Override
+                public void onAnimationStart(Animator animator) {
+                    if (mScale == FULL_SCREEN_SCALE) {
+                        onLeaveFullScreen();
+                    } else {
+                        if (mScale == FILM_STRIP_SCALE) {
+                            onLeaveFilmstrip();
+                        }
+                    }
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animator) {
+                    if (mScale == FULL_SCREEN_SCALE) {
+                        onEnterFullScreen();
+                    } else {
+                        if (mScale == FILM_STRIP_SCALE) {
+                            onEnterFilmstrip();
+                        }
+                    }
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animator) {
+
+                }
+
+                @Override
+                public void onAnimationRepeat(Animator animator) {
+
+                }
+            });
         }
 
         @Override
-        public void setViewGap(int viewGap) {
-            FilmstripView.this.setViewGap(viewGap);
+        public void setImageGap(int imageGap) {
+            FilmstripView.this.setViewGap(imageGap);
         }
 
         @Override
@@ -1821,13 +1858,13 @@
         }
 
         private int estimateMinX(int dataID, int leftPos, int viewWidth) {
-            return leftPos - (dataID + 100) * (viewWidth + mViewGap);
+            return leftPos - (dataID + 100) * (viewWidth + mViewGapInPixel);
         }
 
         private int estimateMaxX(int dataID, int leftPos, int viewWidth) {
             return leftPos
                     + (mDataAdapter.getTotalNumber() - dataID + 100)
-                    * (viewWidth + mViewGap);
+                    * (viewWidth + mViewGapInPixel);
         }
 
         /** Zoom all the way in or out on the image at the given pivot point. */
@@ -1848,7 +1885,7 @@
                 @Override
                 public void onAnimationStart(Animator animation) {
                     if (mScale == FULL_SCREEN_SCALE) {
-                        enterFullScreen();
+                        onEnterFullScreen();
                         setSurroundingViewsVisible(false);
                     }
                     cancelLoadingZoomedImage();
@@ -1868,8 +1905,10 @@
                         setSurroundingViewsVisible(true);
                         mZoomView.setVisibility(GONE);
                         current.resetTransform();
+                        onEnterFullScreen();
                     } else {
                         mController.loadZoomedImage();
+                        onEnterZoomView();
                     }
                     mZoomAnimator = null;
                 }
@@ -1906,7 +1945,6 @@
             mCenterX += deltaX;
 
             boolean stopScroll = clampCenterX();
-            checkCurrentDataCentered(getCurrentId());
             if (stopScroll) {
                 mController.stopScrolling(true);
             }
@@ -1927,7 +1965,7 @@
             if (inFullScreen() && getCurrentViewType() == FilmstripImageData.VIEW_TYPE_STICKY
                     && scaledVelocityX < 0) {
                 // Swipe left in camera preview.
-                goToFilmStrip();
+                goToFilmstrip();
             }
 
             int w = getWidth();
@@ -2050,10 +2088,7 @@
                 return;
             }
             mCanStopScroll = interruptible;
-            mScroller.startScroll(mCenterX, 0, position - mCenterX,
-                    0, duration);
-
-            checkCurrentDataCentered(mViewItem[mCurrentItem].getId());
+            mScroller.startScroll(mCenterX, 0, position - mCenterX, 0, duration);
         }
 
         @Override
@@ -2083,7 +2118,10 @@
         }
 
         @Override
-        public void goToFilmStrip() {
+        public void goToFilmstrip() {
+            if (mScale == FILM_STRIP_SCALE) {
+                return;
+            }
             scaleTo(FILM_STRIP_SCALE, GEOMETRY_ADJUST_TIME_MS);
 
             final ViewItem nextItem = mViewItem[mCurrentItem + 1];
@@ -2094,8 +2132,8 @@
                 scrollToPosition(nextItem.getCenterX(), GEOMETRY_ADJUST_TIME_MS, false);
             }
 
-            if (mListener != null) {
-                mListener.onDataFullScreenChange(mViewItem[mCurrentItem].getId(), false);
+            if (mScale == FILM_STRIP_SCALE) {
+                onLeaveFilmstrip();
             }
         }
 
@@ -2104,8 +2142,10 @@
             if (inFullScreen()) {
                 return;
             }
-            enterFullScreen();
-            scaleTo(1f, GEOMETRY_ADJUST_TIME_MS);
+            scaleTo(FULL_SCREEN_SCALE, GEOMETRY_ADJUST_TIME_MS);
+            if (mScale == FULL_SCREEN_SCALE) {
+                onLeaveFullScreen();
+            }
         }
 
         private void cancelFlingAnimation() {
@@ -2121,12 +2161,6 @@
             }
         }
 
-        private void enterFullScreen() {
-            if (mListener != null) {
-                mListener.onDataFullScreenChange(mViewItem[mCurrentItem].getId(), true);
-            }
-        }
-
         private void setSurroundingViewsVisible(boolean visible) {
             // Hide everything on the left
             // TODO: Need to find a better way to toggle the visibility of views
@@ -2139,12 +2173,6 @@
             }
         }
 
-        private void leaveFullScreen() {
-            if (mListener != null) {
-                mListener.onDataFullScreenChange(mViewItem[mCurrentItem].getId(), false);
-            }
-        }
-
         private Uri getCurrentContentUri() {
             ViewItem curr = mViewItem[mCurrentItem];
             if (curr == null) {
@@ -2207,6 +2235,9 @@
 
         @Override
         public void goToFirstItem() {
+            if (mViewItem[mCurrentItem] == null) {
+                return;
+            }
             resetZoomView();
             // TODO: animate to camera if it is still in the mViewItem buffer
             // versus a full reload which will perform an immediate transition
@@ -2351,11 +2382,7 @@
                     return true;
                 }
             } else if (inFullScreen()) {
-                int dataID = -1;
-                if (centerItem != null) {
-                    dataID = centerItem.getId();
-                }
-                mListener.onToggleSystemDecorsVisibility(dataID);
+                mController.goToFilmstrip();
                 return true;
             }
             return false;
@@ -2376,7 +2403,6 @@
             if (!mController.stopScrolling(false)) {
                 return false;
             }
-            mListener.setSystemDecorsVisibility(false);
             mController.zoomAt(current, x, y);
             return true;
         }
@@ -2440,7 +2466,7 @@
                     && currId == 0
                     && getCurrentViewType() == FilmstripImageData.VIEW_TYPE_STICKY
                     && mDataIdOnUserScrolling == 0) {
-                mController.goToFilmStrip();
+                mController.goToFilmstrip();
                 // Special case to go from camera preview to the next photo.
                 if (mViewItem[mCurrentItem + 1] != null) {
                     mController.scrollToPosition(
@@ -2459,7 +2485,7 @@
                     // Special case to go to filmstrip when the user scroll away
                     // from the camera preview and the current one is not the
                     // preview anymore.
-                    mController.goToFilmStrip();
+                    mController.goToFilmstrip();
                     mDataIdOnUserScrolling = currId;
                 }
                 snapInCenter();
@@ -2597,7 +2623,7 @@
                         mController.scrollToPosition(
                                 nextItem.getCenterX(), GEOMETRY_ADJUST_TIME_MS, true);
                         if (getCurrentViewType() == FilmstripImageData.VIEW_TYPE_STICKY) {
-                            mController.goToFilmStrip();
+                            mController.goToFilmstrip();
                         }
                     }
                 }
@@ -2632,25 +2658,38 @@
             mScaleTrend = mScaleTrend * 0.3f + scale * 0.7f;
             float newScale = mScale * scale;
             if (mScale < FULL_SCREEN_SCALE && newScale < FULL_SCREEN_SCALE) {
+                if (newScale <= FILM_STRIP_SCALE) {
+                    newScale = FILM_STRIP_SCALE;
+                }
                 // Scaled view is smaller than or equal to screen size both
                 // before and after scaling
-                mScale = newScale;
-                if (mScale <= FILM_STRIP_SCALE) {
-                    mScale = FILM_STRIP_SCALE;
+                if (mScale != newScale) {
+                    if (mScale == FILM_STRIP_SCALE) {
+                        onLeaveFilmstrip();
+                    }
+                    if (newScale == FILM_STRIP_SCALE) {
+                        onEnterFilmstrip();
+                    }
                 }
+                mScale = newScale;
                 invalidate();
             } else if (mScale < FULL_SCREEN_SCALE && newScale >= FULL_SCREEN_SCALE) {
                 // Going from smaller than screen size to bigger than or equal
                 // to screen size
+                if (mScale == FILM_STRIP_SCALE) {
+                    onLeaveFilmstrip();
+                }
                 mScale = FULL_SCREEN_SCALE;
-                mController.enterFullScreen();
+                onEnterFullScreen();
                 invalidate();
                 mController.setSurroundingViewsVisible(false);
             } else if (mScale >= FULL_SCREEN_SCALE && newScale < FULL_SCREEN_SCALE) {
                 // Going from bigger than or equal to screen size to smaller
                 // than screen size
+                if (mScale == FULL_SCREEN_SCALE) {
+                    onLeaveFullScreen();
+                }
                 mScale = newScale;
-                mController.leaveFullScreen();
                 invalidate();
                 mController.setSurroundingViewsVisible(true);
             } else {
@@ -2668,6 +2707,9 @@
                 float postScale = newScale / mScale;
                 curr.postScale(focusX, focusY, postScale, mDrawArea.width(), mDrawArea.height());
                 mScale = newScale;
+                if (mScale == FULL_SCREEN_SCALE) {
+                    onEnterFullScreen();
+                }
             }
             return true;
         }
@@ -2679,7 +2721,7 @@
             }
             mController.setSurroundingViewsVisible(true);
             if (mScale <= FILM_STRIP_SCALE + TOLERANCE) {
-                mController.goToFilmStrip();
+                mController.goToFilmstrip();
             } else if (mScaleTrend > 1f || mScale > FULL_SCREEN_SCALE - TOLERANCE) {
                 if (mController.isZoomStarted()) {
                     mScale = FULL_SCREEN_SCALE;
@@ -2687,7 +2729,7 @@
                 }
                 mController.goToFullScreen();
             } else {
-                mController.goToFilmStrip();
+                mController.goToFilmstrip();
             }
             mScaleTrend = 1f;
         }
diff --git a/src/com/android/camera/ui/ModeListView.java b/src/com/android/camera/ui/ModeListView.java
index edc0d17..1266dd1 100644
--- a/src/com/android/camera/ui/ModeListView.java
+++ b/src/com/android/camera/ui/ModeListView.java
@@ -37,6 +37,7 @@
 
 import java.util.ArrayList;
 import java.util.LinkedList;
+import java.util.List;
 
 /**
  * ModeListView class displays all camera modes and settings in the form
@@ -111,6 +112,7 @@
     private float mScrollTrendX = 0f;
     private float mScrollTrendY = 0f;
     private ModeSwitchListener mListener = null;
+    private int[] mSupportedModes;
     private final LinkedList<TimeBasedPosition> mPositionHistory
             = new LinkedList<TimeBasedPosition>();
     private long mCurrentTime;
@@ -221,7 +223,8 @@
             int index = getFocusItem(ev.getX(), ev.getY());
             // Validate the selection
             if (index != NO_ITEM_SELECTED) {
-                onModeSelected(index);
+                int modeId = getModeIndex(index);
+                onModeSelected(modeId);
             }
             return true;
         }
@@ -251,13 +254,47 @@
         mListView.setBackgroundColor(mListBackgroundColor);
     }
 
-    @Override
-    public void onFinishInflate() {
-        // TODO: Total modes will need to be dynamically queried in the beginning of
-        // app startup.
-        mTotalModes = MODE_TOTAL;
-        mModeSelectorItems = new ModeSelectorItem[mTotalModes];
+    /**
+     * Initialize mode list with a list of indices of supported modes.
+     *
+     * @param modeIndexList a list of indices of supported modes
+     */
+    public void init(List<Integer> modeIndexList) {
+        boolean[] modeIsSupported = new boolean[MODE_TOTAL];
+        // Setting should always be supported
+        modeIsSupported[MODE_SETTING] = true;
+        mTotalModes = 1;
 
+        // Mark the supported modes in a boolean array to preserve the
+        // sequence of the modes
+        for (int i = 0; i < modeIndexList.size(); i++) {
+            int mode = modeIndexList.get(i);
+            if (mode >= MODE_TOTAL) {
+                // This is a mode that we don't display in the mode list, skip.
+                continue;
+            }
+            if (modeIsSupported[mode] == false) {
+                modeIsSupported[mode] = true;
+                mTotalModes++;
+            }
+        }
+        // Put the indices of supported modes into an array preserving their
+        // display order.
+        mSupportedModes = new int[mTotalModes];
+        int modeCount = 0;
+        for (int i = 0; i < MODE_TOTAL; i++) {
+            if (modeIsSupported[i]) {
+                mSupportedModes[modeCount] = i;
+                modeCount++;
+            }
+        }
+
+        initializeModeSelectorItems();
+    }
+
+    // TODO: Initialize mode selectors with different sizes based on number of modes supported
+    private void initializeModeSelectorItems() {
+        mModeSelectorItems = new ModeSelectorItem[mTotalModes];
         // Inflate the mode selector items and add them to a linear layout
         LayoutInflater inflater = (LayoutInflater) getContext()
                 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -274,13 +311,15 @@
                 selectorItem.setBackgroundColor(getResources()
                         .getColor(R.color.mode_selector_background_dark));
             }
-            selectorItem.setIconBackgroundColor(getResources().getColor(mIconBlockColor[i]));
+            int modeId = getModeIndex(i);
+            selectorItem.setIconBackgroundColor(getResources()
+                    .getColor(mIconBlockColor[modeId]));
 
             // Set image
-            selectorItem.setImageResource(mIconResId[i]);
+            selectorItem.setImageResource(mIconResId[modeId]);
 
             // Set text
-            CharSequence text = getResources().getText(mTextResId[i]);
+            CharSequence text = getResources().getText(mTextResId[modeId]);
             selectorItem.setText(text);
             mModeSelectorItems[i] = selectorItem;
         }
@@ -288,6 +327,21 @@
         resetModeSelectors();
     }
 
+    /**
+     * Maps between the UI mode selector index to the actual mode id.
+     *
+     * @param modeSelectorIndex the index of the UI item
+     * @return the index of the corresponding camera mode
+     */
+    private int getModeIndex(int modeSelectorIndex) {
+        if (modeSelectorIndex < mTotalModes && modeSelectorIndex >= 0) {
+            return mSupportedModes[modeSelectorIndex];
+        }
+        Log.e(TAG, "Invalid mode selector index: " + modeSelectorIndex + ", total modes: "
+                + mTotalModes);
+        return MODE_PHOTO;
+    }
+
     /** Notify ModeSwitchListener, if any, of the mode change. */
     private void onModeSelected(int modeIndex) {
         if (mListener != null) {
@@ -370,7 +424,7 @@
             height = height / ROWS_TO_SHOW_IN_LANDSCAPE;
             setVerticalScrollBarEnabled(true);
         } else {
-            height = height / MODE_TOTAL;
+            height = height / mTotalModes;
             setVerticalScrollBarEnabled(false);
         }
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(mWidth, 0);