resolved conflicts for merge of e903ae3f to ub-camera-everglades

Change-Id: Ic0ea3fdfd5bad7ea3a576c3bc1ab7fddd7b557bc
diff --git a/Android.mk b/Android.mk
index 8779081..4f18789 100644
--- a/Android.mk
+++ b/Android.mk
@@ -6,6 +6,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13
 LOCAL_STATIC_JAVA_LIBRARIES += xmp_toolkit
+LOCAL_STATIC_JAVA_LIBRARIES += glide
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SRC_FILES += $(call all-java-files-under, src_pd)
diff --git a/res/layout/camera.xml b/res/layout/camera.xml
index 9639d73..4d3d097 100644
--- a/res/layout/camera.xml
+++ b/res/layout/camera.xml
@@ -66,4 +66,26 @@
     </FrameLayout>
 
     <include layout="@layout/mode_list_layout" />
+
+    <LinearLayout
+        android:id="@+id/accessibility_affordances"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:layout_gravity="top|left"
+        android:visibility="gone">
+        <Button
+            android:id="@+id/accessibility_mode_toggle_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/btn_mode_list_toggle"
+            android:contentDescription="@string/accessibility_mode_list_toggle"/>
+        <Button
+            android:id="@+id/accessibility_filmstrip_toggle_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/btn_filmstrip_toggle"
+            android:contentDescription="@string/accessibility_filmstrip_toggle"/>/>
+    </LinearLayout>
+
 </FrameLayout>
diff --git a/res/layout/filmstrip_video.xml b/res/layout/filmstrip_video.xml
new file mode 100644
index 0000000..8439b61
--- /dev/null
+++ b/res/layout/filmstrip_video.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ImageView
+        android:id="@+id/video_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_gravity="center" />
+    <ImageView
+        android:id="@+id/play_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:contentDescription="@string/video_control_play"
+        android:src="@drawable/ic_control_play" />
+</FrameLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1b54aef..da55831 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -527,8 +527,6 @@
     <string name="accessibility_mode_list_hidden">Mode list closed</string>
     <!-- Announcement when the mode list becomes shown on screen [CHAR LIMIT=NONE] -->
     <string name="accessibility_mode_list_shown">Mode list open</string>
-    <!-- Announcement when the mode switcher, on startup, is temporarily on screen as visual hint, then moves off left [CHAR LIMIT=NONE] -->
-    <string name="accessibility_mode_list_shimmy">Press left to toggle mode list</string>
     <!-- Announcement when a capture is performed, and visually the capture 'peeks' from right screen [CHAR LIMIT=NONE] -->
     <string name="accessibility_peek">Capture taken</string>
     <!-- Description for the lock icon in Secure Camera filmstrip that when clicked, brings user to lock screen then full Camera [CHAR LIMIT=NONE] -->
@@ -539,6 +537,18 @@
     <!-- Content description for media items in filmstrip which are still in the middle of processing [CHAR LIMIT=NONE] -->
     <string name="media_processing_content_description">Media processing</string>
 
+    <!-- Content description of the button that toggles the on screen list of camera mode. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_mode_list_toggle">Toggle mode list</string>
+
+    <!-- Text on the button that toggles the on screen list of camera mode. [CHAR LIMIT=20] -->
+    <string name="btn_mode_list_toggle">Mode list</string>
+
+    <!-- Content description of the button that toggles the filmstrip. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_filmstrip_toggle">Toggle filmstrip</string>
+
+    <!-- Text on the button that toggles the filmstrip. [CHAR LIMIT=20] -->
+    <string name="btn_filmstrip_toggle">Filmstrip</string>
+
     <!-- Default text for a button that can be toggled on and off. -->
     <string name="capital_on">ON</string>
     <!-- Default text for a button that can be toggled on and off. -->
diff --git a/res/drawable-hdpi/ic_video_disabled.png b/res_p/drawable-hdpi/ic_video_disabled.png
similarity index 100%
rename from res/drawable-hdpi/ic_video_disabled.png
rename to res_p/drawable-hdpi/ic_video_disabled.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_video_disabled.png b/res_p/drawable-mdpi/ic_video_disabled.png
similarity index 100%
rename from res/drawable-mdpi/ic_video_disabled.png
rename to res_p/drawable-mdpi/ic_video_disabled.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_video_disabled.png b/res_p/drawable-xhdpi/ic_video_disabled.png
similarity index 100%
rename from res/drawable-xhdpi/ic_video_disabled.png
rename to res_p/drawable-xhdpi/ic_video_disabled.png
Binary files differ
diff --git a/res/drawable-xxhdpi/ic_video_disabled.png b/res_p/drawable-xxhdpi/ic_video_disabled.png
similarity index 100%
rename from res/drawable-xxhdpi/ic_video_disabled.png
rename to res_p/drawable-xxhdpi/ic_video_disabled.png
Binary files differ
diff --git a/src/com/android/camera/ButtonManager.java b/src/com/android/camera/ButtonManager.java
index c467f5a..cd48928 100644
--- a/src/com/android/camera/ButtonManager.java
+++ b/src/com/android/camera/ButtonManager.java
@@ -74,6 +74,10 @@
     private ImageButton mExposureP1;
     private ImageButton mExposureP2;
 
+    private int mMinExposureCompensation;
+    private int mMaxExposureCompensation;
+    private float mExposureCompensationStep;
+
     /** A listener for button enabled and visibility
         state changes. */
     private ButtonStatusListener mListener;
@@ -486,8 +490,12 @@
                         case R.id.exposure_p2:
                             comp = 2;
                     }
-                    // Each integer compensation represent 1/6 of a stop.
-                    cb.setExposure(comp * 6);
+
+                    if (mExposureCompensationStep != 0.0f) {
+                        int compValue =
+                            Math.round(comp / mExposureCompensationStep);
+                        cb.setExposure(compValue);
+                    }
                 }
             };
 
@@ -500,6 +508,25 @@
     }
 
     /**
+     * Set the exposure compensation parameters supported by the current camera mode.
+     * @param min Minimum exposure compensation value.
+     * @param max Maximum exposure compensation value.
+     * @param step Expsoure compensation step value.
+     */
+    public void setExposureCompensationParameters(int min, int max, float step) {
+        mMaxExposureCompensation = max;
+        mMinExposureCompensation = min;
+        mExposureCompensationStep = step;
+
+        mExposureN2.setEnabled(Math.round(min * step) <= -2);
+        mExposureN1.setEnabled(Math.round(min * step) <= -1);
+        mExposureP1.setEnabled(Math.round(max * step) >= 1);
+        mExposureP1.setEnabled(Math.round(max * step) >= 2);
+
+        updateExposureButtons();
+    }
+
+    /**
      * Check if a button is enabled with the given button id..
      */
     public boolean isEnabled(int buttonId) {
@@ -670,7 +697,7 @@
      */
     public void updateExposureButtons() {
         String compString = mSettingsManager.get(SettingsManager.SETTING_EXPOSURE_COMPENSATION_VALUE);
-        int comp = Integer.parseInt(compString);
+        int compValue = Integer.parseInt(compString);
 
         // Reset all button states.
         mExposureN2.setBackground(null);
@@ -684,22 +711,24 @@
         Drawable background = context.getResources()
             .getDrawable(R.drawable.button_background_selected_photo);
 
-        // Each integer compensation represent 1/6 of a stop.
-        switch (comp / 6) {
-            case -2:
-                mExposureN2.setBackground(background);
-                break;
-            case -1:
-                mExposureN1.setBackground(background);
-                break;
-            case 0:
-                mExposure0.setBackground(background);
-                break;
-            case 1:
-                mExposureP1.setBackground(background);
-                break;
-            case 2:
-                mExposureP2.setBackground(background);
+        if (mExposureCompensationStep != 0.0f) {
+            int comp = Math.round(compValue * mExposureCompensationStep);
+            switch (comp) {
+                case -2:
+                    mExposureN2.setBackground(background);
+                    break;
+                case -1:
+                    mExposureN1.setBackground(background);
+                    break;
+                case 0:
+                    mExposure0.setBackground(background);
+                    break;
+                case 1:
+                    mExposureP1.setBackground(background);
+                    break;
+                case 2:
+                    mExposureP2.setBackground(background);
+            }
         }
     }
 
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index bd27f75..95ec0a4 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -36,7 +36,6 @@
 import android.graphics.Matrix;
 import android.graphics.Point;
 import android.graphics.SurfaceTexture;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.hardware.Camera;
 import android.net.Uri;
@@ -72,8 +71,8 @@
 import com.android.camera.app.AppController;
 import com.android.camera.app.CameraAppUI;
 import com.android.camera.app.CameraController;
-import com.android.camera.app.CameraManager;
-import com.android.camera.app.CameraManagerFactory;
+import com.android.camera.cameradevice.CameraManager;
+import com.android.camera.cameradevice.CameraManagerFactory;
 import com.android.camera.app.CameraProvider;
 import com.android.camera.app.CameraServices;
 import com.android.camera.app.LocationManager;
@@ -83,6 +82,7 @@
 import com.android.camera.app.OrientationManager;
 import com.android.camera.app.OrientationManagerImpl;
 import com.android.camera.data.CameraDataAdapter;
+import com.android.camera.data.LocalDataViewType;
 import com.android.camera.data.FixedLastDataAdapter;
 import com.android.camera.data.LocalData;
 import com.android.camera.data.LocalDataAdapter;
@@ -128,6 +128,8 @@
 import com.android.camera.widget.FilmstripView;
 import com.android.camera.widget.Preloader;
 import com.android.camera2.R;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.resize.ImageManager;
 import com.google.common.logging.eventprotos;
 import com.google.common.logging.eventprotos.ForegroundEvent.ForegroundSource;
 import com.google.common.logging.eventprotos.MediaInteraction;
@@ -140,6 +142,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.Executors;
 
 public class CameraActivity extends Activity
         implements AppController, CameraManager.CameraOpenCallback,
@@ -1157,6 +1160,13 @@
         CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
 
         super.onCreate(state);
+        final Glide glide = Glide.get();
+        if (!glide.isImageManagerSet()) {
+            // We load exclusively large images, so we want fewer threads to minimize jank.
+            glide.setImageManager(new ImageManager.Builder(getApplicationContext())
+                    .setResizeService(Executors.newSingleThreadExecutor()));
+        }
+
         mOnCreateTime = System.currentTimeMillis();
         mAppContext = getApplicationContext();
         mSettingsManager = new SettingsManager(mAppContext, this);
@@ -1238,8 +1248,7 @@
         mPanoramaViewHelper = new PanoramaViewHelper(this);
         mPanoramaViewHelper.onCreate();
         // Set up the camera preview first so the preview shows up ASAP.
-        mDataAdapter = new CameraDataAdapter(mAppContext,
-                new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
+        mDataAdapter = new CameraDataAdapter(mAppContext, R.color.photo_placeholder);
         mDataAdapter.setLocalDataListener(mLocalDataListener);
 
         mPreloader = new Preloader<Integer, AsyncTask>(FILMSTRIP_PRELOAD_AHEAD_ITEMS, mDataAdapter,
@@ -1289,6 +1298,7 @@
                     mDataAdapter,
                     new SimpleViewData(
                             v,
+                            LocalDataViewType.SECURE_ALBUM_PLACEHOLDER,
                             v.getDrawable().getIntrinsicWidth(),
                             v.getDrawable().getIntrinsicHeight(),
                             0, 0));
@@ -1366,19 +1376,11 @@
         }
 
         int visibility = getPreviewVisibility();
+        mCameraAppUI.onPreviewVisiblityChanged(visibility);
         updatePreviewRendering(visibility);
-        updateCaptureControls(visibility);
         mCurrentModule.onPreviewVisibilityChanged(visibility);
     }
 
-    private void updateCaptureControls(int visibility) {
-        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
-            mCameraAppUI.setIndicatorBottomBarWrapperVisible(false);
-        } else {
-            mCameraAppUI.setIndicatorBottomBarWrapperVisible(true);
-        }
-    }
-
     private void updatePreviewRendering(int visibility) {
         if (visibility == ModuleController.VISIBILITY_HIDDEN) {
             mCameraAppUI.pausePreviewRendering();
diff --git a/src/com/android/camera/CameraErrorCallback.java b/src/com/android/camera/CameraErrorCallback.java
index af855d1..187a351 100644
--- a/src/com/android/camera/CameraErrorCallback.java
+++ b/src/com/android/camera/CameraErrorCallback.java
@@ -16,7 +16,7 @@
 
 package com.android.camera;
 
-import com.android.camera.app.CameraManager;
+import com.android.camera.cameradevice.CameraManager;
 import com.android.camera.debug.Log;
 
 public class CameraErrorCallback
diff --git a/src/com/android/camera/CameraModule.java b/src/com/android/camera/CameraModule.java
index 41be023..db23952 100644
--- a/src/com/android/camera/CameraModule.java
+++ b/src/com/android/camera/CameraModule.java
@@ -72,7 +72,7 @@
      * This calls {@link
      * com.android.camera.app.CameraProvider#requestCamera(int)}. The camera
      * will be returned through {@link
-     * #onCameraAvailable(com.android.camera.app.CameraManager.CameraProxy)}
+     * #onCameraAvailable(com.android.camera.cameradevice.CameraManager.CameraProxy)}
      * when it's available. This is a no-op when there's no back camera
      * available.
      */
@@ -83,8 +83,7 @@
         }
     }
 
-    @Override
-    public void onPreviewInitialDataReceived() {};
+    public void onPreviewInitialDataReceived() {}
 
     /**
      * Releases the back camera through {@link CameraProvider}.
diff --git a/src/com/android/camera/CameraTestDevice.java b/src/com/android/camera/CameraTestDevice.java
index a31580f..ab9a114 100644
--- a/src/com/android/camera/CameraTestDevice.java
+++ b/src/com/android/camera/CameraTestDevice.java
@@ -18,7 +18,7 @@
 
 import android.hardware.Camera.CameraInfo;
 
-import com.android.camera.app.CameraManager.CameraProxy;
+import com.android.camera.cameradevice.CameraManager.CameraProxy;
 
 /**
  * The class is kept to make sure the tests can build.
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 28ed27d..876a39b 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -25,7 +25,6 @@
 import android.graphics.SurfaceTexture;
 import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Parameters;
-import android.hardware.Camera.Size;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
@@ -33,6 +32,7 @@
 import android.location.Location;
 import android.media.CameraProfile;
 import android.net.Uri;
+import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -48,15 +48,15 @@
 import com.android.camera.PhotoModule.NamedImages.NamedEntity;
 import com.android.camera.app.AppController;
 import com.android.camera.app.CameraAppUI;
-import com.android.camera.app.CameraManager.CameraAFCallback;
-import com.android.camera.app.CameraManager.CameraAFMoveCallback;
-import com.android.camera.app.CameraManager.CameraPictureCallback;
-import com.android.camera.app.CameraManager.CameraProxy;
-import com.android.camera.app.CameraManager.CameraShutterCallback;
 import com.android.camera.app.LocationManager;
 import com.android.camera.app.MediaSaver;
 import com.android.camera.app.MemoryManager;
 import com.android.camera.app.MemoryManager.MemoryListener;
+import com.android.camera.cameradevice.CameraManager.CameraAFCallback;
+import com.android.camera.cameradevice.CameraManager.CameraAFMoveCallback;
+import com.android.camera.cameradevice.CameraManager.CameraPictureCallback;
+import com.android.camera.cameradevice.CameraManager.CameraProxy;
+import com.android.camera.cameradevice.CameraManager.CameraShutterCallback;
 import com.android.camera.debug.Log;
 import com.android.camera.exif.ExifInterface;
 import com.android.camera.exif.ExifTag;
@@ -64,12 +64,14 @@
 import com.android.camera.hardware.HardwareSpec;
 import com.android.camera.hardware.HardwareSpecImpl;
 import com.android.camera.module.ModuleController;
+import com.android.camera.remote.RemoteCameraModule;
 import com.android.camera.settings.SettingsManager;
 import com.android.camera.settings.SettingsUtil;
 import com.android.camera.util.ApiHelper;
 import com.android.camera.util.CameraUtil;
 import com.android.camera.util.GcamHelper;
 import com.android.camera.util.SessionStatsCollector;
+import com.android.camera.util.Size;
 import com.android.camera.util.UsageStatistics;
 import com.android.camera2.R;
 import com.google.common.logging.eventprotos;
@@ -90,7 +92,8 @@
         MemoryListener,
         FocusOverlayManager.Listener,
         SensorEventListener,
-        SettingsManager.OnSettingChangedListener {
+        SettingsManager.OnSettingChangedListener,
+        RemoteCameraModule {
 
     private static final Log.Tag TAG = new Log.Tag("PhotoModule");
 
@@ -113,9 +116,6 @@
 
     private static final String DEBUG_IMAGE_PREFIX = "DEBUG_";
 
-    private static final boolean NO_STOP_PREVIEW_ICS_WORKAROUND =
-            shouldNotCallStopPreviewAfterTakingPicture();
-
     private CameraActivity mActivity;
     private CameraProxy mCameraDevice;
     private int mCameraId;
@@ -379,10 +379,6 @@
         locationFirstRun();
     }
 
-    @Override
-    public void onPreviewInitialDataReceived() {
-    }
-
     // Prompt the user to pick to record location for the very first run of
     // camera only
     private void locationFirstRun() {
@@ -555,6 +551,9 @@
                 setExposureCompensation(value);
             }
         };
+        bottomBarSpec.minExposureCompensation = mParameters.getMinExposureCompensation();
+        bottomBarSpec.maxExposureCompensation = mParameters.getMaxExposureCompensation();
+        bottomBarSpec.exposureCompensationStep = mParameters.getExposureCompensationStep();
 
         if (isImageCaptureIntent()) {
             bottomBarSpec.showCancel = true;
@@ -771,14 +770,14 @@
 
             if (!mIsImageCaptureIntent) {
                 // Calculate the width and the height of the jpeg.
-                Size s = mParameters.getPictureSize();
+                Size s = new Size(mParameters.getPictureSize());
                 int width, height;
                 if ((mJpegRotation + orientation) % 180 == 0) {
-                    width = s.width;
-                    height = s.height;
+                    width = s.width();
+                    height = s.height();
                 } else {
-                    width = s.height;
-                    height = s.width;
+                    width = s.height();
+                    height = s.width();
                 }
                 NamedEntity name = mNamedImages.getNextNameEntity();
                 String title = (name == null) ? null : name.title;
@@ -828,6 +827,15 @@
                 }
             }
 
+            // Send the taken photo to remote shutter listeners, if any are
+            // registered.
+            AsyncTask.SERIAL_EXECUTOR.execute(new Runnable() {
+                @Override
+                public void run() {
+                    getServices().getRemoteShutterListener().onPictureTaken(jpegData);
+                }
+            });
+
             // Check this in advance of each shot so we don't add to shutter
             // latency. It's true that someone else could write to the SD card
             // in the mean time and fill it, but that could have happened
@@ -931,6 +939,7 @@
             return false;
         }
         mCaptureStartTime = System.currentTimeMillis();
+
         mPostViewPictureCallbackTime = 0;
         mJpegImageData = null;
 
@@ -1263,11 +1272,13 @@
             Log.v(TAG, "On resume.");
             onResumeTasks();
         }
+        getServices().getRemoteShutterListener().onModuleReady(this);
         SessionStatsCollector.instance().sessionActive(true);
     }
 
     @Override
     public void pause() {
+        getServices().getRemoteShutterListener().onModuleExit();
         mPaused = true;
         SessionStatsCollector.instance().sessionActive(false);
 
@@ -1500,16 +1511,6 @@
         }
 
         mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
-        // ICS camera frameworks has a bug. Face detection state is not cleared
-        // after taking a picture. Stop the preview to work around it. The bug
-        // was fixed in JB.
-        // TODO: Remove all of this for 'E'.
-        if (!NO_STOP_PREVIEW_ICS_WORKAROUND) {
-            if (mCameraState != PREVIEW_STOPPED) {
-                stopPreview();
-            }
-        }
-
         setDisplayOrientation();
 
         if (!mSnapshotOnIdle) {
@@ -1658,19 +1659,19 @@
                 .get(isCameraFrontFacing() ? SettingsManager.SETTING_PICTURE_SIZE_FRONT
                         : SettingsManager.SETTING_PICTURE_SIZE_BACK);
 
-        List<Size> supported = mParameters.getSupportedPictureSizes();
+        List<Size> supported = Size.buildListFromCameraSizes(mParameters.getSupportedPictureSizes());
         SettingsUtil.setCameraPictureSize(pictureSize, supported, mParameters,
                 mCameraDevice.getCameraId());
-        Size size = mParameters.getPictureSize();
+        Size size = new Size(mParameters.getPictureSize());
 
         // Set a preview size that is closest to the viewfinder height and has
         // the right aspect ratio.
-        List<Size> sizes = mParameters.getSupportedPreviewSizes();
+        List<Size> sizes = Size.buildListFromCameraSizes(mParameters.getSupportedPreviewSizes());
         Size optimalSize = CameraUtil.getOptimalPreviewSize(mActivity, sizes,
-                (double) size.width / size.height);
-        Size original = mParameters.getPreviewSize();
+                (double) size.width() / size.height());
+        Size original = new Size(mParameters.getPreviewSize());
         if (!original.equals(optimalSize)) {
-            mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
+            mParameters.setPreviewSize(optimalSize.width(), optimalSize.height());
 
             // Zoom related settings will be changed for different preview
             // sizes, so set and read the parameters to get latest values
@@ -1683,11 +1684,11 @@
             mParameters = mCameraDevice.getParameters();
         }
 
-        if (optimalSize.width != 0 && optimalSize.height != 0) {
-            mUI.updatePreviewAspectRatio((float) optimalSize.width
-                    / (float) optimalSize.height);
+        if (optimalSize.width() != 0 && optimalSize.height() != 0) {
+            mUI.updatePreviewAspectRatio((float) optimalSize.width()
+                    / (float) optimalSize.height());
         }
-        Log.i(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
+        Log.i(TAG, "Preview size is " + optimalSize);
     }
 
     private void updateParametersPictureQuality() {
@@ -1941,20 +1942,8 @@
         }
     }
 
-    /**
-     * Depending on the device, this returns whether we should avoid using the
-     * ICS-only workaround to call stopPreview before startPreview
-     * <p>
-     * The proper solution is to remove the stopPreview call completely, but as
-     * we have only limited time for testing left, let's be careful and target
-     * specific devices only.
-     * <p>
-     * Context: http://b/13966525
-     */
-    private static boolean shouldNotCallStopPreviewAfterTakingPicture() {
-        // The M8 is the only known device with a really long stopPreview
-        // duration.
-        return Build.MANUFACTURER.toLowerCase().contains("htc") &&
-                Build.DEVICE.toLowerCase().contains("m8");
+    @Override
+    public void onRemoteShutterPress() {
+        capture();
     }
 }
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index bdff1f2..1de6d04 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -29,7 +29,7 @@
 import android.view.ViewGroup;
 
 import com.android.camera.FocusOverlayManager.FocusUI;
-import com.android.camera.app.CameraManager;
+import com.android.camera.cameradevice.CameraManager;
 import com.android.camera.debug.Log;
 import com.android.camera.ui.FaceView;
 import com.android.camera.ui.PreviewOverlay;
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 70cc12e..bee5863 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -30,7 +30,6 @@
 import android.graphics.SurfaceTexture;
 import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Parameters;
-import android.hardware.Camera.Size;
 import android.location.Location;
 import android.media.AudioManager;
 import android.media.CamcorderProfile;
@@ -53,13 +52,13 @@
 
 import com.android.camera.app.AppController;
 import com.android.camera.app.CameraAppUI;
-import com.android.camera.app.CameraManager;
-import com.android.camera.app.CameraManager.CameraPictureCallback;
-import com.android.camera.app.CameraManager.CameraProxy;
 import com.android.camera.app.LocationManager;
 import com.android.camera.app.MediaSaver;
 import com.android.camera.app.MemoryManager;
 import com.android.camera.app.MemoryManager.MemoryListener;
+import com.android.camera.cameradevice.CameraManager;
+import com.android.camera.cameradevice.CameraManager.CameraPictureCallback;
+import com.android.camera.cameradevice.CameraManager.CameraProxy;
 import com.android.camera.debug.Log;
 import com.android.camera.exif.ExifInterface;
 import com.android.camera.hardware.HardwareSpec;
@@ -69,6 +68,7 @@
 import com.android.camera.settings.SettingsUtil;
 import com.android.camera.util.ApiHelper;
 import com.android.camera.util.CameraUtil;
+import com.android.camera.util.Size;
 import com.android.camera.util.UsageStatistics;
 import com.android.camera2.R;
 import com.google.common.logging.eventprotos;
@@ -323,6 +323,9 @@
         // TODO: Need to look at the controller interface to see if we can get
         // rid of passing in the activity directly.
         mAppController = mActivity;
+
+        mActivity.updateStorageSpaceAndHint();
+
         mUI = new VideoUI(mActivity, this,  mActivity.getModuleLayoutRoot());
         mActivity.setPreviewStatusListener(mUI);
 
@@ -765,26 +768,25 @@
 
         final int previewScreenShortSide = (previewScreenSize.x < previewScreenSize.y ?
                 previewScreenSize.x : previewScreenSize.y);
-        List<Size> sizes = parameters.getSupportedPreviewSizes();
-        Size preferred = parameters.getPreferredPreviewSizeForVideo();
-        final int preferredPreviewSizeShortSide = (preferred.width < preferred.height ?
-                preferred.width : preferred.height);
+        List<Size> sizes = Size.buildListFromCameraSizes(parameters.getSupportedPreviewSizes());
+        Size preferred = new Size(parameters.getPreferredPreviewSizeForVideo());
+        final int preferredPreviewSizeShortSide = (preferred.width() < preferred.height() ?
+                preferred.width() : preferred.height());
         if (preferredPreviewSizeShortSide * 2 < previewScreenShortSide) {
-            preferred.width = profile.videoFrameWidth;
-            preferred.height = profile.videoFrameHeight;
+            preferred = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
         }
-        int product = preferred.width * preferred.height;
+        int product = preferred.width() * preferred.height();
         Iterator<Size> it = sizes.iterator();
         // Remove the preview sizes that are not preferred.
         while (it.hasNext()) {
             Size size = it.next();
-            if (size.width * size.height > product) {
+            if (size.width() * size.height() > product) {
                 it.remove();
             }
         }
         Size optimalSize = CameraUtil.getOptimalPreviewSize(context, sizes,
                 (double) profile.videoFrameWidth / profile.videoFrameHeight);
-        return new Point(optimalSize.width, optimalSize.height);
+        return new Point(optimalSize.width(), optimalSize.height());
     }
 
     private void resizeForPreviewAspectRatio() {
@@ -901,6 +903,10 @@
     }
 
     @Override
+    public void onPreviewInitialDataReceived() {
+    }
+
+    @Override
     public void stopPreview() {
         if (!mPreviewing) {
             return;
@@ -1544,15 +1550,15 @@
         // The logic here is different from the logic in still-mode camera.
         // There we determine the preview size based on the picture size, but
         // here we determine the picture size based on the preview size.
-        List<Size> supported = mParameters.getSupportedPictureSizes();
+        List<Size> supported =
+                Size.buildListFromCameraSizes(mParameters.getSupportedPictureSizes());
         Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
                 (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
-        Size original = mParameters.getPictureSize();
+        Size original = new Size(mParameters.getPictureSize());
         if (!original.equals(optimalSize)) {
-            mParameters.setPictureSize(optimalSize.width, optimalSize.height);
+            mParameters.setPictureSize(optimalSize.width(), optimalSize.height());
         }
-        Log.d(TAG, "Video snapshot size is " + optimalSize.width + "x" +
-                optimalSize.height);
+        Log.d(TAG, "Video snapshot size is " + optimalSize);
 
         // Set JPEG quality.
         int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
diff --git a/src/com/android/camera/app/CameraApp.java b/src/com/android/camera/app/CameraApp.java
index 713a586..7c1a9c6 100644
--- a/src/com/android/camera/app/CameraApp.java
+++ b/src/com/android/camera/app/CameraApp.java
@@ -23,12 +23,14 @@
 import com.android.camera.MediaSaverImpl;
 import com.android.camera.debug.LogHelper;
 import com.android.camera.processing.ProcessingServiceManager;
+import com.android.camera.remote.RemoteShutterListener;
 import com.android.camera.session.CaptureSessionManager;
 import com.android.camera.session.CaptureSessionManagerImpl;
 import com.android.camera.session.PlaceholderManager;
 import com.android.camera.session.SessionStorageManager;
 import com.android.camera.session.SessionStorageManagerImpl;
 import com.android.camera.util.CameraUtil;
+import com.android.camera.util.RemoteShutterHelper;
 import com.android.camera.util.SessionStatsCollector;
 import com.android.camera.util.UsageStatistics;
 
@@ -42,6 +44,7 @@
     private SessionStorageManager mSessionStorageManager;
     private MemoryManagerImpl mMemoryManager;
     private PlaceholderManager mPlaceHolderManager;
+    private RemoteShutterListener mRemoteShutterListener;
 
     @Override
     public void onCreate() {
@@ -62,6 +65,7 @@
         mSessionManager = new CaptureSessionManagerImpl(mMediaSaver, getContentResolver(),
                 mPlaceHolderManager, mSessionStorageManager);
         mMemoryManager = MemoryManagerImpl.create(getApplicationContext(), mMediaSaver);
+        mRemoteShutterListener = RemoteShutterHelper.create(this);
 
         clearNotifications();
     }
@@ -82,6 +86,11 @@
         return mMediaSaver;
     }
 
+    @Override
+    public RemoteShutterListener getRemoteShutterListener() {
+        return mRemoteShutterListener;
+    }
+
     /**
      * Clears all notifications. This cleans up notifications that we might have
      * created earlier but remained after a crash.
diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java
index 2717278..77e1db5 100644
--- a/src/com/android/camera/app/CameraAppUI.java
+++ b/src/com/android/camera/app/CameraAppUI.java
@@ -23,6 +23,7 @@
 import android.graphics.Matrix;
 import android.graphics.RectF;
 import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
 import android.hardware.display.DisplayManager;
 import android.util.CameraPerformanceTracker;
 import android.view.GestureDetector;
@@ -32,6 +33,7 @@
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.FrameLayout;
 
 import com.android.camera.AnimationManager;
@@ -429,9 +431,16 @@
          * when an expsosure button is pressed. This callback can be null.
          */
         public interface ExposureCompensationSetCallback {
-            public abstract void setExposure(int value);
+            public void setExposure(int value);
         }
         public ExposureCompensationSetCallback exposureCompensationSetCallback;
+
+        /**
+         * Exposure compensation parameters.
+         */
+        public int minExposureCompensation;
+        public int maxExposureCompensation;
+        public float exposureCompensationStep;
     }
 
 
@@ -506,6 +515,8 @@
     private View mModeOptionsToggle;
     private final PeekView mPeekView;
     private final CaptureLayoutHelper mCaptureLayoutHelper;
+    private boolean mAccessibilityEnabled;
+    private View mAccessiblityAffordances;
 
     /**
      * Provides current preview frame and the controls/overlay from the module that
@@ -689,6 +700,22 @@
         mPeekView = (PeekView) appRootView.findViewById(R.id.peek_view);
         mAppRootView.setNonDecorWindowSizeChangedListener(mCaptureLayoutHelper);
         initDisplayListener();
+        mAccessiblityAffordances = mAppRootView.findViewById(R.id.accessibility_affordances);
+        View modeListToggle = mAppRootView.findViewById(R.id.accessibility_mode_toggle_button);
+        modeListToggle.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mModeListView.onMenuPressed();
+            }
+        });
+        View filmstripToggle = mAppRootView.findViewById(
+                R.id.accessibility_filmstrip_toggle_button);
+        filmstripToggle.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mFilmstripLayout.showFilmstrip();
+            }
+        });
     }
 
     /**
@@ -855,6 +882,12 @@
         // Hide action bar first since we are in full screen mode first, and
         // switch the system UI to lights-out mode.
         mFilmstripPanel.hide();
+
+        AccessibilityManager accessibilityManager = (AccessibilityManager) mController
+                .getAndroidContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        mAccessibilityEnabled = accessibilityManager.isEnabled();
+        mAccessiblityAffordances.setVisibility(mAccessibilityEnabled ? View.VISIBLE : View.GONE);
+
     }
 
     /**
@@ -895,6 +928,21 @@
         }
     }
 
+
+    public void onPreviewVisiblityChanged(int visibility) {
+        if (visibility == ModuleController.VISIBILITY_HIDDEN) {
+            setIndicatorBottomBarWrapperVisible(false);
+            mAccessiblityAffordances.setVisibility(View.GONE);
+        } else {
+            setIndicatorBottomBarWrapperVisible(true);
+            if (mAccessibilityEnabled) {
+                mAccessiblityAffordances.setVisibility(View.VISIBLE);
+            } else {
+                mAccessiblityAffordances.setVisibility(View.GONE);
+            }
+        }
+    }
+
     /**
      * Call to stop the preview from being rendered.
      */
@@ -1654,6 +1702,11 @@
             buttonManager.setExposureCompensationCallback(null);
         }
 
+        buttonManager.setExposureCompensationParameters(
+                bottomBarSpec.minExposureCompensation,
+                bottomBarSpec.maxExposureCompensation,
+                bottomBarSpec.exposureCompensationStep);
+
         /** Intent UI */
         if (bottomBarSpec.showCancel) {
             buttonManager.initializePushButton(ButtonManager.BUTTON_CANCEL,
diff --git a/src/com/android/camera/app/CameraController.java b/src/com/android/camera/app/CameraController.java
index aa046ff..e8f6c5f 100644
--- a/src/com/android/camera/app/CameraController.java
+++ b/src/com/android/camera/app/CameraController.java
@@ -21,7 +21,8 @@
 import android.os.Handler;
 
 import com.android.camera.CameraDisabledException;
-import com.android.camera.app.CameraManager.CameraExceptionCallback;
+import com.android.camera.cameradevice.CameraManager;
+import com.android.camera.cameradevice.CameraManager.CameraExceptionCallback;
 import com.android.camera.debug.Log;
 import com.android.camera.util.CameraUtil;
 
diff --git a/src/com/android/camera/app/CameraProvider.java b/src/com/android/camera/app/CameraProvider.java
index 54e26ef..34bc676 100644
--- a/src/com/android/camera/app/CameraProvider.java
+++ b/src/com/android/camera/app/CameraProvider.java
@@ -19,7 +19,7 @@
 import android.hardware.Camera;
 import android.os.Handler;
 
-import com.android.camera.app.CameraManager.CameraExceptionCallback;
+import com.android.camera.cameradevice.CameraManager.CameraExceptionCallback;
 
 /**
  * An interface which defines the camera provider.
diff --git a/src/com/android/camera/app/CameraServices.java b/src/com/android/camera/app/CameraServices.java
index 2c0216e..b6c8ec7 100644
--- a/src/com/android/camera/app/CameraServices.java
+++ b/src/com/android/camera/app/CameraServices.java
@@ -16,6 +16,7 @@
 
 package com.android.camera.app;
 
+import com.android.camera.remote.RemoteShutterListener;
 import com.android.camera.session.CaptureSessionManager;
 
 /**
@@ -43,4 +44,10 @@
      */
     @Deprecated
     public MediaSaver getMediaSaver();
+
+    /**
+     * @return A listener to be informed by events interesting for remote
+     *         capture apps. Will never return null.
+     */
+    public RemoteShutterListener getRemoteShutterListener();
 }
diff --git a/src/com/android/camera/app/AndroidCameraManagerImpl.java b/src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java
similarity index 98%
rename from src/com/android/camera/app/AndroidCameraManagerImpl.java
rename to src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java
index 5400395..f6bdebc 100644
--- a/src/com/android/camera/app/AndroidCameraManagerImpl.java
+++ b/src/com/android/camera/cameradevice/AndroidCameraManagerImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.camera.app;
+package com.android.camera.cameradevice;
 
 import android.annotation.TargetApi;
 import android.graphics.SurfaceTexture;
@@ -304,7 +304,11 @@
         @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
         private void setAutoFocusMoveCallback(
                 android.hardware.Camera camera, Object cb) {
-            camera.setAutoFocusMoveCallback((AutoFocusMoveCallback) cb);
+            try {
+                camera.setAutoFocusMoveCallback((AutoFocusMoveCallback) cb);
+            } catch (RuntimeException ex) {
+                Log.w(TAG, ex.getMessage());
+            }
         }
 
         private void capture(final CaptureCallbacks cb) {
@@ -575,7 +579,7 @@
 
     private class DispatchThread extends Thread {
 
-        private Queue<Runnable> mJobQueue;
+        private final Queue<Runnable> mJobQueue;
         private Boolean mIsEnded;
 
         public DispatchThread() {
@@ -1120,7 +1124,7 @@
 
     private static class WaitDoneBundle {
         public Runnable mUnlockRunnable;
-        private Object mWaitLock;
+        private final Object mWaitLock;
 
         WaitDoneBundle() {
             mWaitLock = new Object();
@@ -1154,7 +1158,9 @@
          */
         public static AFCallbackForward getNewInstance(
                 Handler handler, CameraProxy camera, CameraAFCallback cb) {
-            if (handler == null || camera == null || cb == null) return null;
+            if (handler == null || camera == null || cb == null) {
+                return null;
+            }
             return new AFCallbackForward(handler, camera, cb);
         }
 
@@ -1195,7 +1201,9 @@
          */
         public static ErrorCallbackForward getNewInstance(
                 Handler handler, CameraProxy camera, CameraErrorCallback cb) {
-            if (handler == null || camera == null || cb == null) return null;
+            if (handler == null || camera == null || cb == null) {
+                return null;
+            }
             return new ErrorCallbackForward(handler, camera, cb);
         }
 
@@ -1235,7 +1243,9 @@
          */
         public static AFMoveCallbackForward getNewInstance(
                 Handler handler, CameraProxy camera, CameraAFMoveCallback cb) {
-            if (handler == null || camera == null || cb == null) return null;
+            if (handler == null || camera == null || cb == null) {
+                return null;
+            }
             return new AFMoveCallbackForward(handler, camera, cb);
         }
 
@@ -1277,7 +1287,9 @@
          */
         public static ShutterCallbackForward getNewInstance(
                 Handler handler, CameraProxy camera, CameraShutterCallback cb) {
-            if (handler == null || camera == null || cb == null) return null;
+            if (handler == null || camera == null || cb == null) {
+                return null;
+            }
             return new ShutterCallbackForward(handler, camera, cb);
         }
 
@@ -1318,7 +1330,9 @@
          */
         public static PictureCallbackForward getNewInstance(
                 Handler handler, CameraProxy camera, CameraPictureCallback cb) {
-            if (handler == null || camera == null || cb == null) return null;
+            if (handler == null || camera == null || cb == null) {
+                return null;
+            }
             return new PictureCallbackForward(handler, camera, cb);
         }
 
@@ -1360,7 +1374,9 @@
          */
         public static PreviewCallbackForward getNewInstance(
                 Handler handler, CameraProxy camera, CameraPreviewDataCallback cb) {
-            if (handler == null || camera == null || cb == null) return null;
+            if (handler == null || camera == null || cb == null) {
+                return null;
+            }
             return new PreviewCallbackForward(handler, camera, cb);
         }
 
@@ -1399,7 +1415,9 @@
          */
         public static FaceDetectionCallbackForward getNewInstance(
                 Handler handler, CameraProxy camera, CameraFaceDetectionCallback cb) {
-            if (handler == null || camera == null || cb == null) return null;
+            if (handler == null || camera == null || cb == null) {
+                return null;
+            }
             return new FaceDetectionCallbackForward(handler, camera, cb);
         }
 
diff --git a/src/com/android/camera/app/CameraManager.java b/src/com/android/camera/cameradevice/CameraManager.java
similarity index 98%
rename from src/com/android/camera/app/CameraManager.java
rename to src/com/android/camera/cameradevice/CameraManager.java
index c8c060a..f6c87db 100644
--- a/src/com/android/camera/app/CameraManager.java
+++ b/src/com/android/camera/cameradevice/CameraManager.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.camera.app;
+package com.android.camera.cameradevice;
 
 import android.annotation.TargetApi;
 import android.graphics.SurfaceTexture;
@@ -162,7 +162,7 @@
      * Opens the camera of the specified ID asynchronously. The camera device
      * will be opened in the camera handler thread and will be returned through
      * the {@link CameraManager.CameraOpenCallback#
-     * onCameraOpened(com.android.camera.app.CameraManager.CameraProxy)}.
+     * onCameraOpened(com.android.camera.cameradevice.CameraManager.CameraProxy)}.
      *
      * @param handler The {@link android.os.Handler} in which the callback
      *                was handled.
@@ -207,7 +207,7 @@
         /**
          * Reconnects to the camera device. On success, the camera device will
          * be returned through {@link CameraManager
-         * .CameraOpenCallback#onCameraOpened(com.android.camera.app.CameraManager
+         * .CameraOpenCallback#onCameraOpened(com.android.camera.cameradevice.CameraManager
          * .CameraProxy)}.
          * @see android.hardware.Camera#reconnect()
          *
diff --git a/src/com/android/camera/app/CameraManagerFactory.java b/src/com/android/camera/cameradevice/CameraManagerFactory.java
similarity index 90%
rename from src/com/android/camera/app/CameraManagerFactory.java
rename to src/com/android/camera/cameradevice/CameraManagerFactory.java
index e08ef78..5f1c39d 100644
--- a/src/com/android/camera/app/CameraManagerFactory.java
+++ b/src/com/android/camera/cameradevice/CameraManagerFactory.java
@@ -14,10 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.camera.app;
-
-import com.android.camera.app.AndroidCameraManagerImpl;
-import com.android.camera.app.CameraManager;
+package com.android.camera.cameradevice;
 
 /**
  * A factory class for {@link CameraManager}.
@@ -28,7 +25,7 @@
     private static int sAndoridCameraManagerClientCount;
 
     /**
-     * Returns the android camera implementation of {@link com.android.camera.app.CameraManager}.
+     * Returns the android camera implementation of {@link com.android.camera.cameradevice.CameraManager}.
      *
      * @return The {@link CameraManager} to control the camera device.
      */
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
index 1bbeaf7..7411735 100644
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -18,7 +18,6 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
-import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.view.View;
@@ -46,7 +45,7 @@
 
     private Listener mListener;
     private LocalDataListener mLocalDataListener;
-    private final Drawable mPlaceHolder;
+    private final int mPlaceHolderResourceId;
 
     private int mSuggestedWidth = DEFAULT_DECODE_SIZE;
     private int mSuggestedHeight = DEFAULT_DECODE_SIZE;
@@ -54,10 +53,10 @@
 
     private LocalData mLocalDataToDelete;
 
-    public CameraDataAdapter(Context context, Drawable placeHolder) {
+    public CameraDataAdapter(Context context, int placeholderResource) {
         mContext = context;
         mImages = new LocalDataList();
-        mPlaceHolder = placeHolder;
+        mPlaceHolderResourceId = placeholderResource;
     }
 
     @Override
@@ -93,6 +92,15 @@
     }
 
     @Override
+    public int getItemViewType(int dataId) {
+        if (dataId > mImages.size() || dataId < 0) {
+            return -1;
+        }
+
+        return mImages.get(dataId).getItemViewType().ordinal();
+    }
+
+    @Override
     public LocalData getLocalData(int dataID) {
         if (dataID < 0 || dataID >= mImages.size()) {
             return null;
@@ -117,14 +125,14 @@
     }
 
     @Override
-    public View getView(Context context, int dataID) {
+    public View getView(Context context, View recycled, int dataID) {
         if (dataID >= mImages.size() || dataID < 0) {
             return null;
         }
 
         return mImages.get(dataID).getView(
-                context, mSuggestedWidth, mSuggestedHeight,
-                mPlaceHolder.getConstantState().newDrawable(), this, /* inProgress */ false);
+                context, recycled, mSuggestedWidth, mSuggestedHeight,
+                mPlaceHolderResourceId, this, /* inProgress */ false);
     }
 
     @Override
@@ -132,7 +140,7 @@
         if (dataID >= mImages.size() || dataID < 0) {
             return;
         }
-        mImages.get(dataID).resizeView(context, w, h, view, this);
+        mImages.get(dataID).loadFullImage(context, w, h, view, this);
     }
 
     @Override
diff --git a/src/com/android/camera/data/CameraPreviewData.java b/src/com/android/camera/data/CameraPreviewData.java
index 350ee8d..efbbe35 100644
--- a/src/com/android/camera/data/CameraPreviewData.java
+++ b/src/com/android/camera/data/CameraPreviewData.java
@@ -35,7 +35,7 @@
      * @param height The height of the camera preview.
      */
     public CameraPreviewData(View v, int width, int height) {
-        super(v, width, height, -1, -1);
+        super(v, LocalDataViewType.CAMERA_PREVIEW, width, height, -1, -1);
         mPreviewLocked = true;
     }
 
diff --git a/src/com/android/camera/data/FixedFirstDataAdapter.java b/src/com/android/camera/data/FixedFirstDataAdapter.java
index f5e8f06..471fd19 100644
--- a/src/com/android/camera/data/FixedFirstDataAdapter.java
+++ b/src/com/android/camera/data/FixedFirstDataAdapter.java
@@ -110,12 +110,20 @@
     }
 
     @Override
-    public View getView(Context context, int dataID) {
+    public View getView(Context context, View recycled, int dataID) {
         if (dataID == 0) {
             return mFirstData.getView(
-                    context, mSuggestedWidth, mSuggestedHeight, null, null, false);
+                    context, recycled, mSuggestedWidth, mSuggestedHeight, 0, null, false);
         }
-        return mAdapter.getView(context, dataID - 1);
+        return mAdapter.getView(context, recycled, dataID - 1);
+    }
+
+    @Override
+    public int getItemViewType(int dataId) {
+        if (dataId == 0) {
+            return mFirstData.getItemViewType().ordinal();
+        }
+        return mAdapter.getItemViewType(dataId);
     }
 
     @Override
diff --git a/src/com/android/camera/data/FixedLastDataAdapter.java b/src/com/android/camera/data/FixedLastDataAdapter.java
index c94c1b4..35978d8 100644
--- a/src/com/android/camera/data/FixedLastDataAdapter.java
+++ b/src/com/android/camera/data/FixedLastDataAdapter.java
@@ -112,20 +112,33 @@
     }
 
     @Override
-    public View getView(Context context, int dataID) {
+    public View getView(Context context, View recycled, int dataID) {
         int totalNumber = mAdapter.getTotalNumber();
 
         if (dataID < totalNumber) {
-            return mAdapter.getView(context, dataID);
+            return mAdapter.getView(context, recycled, dataID);
         } else if (dataID == totalNumber) {
-            return mLastData.getView(context,
-                    mSuggestedWidth, mSuggestedHeight, null, null, false);
+            return mLastData.getView(context, recycled,
+                    mSuggestedWidth, mSuggestedHeight, 0, null, false);
         }
 
         return null;
     }
 
     @Override
+    public int getItemViewType(int dataId) {
+        int totalNumber = mAdapter.getTotalNumber();
+
+        if (dataId < totalNumber) {
+            return mAdapter.getItemViewType(dataId);
+        } else if (dataId == totalNumber) {
+            return mLastData.getItemViewType().ordinal();
+        }
+
+        return -1;
+   }
+
+    @Override
     public void resizeView(Context context, int dataID, View view, int w, int h) {
         // Do nothing.
     }
diff --git a/src/com/android/camera/data/ImageModelLoader.java b/src/com/android/camera/data/ImageModelLoader.java
new file mode 100644
index 0000000..9eea4d3
--- /dev/null
+++ b/src/com/android/camera/data/ImageModelLoader.java
@@ -0,0 +1,31 @@
+package com.android.camera.data;
+
+import android.content.Context;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.loader.bitmap.model.ModelLoader;
+import com.bumptech.glide.loader.bitmap.model.stream.StreamModelLoader;
+import com.bumptech.glide.loader.bitmap.resource.ResourceFetcher;
+
+import java.io.InputStream;
+
+/**
+ * Translates a local data representing an image into an InputStream that can be loaded by the
+ * Glide library.
+ */
+public class ImageModelLoader implements StreamModelLoader<LocalData> {
+    private final ModelLoader<String, InputStream> mPathLoader;
+
+    public ImageModelLoader(Context context) {
+        mPathLoader = Glide.buildStreamModelLoader(String.class, context);
+    }
+
+    @Override
+    public ResourceFetcher<InputStream> getResourceFetcher(LocalData model, int width, int height) {
+        return mPathLoader.getResourceFetcher(model.getPath(), width, height);
+    }
+
+    @Override
+    public String getId(LocalData model) {
+        return mPathLoader.getId(model.getPath()) + model.getSignature();
+    }
+}
diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java
index 36c602e..eb3a092 100644
--- a/src/com/android/camera/data/LocalData.java
+++ b/src/com/android/camera/data/LocalData.java
@@ -80,9 +80,16 @@
      * @param height Height in pixels of rendered view.
      * @param adapter Data adapter for this data item.
      */
-    View getView(Context context, int width, int height, Drawable placeHolder,
+    View getView(Context context, View recycled, int width, int height, int placeHolderResourceId,
             LocalDataAdapter adapter, boolean isInProgress);
 
+    /** Returns a unique identifier for the view created by this data so that the view
+     * can be reused.
+     *
+     * @see android.widget.BaseAdapter#getItemViewType(int)
+     */
+    LocalDataViewType getItemViewType();
+
    /**
      * Request resize of View created by getView().
      *
@@ -92,7 +99,7 @@
      * @param view View created by getView();
      * @param adapter Data adapter for this data item.
      */
-    public void resizeView(Context context, int width, int height, View view, LocalDataAdapter adapter);
+    public void loadFullImage(Context context, int width, int height, View view, LocalDataAdapter adapter);
 
     /**
      * Gets the date when this data is created. The returned date is also used
@@ -126,19 +133,6 @@
     /** Removes the data from the storage if possible. */
     boolean delete(Context c);
 
-    /**
-     * Rotate the image in 90 degrees. This is a no-op for non-image.
-     *
-     * @param context Used to update the content provider when rotation is done.
-     * @param adapter Used to update the view.
-     * @param currentDataId Used to update the view.
-     * @param clockwise True if the rotation goes clockwise.
-     *
-     * @return Whether the rotation is supported.
-     */
-    boolean rotate90Degrees(Context context, LocalDataAdapter adapter,
-            int currentDataId, boolean clockwise);
-
     void onFullScreen(boolean fullScreen);
 
     /** Returns {@code true} if it allows swipe to filmstrip in full screen. */
@@ -197,6 +191,15 @@
     Bundle getMetadata();
 
     /**
+     * Any media store attribute that can potentially change the local data
+     * should be included in this signature, primarily oriented at detecting
+     * edits.
+     *
+     * @return A string identifying the set of changeable attributes.
+     */
+    String getSignature();
+
+    /**
      * @return whether the metadata is updated.
      */
     public boolean isMetadataUpdated();
diff --git a/src/com/android/camera/data/LocalDataViewType.java b/src/com/android/camera/data/LocalDataViewType.java
new file mode 100644
index 0000000..39a0697
--- /dev/null
+++ b/src/com/android/camera/data/LocalDataViewType.java
@@ -0,0 +1,13 @@
+package com.android.camera.data;
+
+/**
+ * The set of all unique identifiers for all different views that may be shown
+ * in the Filmstrip.
+ */
+public enum LocalDataViewType {
+    CAMERA_PREVIEW,
+    PHOTO,
+    VIDEO,
+    SESSION,
+    SECURE_ALBUM_PLACEHOLDER,
+}
diff --git a/src/com/android/camera/data/LocalMediaData.java b/src/com/android/camera/data/LocalMediaData.java
index f0b696c..2dc7d98 100644
--- a/src/com/android/camera/data/LocalMediaData.java
+++ b/src/com/android/camera/data/LocalMediaData.java
@@ -18,36 +18,29 @@
 
 import android.app.Activity;
 import android.content.ContentResolver;
-import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.graphics.Point;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.media.CamcorderProfile;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.provider.MediaStore;
-import android.provider.MediaStore.Images;
+import android.view.LayoutInflater;
 import android.view.Gravity;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
 import android.widget.ImageView;
 
 import com.android.camera.Storage;
 import com.android.camera.debug.Log;
 import com.android.camera.util.CameraUtil;
 import com.android.camera2.R;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.presenter.target.ImageViewTarget;
+import com.bumptech.glide.presenter.target.Target;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Date;
@@ -91,11 +84,11 @@
             int width, int height, long sizeInBytes, double latitude,
             double longitude) {
         mContentId = contentId;
-        mTitle = new String(title);
-        mMimeType = new String(mimeType);
+        mTitle = title;
+        mMimeType = mimeType;
         mDateTakenInSeconds = dateTakenInSeconds;
         mDateModifiedInSeconds = dateModifiedInSeconds;
-        mPath = new String(path);
+        mPath = path;
         mWidth = width;
         mHeight = height;
         mSizeInBytes = sizeInBytes;
@@ -150,7 +143,7 @@
 
     @Override
     public String getTitle() {
-        return new String(mTitle);
+        return mTitle;
     }
 
     @Override
@@ -205,34 +198,45 @@
     }
 
     protected ImageView fillImageView(Context context, ImageView v,
-            int decodeWidth, int decodeHeight, Drawable placeHolder,
+            int decodeWidth, int decodeHeight, int placeHolderResourceId,
             LocalDataAdapter adapter, boolean isInProgress) {
-        v.setScaleType(ImageView.ScaleType.FIT_XY);
-        if (placeHolder != null) {
-            v.setImageDrawable(placeHolder);
+
+        SizedImageViewTarget target = (SizedImageViewTarget) v.getTag();
+        if (target != null) {
+            target = new SizedImageViewTarget(v);
+            v.setTag(target);
         }
 
-        // TODO: Load MediaStore or embedded-in-JPEG-stream thumbnail.
+        Glide.with(context)
+                .load(mPath)
+                .fitCenter()
+                .placeholder(placeHolderResourceId)
+                .into(target);
 
-        BitmapLoadTask task = getBitmapLoadTask(context, v, decodeWidth, decodeHeight,
-                context.getContentResolver(), adapter);
-        task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
         v.setContentDescription(context.getResources().getString(
                 R.string.media_date_content_description,
                 getReadableDate(mDateModifiedInSeconds)));
+
         return v;
     }
 
     @Override
-    public View getView(Context context, int decodeWidth, int decodeHeight, Drawable placeHolder,
-            LocalDataAdapter adapter, boolean isInProgress) {
-        return fillImageView(context, new ImageView(context), decodeWidth, decodeHeight,
-                placeHolder, adapter, isInProgress);
+    public View getView(Context context, View recycled, int decodeWidth, int decodeHeight,
+            int placeHolderResourceId, LocalDataAdapter adapter, boolean isInProgress) {
+        final ImageView imageView;
+        if (recycled != null) {
+            imageView = (ImageView) recycled;
+        } else {
+            imageView = new ImageView(context);
+        }
+
+        return fillImageView(context, imageView, decodeWidth, decodeHeight,
+                placeHolderResourceId, adapter, isInProgress);
     }
 
     @Override
-    public void resizeView(Context context, int width, int height, View view,
-                           LocalDataAdapter adapter) {
+    public void loadFullImage(Context context, int width, int height, View view,
+            LocalDataAdapter adapter) {
         // Default is do nothing.
         // Can be implemented by sub-classes.
     }
@@ -245,7 +249,7 @@
     }
 
     @Override
-    public void recycle() {
+    public void recycle(View view) {
         synchronized (mUsing) {
             mUsing = false;
         }
@@ -310,15 +314,6 @@
         return MetadataLoader.isMetadataCached(this);
     }
 
-    /**
-     * A background task that loads the provided ImageView with a Bitmap.
-     * A Bitmap of maximum size that fits into a decodeWidth x decodeHeight
-     * box will be decoded.
-     */
-    protected abstract BitmapLoadTask getBitmapLoadTask(
-            Context context, ImageView v, int decodeWidth, int decodeHeight,
-            ContentResolver resolver, LocalDataAdapter adapter);
-
     public static final class PhotoData extends LocalMediaData {
         private static final Log.Tag TAG = new Log.Tag("PhotoData");
 
@@ -337,8 +332,6 @@
 
         // GL max texture size: keep bitmaps below this value.
         private static final int MAXIMUM_TEXTURE_SIZE = 2048;
-        // Maximum pixel count for Bitmaps.  To limit RAM consumption.
-        private static final int MAXIMUM_DECODE_PIXELS = 4000000;
 
         static final Uri CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
 
@@ -368,6 +361,8 @@
 
         /** from MediaStore, can only be 0, 90, 180, 270 */
         private final int mOrientation;
+        /** @see #getSignature() */
+        private final String mSignature;
 
         public static LocalData fromContentUri(ContentResolver cr, Uri contentUri) {
             List<LocalData> newPhotos = query(cr, contentUri, QUERY_ALL_MEDIA_ID);
@@ -384,7 +379,7 @@
             super(id, title, mimeType, dateTakenInSeconds, dateModifiedInSeconds,
                     path, width, height, sizeInBytes, latitude, longitude);
             mOrientation = orientation;
-
+            mSignature = mimeType + orientation + dateModifiedInSeconds;
         }
 
         static List<LocalData> query(ContentResolver cr, Uri uri, long lastId) {
@@ -506,93 +501,58 @@
         }
 
         @Override
-        public void resizeView(Context context, int w, int h, View v, LocalDataAdapter adapter)
+        public String getSignature() {
+            return mSignature;
+        }
+
+        @Override
+        protected ImageView fillImageView(Context context, final ImageView v, final int decodeWidth,
+                final int decodeHeight, int placeHolderResourceId, LocalDataAdapter adapter,
+                boolean isInProgress) {
+            loadImage(context, v, decodeWidth, decodeHeight, placeHolderResourceId, false);
+            return v;
+        }
+
+        private void loadImage(Context context, ImageView imageView, int decodeWidth,
+                int decodeHeight, int placeHolderResourceId, boolean full) {
+            ThumbTarget thumbTarget = (ThumbTarget) imageView.getTag();
+            if (thumbTarget == null) {
+                thumbTarget = new ThumbTarget(imageView);
+                imageView.setTag(thumbTarget);
+            }
+            // Make sure we've reset all state related to showing thumb and full whenever
+            // we load a thumbnail because loading a thumbnail only happens when we're changing
+            // images.
+            if (!full) {
+                thumbTarget.clearTargets();
+            }
+
+            Glide.with(context)
+                    .using(new ImageModelLoader(context))
+                    .load(this)
+                    .placeholder(placeHolderResourceId)
+                    .fitCenter()
+                    .into(thumbTarget.getTarget(decodeWidth, decodeHeight, full));
+        }
+
+        @Override
+        public void recycle(View view) {
+            super.recycle(view);
+            if (view != null) {
+                ThumbTarget thumbTarget = (ThumbTarget) view.getTag();
+                thumbTarget.clearTargets();
+            }
+        }
+
+        @Override
+        public LocalDataViewType getItemViewType() {
+            return LocalDataViewType.PHOTO;
+        }
+
+        @Override
+        public void loadFullImage(Context context, int w, int h, View v, LocalDataAdapter adapter)
         {
-            // This will call PhotoBitmapLoadTask.
-            fillImageView(context, (ImageView) v, w, h, null, adapter, false);
-        }
-
-        @Override
-        protected BitmapLoadTask getBitmapLoadTask(Context context, ImageView v, int decodeWidth,
-                int decodeHeight, ContentResolver resolver, LocalDataAdapter adapter) {
-            return new PhotoBitmapLoadTask(context, v, decodeWidth, decodeHeight, resolver,
-                    adapter);
-        }
-
-        @Override
-        public boolean rotate90Degrees(Context context, LocalDataAdapter adapter,
-                int currentDataId, boolean clockwise) {
-            RotationTask task = new RotationTask(context, adapter,
-                    currentDataId, clockwise);
-            task.execute(this);
-            return true;
-        }
-
-        private final class PhotoBitmapLoadTask extends BitmapLoadTask {
-            private final int mDecodeWidth;
-            private final int mDecodeHeight;
-            private final Context mContext;
-            private final LocalDataAdapter mAdapter;
-
-            private boolean mNeedsRefresh;
-
-            public PhotoBitmapLoadTask(Context context, ImageView v, int decodeWidth,
-                    int decodeHeight, ContentResolver resolver, LocalDataAdapter adapter) {
-                super(context, v);
-                mDecodeWidth = decodeWidth;
-                mDecodeHeight = decodeHeight;
-                mContext = context;
-                mAdapter = adapter;
-            }
-
-            @Override
-            protected Bitmap doInBackground(Void... v) {
-                // TODO: Implement image cache, which can verify image dims.
-
-                // For correctness, double check image size here.
-                // This only takes 1% of full decode time.
-                Point decodedSize = LocalDataUtil.decodeBitmapDimension(mPath);
-
-                // If the width and height are valid and not matching the values
-                // from MediaStore, then update the MediaStore. This only
-                // happens when the MediaStore has been told incorrect values.
-                if (decodedSize != null && (decodedSize.x != mWidth || decodedSize.y != mHeight)) {
-                    ContentValues values = new ContentValues();
-                    values.put(Images.Media.WIDTH, decodedSize.x);
-                    values.put(Images.Media.HEIGHT, decodedSize.y);
-                    mContext.getContentResolver().update(getUri(), values, null, null);
-                    mNeedsRefresh = true;
-                    Log.w(TAG, "Uri " + getUri() + " has been updated with" +
-                            " the correct size!");
-                    return null;
-                }
-
-                InputStream stream;
-                Bitmap bitmap;
-                try {
-                    stream = new FileInputStream(mPath);
-                    bitmap = LocalDataUtil
-                            .loadImageThumbnailFromStream(stream, mWidth, mHeight, mDecodeWidth,
-                                    mDecodeHeight, mOrientation, MAXIMUM_DECODE_PIXELS);
-                    stream.close();
-                } catch (FileNotFoundException e) {
-                    Log.v(TAG, "File not found:" + mPath);
-                    bitmap = null;
-                } catch (IOException e) {
-                    Log.v(TAG, "IOException for " + mPath, e);
-                    bitmap = null;
-                }
-
-                return bitmap;
-            }
-
-            @Override
-            protected void onPostExecute(Bitmap bitmap) {
-                super.onPostExecute(bitmap);
-                if (mNeedsRefresh && mAdapter != null) {
-                    mAdapter.refresh(getUri());
-                }
-            }
+            loadImage(context, (ImageView) v, w, h, 0, true);
         }
 
         private static class PhotoDataBuilder implements CursorToLocalData {
@@ -601,6 +561,67 @@
                 return LocalMediaData.PhotoData.buildFromCursor(cursor);
             }
         }
+
+        /**
+         * In the filmstrip we want to load a small version of an image and then load a
+         * larger version when we switch to full screen mode. This class manages
+         * that transition and loading process by keeping one target for the smaller thumbnail
+         * and a second target for the larger version.
+         */
+        private static class ThumbTarget {
+            private final SizedImageViewTarget mThumbTarget;
+            private final SizedImageViewTarget mFullTarget;
+            private boolean fullLoaded = false;
+
+            public ThumbTarget(ImageView imageView) {
+                mThumbTarget = new SizedImageViewTarget(imageView) {
+                    @Override
+                    public void onImageReady(Bitmap bitmap) {
+                        // If we manage to load the thumb after the full, we don't
+                        // want to replace the higher quality full with the thumb.
+                        if (!fullLoaded) {
+                            super.onImageReady(bitmap);
+                        }
+                    }
+                };
+
+                mFullTarget = new SizedImageViewTarget(imageView) {
+
+                    @Override
+                    public void onImageReady(Bitmap bitmap) {
+                        // When the full is loaded, we no longer need the thumb.
+                        fullLoaded = true;
+                        Glide.cancel(mThumbTarget);
+                        super.onImageReady(bitmap);
+                    }
+
+                    @Override
+                    public void setPlaceholder(Drawable placeholder) {
+                        // We always load the thumb first which will set the placeholder.
+                        // If we were to set the placeholder here too, instead of showing
+                        // the thumb while we load the full, we will instead revert back
+                        // to the placeholder.
+                    }
+                };
+            }
+
+            public Target getTarget(int width, int height, boolean full) {
+                final SizedImageViewTarget result = full ? mFullTarget : mThumbTarget;
+                // Limit the target size so we don't load a bitmap larger than the max size we
+                // can display.
+                width = Math.min(width, MAXIMUM_TEXTURE_SIZE);
+                height = Math.min(height, MAXIMUM_TEXTURE_SIZE);
+
+                result.setSize(width, height);
+                return result;
+            }
+
+            public void clearTargets() {
+                fullLoaded = false;
+                Glide.cancel(mThumbTarget);
+                Glide.cancel(mFullTarget);
+            }
+        }
     }
 
     public static final class VideoData extends LocalMediaData {
@@ -612,11 +633,10 @@
         public static final int COL_DATA = 5;
         public static final int COL_WIDTH = 6;
         public static final int COL_HEIGHT = 7;
-        public static final int COL_RESOLUTION = 8;
-        public static final int COL_SIZE = 9;
-        public static final int COL_LATITUDE = 10;
-        public static final int COL_LONGITUDE = 11;
-        public static final int COL_DURATION = 12;
+        public static final int COL_SIZE = 8;
+        public static final int COL_LATITUDE = 9;
+        public static final int COL_LONGITUDE = 10;
+        public static final int COL_DURATION = 11;
 
         static final Uri CONTENT_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
 
@@ -638,15 +658,15 @@
                 MediaStore.Video.VideoColumns.DATA,          // 5, string
                 MediaStore.Video.VideoColumns.WIDTH,         // 6, int
                 MediaStore.Video.VideoColumns.HEIGHT,        // 7, int
-                MediaStore.Video.VideoColumns.RESOLUTION,    // 8 string
-                MediaStore.Video.VideoColumns.SIZE,          // 9 long
-                MediaStore.Video.VideoColumns.LATITUDE,      // 10 double
-                MediaStore.Video.VideoColumns.LONGITUDE,     // 11 double
-                MediaStore.Video.VideoColumns.DURATION       // 12 long
+                MediaStore.Video.VideoColumns.SIZE,          // 8 long
+                MediaStore.Video.VideoColumns.LATITUDE,      // 9 double
+                MediaStore.Video.VideoColumns.LONGITUDE,     // 10 double
+                MediaStore.Video.VideoColumns.DURATION       // 11 long
         };
 
         /** The duration in milliseconds. */
         private final long mDurationInSeconds;
+        private final String mSignature;
 
         public VideoData(long id, String title, String mimeType,
                 long dateTakenInSeconds, long dateModifiedInSeconds,
@@ -655,6 +675,7 @@
             super(id, title, mimeType, dateTakenInSeconds, dateModifiedInSeconds,
                     path, width, height, sizeInBytes, latitude, longitude);
             mDurationInSeconds = durationInSeconds;
+            mSignature = mimeType + dateModifiedInSeconds;
         }
 
         public static LocalData fromContentUri(ContentResolver cr, Uri contentUri) {
@@ -798,28 +819,54 @@
         }
 
         @Override
-        public View getView(final Context context,
-                int decodeWidth, int decodeHeight, Drawable placeHolder,
+        public String getSignature() {
+            return mSignature;
+        }
+
+        @Override
+        protected ImageView fillImageView(Context context, final ImageView v, final int decodeWidth,
+                final int decodeHeight, int placeHolderResourceId, LocalDataAdapter adapter,
+                boolean isInProgress) {
+            SizedImageViewTarget target = (SizedImageViewTarget) v.getTag();
+            if (target == null) {
+                target = new SizedImageViewTarget(v);
+                v.setTag(target);
+            }
+            target.setSize(decodeWidth, decodeHeight);
+
+            Glide.with(context)
+                    .using(new VideoModelLoader(context))
+                    .loadFromVideo(this)
+                    .placeholder(placeHolderResourceId)
+                    .fitCenter()
+                    .into(target);
+
+            return v;
+        }
+
+        @Override
+        public View getView(final Context context, View recycled,
+                int decodeWidth, int decodeHeight, int placeHolderResourceId,
                 LocalDataAdapter adapter, boolean isInProgress) {
 
-            // ImageView for the bitmap.
-            ImageView iv = new ImageView(context);
-            iv.setLayoutParams(new FrameLayout.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT,
-                    ViewGroup.LayoutParams.MATCH_PARENT, Gravity.CENTER));
-            fillImageView(context, iv, decodeWidth, decodeHeight, placeHolder,
-                    adapter, isInProgress);
+            final VideoViewHolder viewHolder;
+            final View result;
+            if (recycled != null) {
+                result = recycled;
+                viewHolder = (VideoViewHolder) recycled.getTag();
+            } else {
+                result = LayoutInflater.from(context).inflate(R.layout.filmstrip_video, null);
+                ImageView videoView = (ImageView) result.findViewById(R.id.video_view);
+                ImageView playButton = (ImageView) result.findViewById(R.id.play_button);
+                viewHolder = new VideoViewHolder(videoView, playButton);
+                result.setTag(viewHolder);
+            }
+
+            fillImageView(context, viewHolder.mVideoView, decodeWidth, decodeHeight,
+                    placeHolderResourceId, adapter, isInProgress);
 
             // ImageView for the play icon.
-            ImageView icon = new ImageView(context);
-            icon.setImageResource(R.drawable.ic_control_play);
-            icon.setScaleType(ImageView.ScaleType.CENTER);
-            icon.setLayoutParams(new FrameLayout.LayoutParams(
-                    ViewGroup.LayoutParams.WRAP_CONTENT,
-                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.CENTER));
-            icon.setContentDescription(
-                    context.getResources().getString(R.string.video_control_play));
-            icon.setOnClickListener(new View.OnClickListener() {
+            viewHolder.mPlayButton.setOnClickListener(new View.OnClickListener() {
                 @Override
                 public void onClick(View v) {
                     // TODO: refactor this into activities to avoid this class
@@ -828,76 +875,20 @@
                 }
             });
 
-            FrameLayout f = new FrameLayout(context);
-            f.addView(iv);
-            f.addView(icon);
-            return f;
+            return result;
         }
 
         @Override
-        protected BitmapLoadTask getBitmapLoadTask(
-                Context context, ImageView v, int decodeWidth, int decodeHeight,
-                ContentResolver resolver, LocalDataAdapter adapter) {
-            // TODO: Support isInProgressSession for videos when we need it.
-            return new VideoBitmapLoadTask(context, v);
-        }
-
-        private final class VideoBitmapLoadTask extends BitmapLoadTask {
-
-            public VideoBitmapLoadTask(Context context, ImageView v) {
-                super(context, v);
-            }
-
-            @Override
-            protected Bitmap doInBackground(Void... v) {
-                if (isCancelled() || !isUsing()) {
-                    return null;
-                }
-                Bitmap bitmap = null;
-                bitmap = LocalDataUtil.loadVideoThumbnail(mPath);
-
-                if (isCancelled() || !isUsing()) {
-                    return null;
-                }
-                return bitmap;
-            }
+        public void recycle(View view) {
+            super.recycle(view);
+            VideoViewHolder videoViewHolder = (VideoViewHolder) view.getTag();
+            Target target = (Target) videoViewHolder.mVideoView.getTag();
+            Glide.cancel(target);
         }
 
         @Override
-        public boolean rotate90Degrees(Context context, LocalDataAdapter adapter,
-                int currentDataId, boolean clockwise) {
-            // We don't support rotation for video data.
-            Log.e(TAG, "Unexpected call in rotate90Degrees()");
-            return false;
-        }
-    }
-
-    /**
-     * An {@link AsyncTask} class that loads the bitmap in the background
-     * thread. Sub-classes should implement their own
-     * {@code BitmapLoadTask#doInBackground(Void...)}."
-     */
-    protected abstract class BitmapLoadTask extends AsyncTask<Void, Void, Bitmap> {
-        protected final Context mContext;
-        protected ImageView mView;
-
-        protected BitmapLoadTask(Context context, ImageView v) {
-            mContext = context;
-            mView = v;
-        }
-
-        @Override
-        protected void onPostExecute(Bitmap bitmap) {
-            if (!isUsing()) {
-                return;
-            }
-            if (bitmap == null) {
-                Log.e(TAG, "Failed decoding bitmap for file:" + mPath);
-                return;
-            }
-            BitmapDrawable d = new BitmapDrawable(mContext.getResources(), bitmap);
-            mView.setScaleType(ImageView.ScaleType.FIT_XY);
-            mView.setImageDrawable(d);
+        public LocalDataViewType getItemViewType() {
+            return LocalDataViewType.VIDEO;
         }
     }
 
@@ -909,4 +900,39 @@
         }
     }
 
+     private static class VideoViewHolder {
+        private final ImageView mVideoView;
+        private final ImageView mPlayButton;
+
+        public VideoViewHolder(ImageView videoView, ImageView playButton) {
+            mVideoView = videoView;
+            mPlayButton = playButton;
+        }
+    }
+
+    /**
+     * Normally Glide will figure out the necessary size based on the view
+     * the image is being loaded into. In filmstrip the view isn't immediately
+     * laid out after being requested from the data, which can cause Glide to give
+     * up on obtaining the view dimensions. To avoid that, we manually set the
+     * dimensions.
+     */
+    private static class SizedImageViewTarget extends ImageViewTarget {
+        private int mWidth;
+        private int mHeight;
+
+        public SizedImageViewTarget(ImageView imageView) {
+            super(imageView);
+        }
+
+        public void setSize(int width, int height) {
+            mWidth = width;
+            mHeight = height;
+        }
+
+        @Override
+        public void getSize(final SizeReadyCallback cb) {
+            cb.onSizeReady(mWidth, mHeight);
+        }
+    }
 }
diff --git a/src/com/android/camera/data/LocalSessionData.java b/src/com/android/camera/data/LocalSessionData.java
index f20ca52..585fa92 100644
--- a/src/com/android/camera/data/LocalSessionData.java
+++ b/src/com/android/camera/data/LocalSessionData.java
@@ -53,12 +53,18 @@
     }
 
     @Override
-    public View getView(Context context, int width, int height, Drawable placeHolder,
-           LocalDataAdapter adapter, boolean isInProgress) {
-        //TODO do this on a background thread
+    public View getView(Context context, View recycled, int width, int height,
+            int placeholderResourcedId, LocalDataAdapter adapter, boolean isInProgress) {
+        final ImageView imageView;
+        if (recycled != null) {
+            imageView = (ImageView) recycled;
+        } else {
+            imageView = new ImageView(context);
+        }
+
         byte[] jpegData = Storage.getJpegForSession(mUri);
+        //TODO do this on a background thread
         Bitmap bmp = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
-        ImageView imageView = new ImageView(context);
         imageView.setImageBitmap(bmp);
         imageView.setContentDescription(context.getResources().getString(
                 R.string.media_processing_content_description));
@@ -66,8 +72,13 @@
     }
 
     @Override
-    public void resizeView(Context context, int width, int height, View view,
-           LocalDataAdapter adapter) {
+    public LocalDataViewType getItemViewType() {
+        return LocalDataViewType.SESSION;
+    }
+
+    @Override
+    public void loadFullImage(Context context, int width, int height, View view,
+            LocalDataAdapter adapter) {
 
     }
 
@@ -97,11 +108,6 @@
     }
 
     @Override
-    public boolean rotate90Degrees(Context context, LocalDataAdapter adapter, int currentDataId, boolean clockwise) {
-        return false;
-    }
-
-    @Override
     public void onFullScreen(boolean fullScreen) {
 
     }
@@ -152,6 +158,11 @@
     }
 
     @Override
+    public String getSignature() {
+        return "";
+    }
+
+    @Override
     public boolean isMetadataUpdated() {
         return true;
     }
@@ -192,7 +203,7 @@
     }
 
     @Override
-    public void recycle() {
+    public void recycle(View view) {
 
     }
 
diff --git a/src/com/android/camera/data/SimpleViewData.java b/src/com/android/camera/data/SimpleViewData.java
index 1101857..74f1073 100644
--- a/src/com/android/camera/data/SimpleViewData.java
+++ b/src/com/android/camera/data/SimpleViewData.java
@@ -41,11 +41,13 @@
     private final long mDateModified;
     private final Bundle mMetaData;
     private final Uri mUri;
+    private final LocalDataViewType mItemViewType;
 
     public SimpleViewData(
-            View v, int width, int height,
+            View v, LocalDataViewType viewType, int width, int height,
             int dateTaken, int dateModified) {
         mView = v;
+        mItemViewType = viewType;
         mWidth = width;
         mHeight = height;
         mDateTaken = dateTaken;
@@ -93,6 +95,11 @@
     }
 
     @Override
+    public LocalDataViewType getItemViewType() {
+        return mItemViewType;
+    }
+
+    @Override
     public String getPath() {
         return "";
     }
@@ -128,13 +135,13 @@
     }
 
     @Override
-    public View getView(Context context, int width, int height, Drawable placeHolder,
+    public View getView(Context context, View recycled, int width, int height, int placeHolderResourceId,
             LocalDataAdapter adapter, boolean isInProgressSession) {
         return mView;
     }
 
     @Override
-    public void resizeView(Context context, int w, int h, View view, LocalDataAdapter adapter) {
+    public void loadFullImage(Context context, int w, int h, View view, LocalDataAdapter adapter) {
         // do nothing.
     }
 
@@ -144,8 +151,8 @@
     }
 
     @Override
-    public void recycle() {
-        // do nothing.
+    public void recycle(View view) {
+        // Do nothing.
     }
 
     @Override
@@ -174,14 +181,6 @@
     }
 
     @Override
-    public boolean rotate90Degrees(Context context, LocalDataAdapter adapter,
-            int currentDataId, boolean clockwise) {
-        // We don't support rotation for SimpleViewData.
-        Log.w(TAG, "Unexpected call in rotate90Degrees()");
-        return false;
-    }
-
-    @Override
     public long getSizeInBytes() {
         return 0;
     }
@@ -197,6 +196,11 @@
     }
 
     @Override
+    public String getSignature() {
+        return "";
+    }
+
+    @Override
     public boolean isMetadataUpdated() {
         return true;
     }
diff --git a/src/com/android/camera/data/VideoModelLoader.java b/src/com/android/camera/data/VideoModelLoader.java
new file mode 100644
index 0000000..b140037
--- /dev/null
+++ b/src/com/android/camera/data/VideoModelLoader.java
@@ -0,0 +1,29 @@
+package com.android.camera.data;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.loader.bitmap.model.ModelLoader;
+import com.bumptech.glide.loader.bitmap.model.file_descriptor.FileDescriptorModelLoader;
+import com.bumptech.glide.loader.bitmap.resource.ResourceFetcher;
+
+/**
+ * Translates a video data into an InputStream for the Glide library.
+ */
+public class VideoModelLoader implements FileDescriptorModelLoader<LocalMediaData.VideoData> {
+    private final ModelLoader<String, ParcelFileDescriptor> mPathLoader;
+
+    public VideoModelLoader(Context context) {
+        mPathLoader = Glide.buildFileDescriptorModelLoader(String.class, context);
+    }
+
+    @Override
+    public ResourceFetcher<ParcelFileDescriptor> getResourceFetcher(LocalMediaData.VideoData model,
+            int width, int height) {
+        return mPathLoader.getResourceFetcher(model.getPath(), width, height);
+    }
+
+    public String getId(LocalMediaData.VideoData model) {
+        return mPathLoader.getId(model.getPath()) + model.getSignature();
+    }
+}
diff --git a/src/com/android/camera/debug/DebugCameraProxy.java b/src/com/android/camera/debug/DebugCameraProxy.java
index 936ed8f..e2f14d2 100644
--- a/src/com/android/camera/debug/DebugCameraProxy.java
+++ b/src/com/android/camera/debug/DebugCameraProxy.java
@@ -21,10 +21,10 @@
 import android.os.Handler;
 import android.view.SurfaceHolder;
 
-import com.android.camera.app.CameraManager;
+import com.android.camera.cameradevice.CameraManager;
 
 /**
- * A {@link com.android.camera.app.CameraManager.CameraProxy} which wraps the
+ * A {@link com.android.camera.cameradevice.CameraManager.CameraProxy} which wraps the
  * other and adds logs for all operations.
  */
 public class DebugCameraProxy implements CameraManager.CameraProxy {
diff --git a/src/com/android/camera/filmstrip/DataAdapter.java b/src/com/android/camera/filmstrip/DataAdapter.java
index e896908..e1ca61d 100644
--- a/src/com/android/camera/filmstrip/DataAdapter.java
+++ b/src/com/android/camera/filmstrip/DataAdapter.java
@@ -80,11 +80,19 @@
      * Returns the view to visually present the image data.
      *
      * @param context The {@link android.content.Context} to create the view.
+     * @param recycled A view that can be reused if one is available, or null.
      * @param dataID The ID of the image data to be presented.
      * @return The view representing the image data. Null if unavailable or
      *         the {@code dataID} is out of range.
      */
-    public View getView(Context context, int dataID);
+    public View getView(Context context, View recycled, int dataID);
+
+    /** Returns a unique identifier for the view created by this data so that the view
+     * can be reused.
+     *
+     * @see android.widget.BaseAdapter#getItemViewType(int)
+     */
+    public int getItemViewType(int dataId);
 
     /**
      * Resizes the view used to visually present the image data.  This is
diff --git a/src/com/android/camera/filmstrip/ImageData.java b/src/com/android/camera/filmstrip/ImageData.java
index 1bbea11..f080f8e 100644
--- a/src/com/android/camera/filmstrip/ImageData.java
+++ b/src/com/android/camera/filmstrip/ImageData.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.net.Uri;
+import android.view.View;
 
 /**
  * Common interface for all images in the filmstrip.
@@ -111,7 +112,7 @@
      * function after its corresponding view is removed from the view
      * hierarchy.
      */
-    public void recycle();
+    public void recycle(View view);
 
     /**
      * @return The URI of this data. Must be a unique one and not null.
diff --git a/src/com/android/camera/module/ModuleController.java b/src/com/android/camera/module/ModuleController.java
index ea6b7e4..f47a278 100644
--- a/src/com/android/camera/module/ModuleController.java
+++ b/src/com/android/camera/module/ModuleController.java
@@ -19,7 +19,7 @@
 import com.android.camera.CameraActivity;
 import com.android.camera.ShutterButton;
 import com.android.camera.app.CameraAppUI.BottomBarUISpec;
-import com.android.camera.app.CameraManager;
+import com.android.camera.cameradevice.CameraManager;
 import com.android.camera.hardware.HardwareSpec;
 import com.android.camera.settings.SettingsManager;
 
@@ -75,11 +75,6 @@
     public void onPreviewVisibilityChanged(int visibility);
 
     /**
-     * Called when the first preview data is received.
-     */
-    public void onPreviewInitialDataReceived();
-
-    /**
      * Called when the framework layout orientation changed.
      *
      * @param isLandscape Whether the new orientation is landscape or portrait.
diff --git a/src/com/android/camera/remote/RemoteCameraModule.java b/src/com/android/camera/remote/RemoteCameraModule.java
new file mode 100644
index 0000000..d094224
--- /dev/null
+++ b/src/com/android/camera/remote/RemoteCameraModule.java
@@ -0,0 +1,33 @@
+/*
+ * 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.remote;
+
+/**
+ * Modules implementing this interface signal that they do support remote
+ * shutter. Such modules need to signal to the remote interface that they are
+ * available or unavailable through calling
+ * {@link RemoteShutterListener#onModuleReady(RemoteCameraModule)} and
+ * {@link RemoteShutterListener#onModuleExit()}.
+ */
+public interface RemoteCameraModule {
+    /**
+     * Called when a remote client wants the module to take a picture. The
+     * module should trigger a capture and send the result via
+     * {@link RemoteShutterListener#onPictureTaken(byte[])}.
+     */
+    void onRemoteShutterPress();
+}
diff --git a/src/com/android/camera/remote/RemoteShutterListener.java b/src/com/android/camera/remote/RemoteShutterListener.java
new file mode 100644
index 0000000..b24a4a1
--- /dev/null
+++ b/src/com/android/camera/remote/RemoteShutterListener.java
@@ -0,0 +1,38 @@
+/*
+ * 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.remote;
+
+/**
+ * Classes implementing this interface can be informed when events relevant to
+ * remote shutter apps are occurring.
+ */
+public interface RemoteShutterListener {
+    /**
+     * Called when the module is active and ready for shutter presses.
+     */
+    void onModuleReady(RemoteCameraModule module);
+
+    /**
+     * Called when module is no longer ready for shutter presses.
+     */
+    void onModuleExit();
+
+    /**
+     * Called when a picture is taken.
+     */
+    void onPictureTaken(byte[] photoData);
+}
diff --git a/src/com/android/camera/settings/CameraSettingsActivity.java b/src/com/android/camera/settings/CameraSettingsActivity.java
index 72dafe5..f1e0ec4 100644
--- a/src/com/android/camera/settings/CameraSettingsActivity.java
+++ b/src/com/android/camera/settings/CameraSettingsActivity.java
@@ -25,7 +25,6 @@
 import android.content.pm.PackageManager;
 import android.hardware.Camera;
 import android.hardware.Camera.CameraInfo;
-import android.hardware.Camera.Size;
 import android.os.Bundle;
 import android.preference.ListPreference;
 import android.preference.Preference;
@@ -42,6 +41,7 @@
 import com.android.camera.settings.SettingsUtil.SelectedVideoQualities;
 import com.android.camera.util.FeedbackHelper;
 import com.android.camera.util.SettingsHelper;
+import com.android.camera.util.Size;
 import com.android.camera2.R;
 
 import java.text.DecimalFormat;
@@ -299,7 +299,7 @@
         /**
          * Sets the entries for the given list preference.
          *
-         * @param selectedQualities The possible S,M,L entries the user can
+         * @param selectedSizes The possible S,M,L entries the user can
          *            choose from.
          * @param preference The preference to set the entries for.
          */
@@ -351,7 +351,7 @@
         /**
          * Sets the summary for the given list preference.
          *
-         * @param selectedQualities The selected picture sizes.
+         * @param selectedSizes The selected picture sizes.
          * @param preference The preference for which to set the summary.
          */
         private void setSummaryForSelection(SelectedPictureSizes selectedSizes,
@@ -395,7 +395,8 @@
                 if (mPictureSizesBack == null) {
                     Camera backCamera = Camera.open(backCameraId);
                     if (backCamera != null) {
-                        List<Size> sizes = backCamera.getParameters().getSupportedPictureSizes();
+                        List<Size> sizes = Size.buildListFromCameraSizes(
+                                backCamera.getParameters().getSupportedPictureSizes());
                         backCamera.release();
                         mPictureSizesBack = SettingsUtil.getSelectedCameraPictureSizes(sizes,
                                 backCameraId);
@@ -415,7 +416,8 @@
                 if (mPictureSizesFront == null) {
                     Camera frontCamera = Camera.open(frontCameraId);
                     if (frontCamera != null) {
-                        List<Size> sizes = frontCamera.getParameters().getSupportedPictureSizes();
+                        List<Size> sizes = Size.buildListFromCameraSizes(
+                                frontCamera.getParameters().getSupportedPictureSizes());
                         frontCamera.release();
                         mPictureSizesFront = SettingsUtil.getSelectedCameraPictureSizes(sizes,
                                 frontCameraId);
@@ -454,7 +456,7 @@
          *         picture size in megapixels.
          */
         private String getSizeSummaryString(Size size) {
-            String megaPixels = sMegaPixelFormat.format((size.width * size.height) / 1e6);
+            String megaPixels = sMegaPixelFormat.format((size.width() * size.height()) / 1e6);
             return getResources().getString(R.string.setting_summary_x_megapixels, megaPixels);
         }
     }
diff --git a/src/com/android/camera/settings/SettingsUtil.java b/src/com/android/camera/settings/SettingsUtil.java
index bd557bd..b14f5e2 100644
--- a/src/com/android/camera/settings/SettingsUtil.java
+++ b/src/com/android/camera/settings/SettingsUtil.java
@@ -20,14 +20,14 @@
 import android.content.DialogInterface;
 import android.hardware.Camera;
 import android.hardware.Camera.Parameters;
-import android.hardware.Camera.Size;
 import android.media.CamcorderProfile;
 import android.util.SparseArray;
 
-import com.android.camera.app.CameraManager;
+import com.android.camera.cameradevice.CameraManager;
 import com.android.camera.debug.Log;
 import com.android.camera.settings.SettingsManager.SettingsCapabilities;
 import com.android.camera.util.Callback;
+import com.android.camera.util.Size;
 import com.android.camera2.R;
 
 import java.util.ArrayList;
@@ -68,7 +68,7 @@
         }
 
         private static String sizeToString(Size size) {
-            return size.width + "x" + size.height;
+            return size.width() + "x" + size.height();
         }
     }
 
@@ -140,9 +140,9 @@
     public static void setCameraPictureSize(String sizeSetting, List<Size> supported,
             Parameters parameters, int cameraId) {
         Size selectedSize = getCameraPictureSize(sizeSetting, supported, cameraId);
-        Log.d(TAG, "Selected " + sizeSetting + " resolution: " + selectedSize.width + "x" +
-                selectedSize.height);
-        parameters.setPictureSize(selectedSize.width, selectedSize.height);
+        Log.d(TAG, "Selected " + sizeSetting + " resolution: " + selectedSize.width() + "x" +
+                selectedSize.height());
+        parameters.setPictureSize(selectedSize.width(), selectedSize.height());
     }
 
     /**
@@ -184,17 +184,17 @@
         Collections.sort(supported, new Comparator<Size>() {
             @Override
             public int compare(Size lhs, Size rhs) {
-                int leftArea = lhs.width * lhs.height;
-                int rightArea = rhs.width * rhs.height;
+                int leftArea = lhs.width() * lhs.height();
+                int rightArea = rhs.width() * rhs.height();
                 return rightArea - leftArea;
             }
         });
         if (DEBUG) {
             Log.d(TAG, "Supported Sizes:");
             for (Size size : supported) {
-                Log.d(TAG, " --> " + size.width + "x" + size.height + "  "
-                        + ((size.width * size.height) / 1000000f) + " - "
-                        + (size.width / (float) size.height));
+                Log.d(TAG, " --> " + size.width() + "x" + size.height() + "  "
+                        + ((size.width() * size.height()) / 1000000f) + " - "
+                        + (size.width() / (float) size.height()));
             }
         }
 
@@ -203,14 +203,14 @@
 
         // If possible we want to find medium and small sizes with the same
         // aspect ratio as 'large'.
-        final float targetAspectRatio = selectedSizes.large.width
-                / (float) selectedSizes.large.height;
+        final float targetAspectRatio = selectedSizes.large.width()
+                / (float) selectedSizes.large.height();
 
         // Create a list of sizes with the same aspect ratio as "large" which we
         // will search in primarily.
         ArrayList<Size> aspectRatioMatches = new ArrayList<Size>();
         for (Size size : supported) {
-            float aspectRatio = size.width / (float) size.height;
+            float aspectRatio = size.width() / (float) size.height();
             // Allow for small rounding errors in aspect ratio.
             if (Math.abs(aspectRatio - targetAspectRatio) < 0.01) {
                 aspectRatioMatches.add(size);
@@ -245,7 +245,7 @@
 
             // Based on the large pixel count, determine the target pixel count
             // for medium and small.
-            final int largePixelCount = selectedSizes.large.width * selectedSizes.large.height;
+            final int largePixelCount = selectedSizes.large.width() * selectedSizes.large.height();
             final int mediumTargetPixelCount = (int) (largePixelCount * MEDIUM_RELATIVE_PICTURE_SIZE);
             final int smallTargetPixelCount = (int) (largePixelCount * SMALL_RELATIVE_PICTURE_SIZE);
 
@@ -339,7 +339,7 @@
 
         for (int i = 0; i < sortedSizes.size(); ++i) {
             Size size = sortedSizes.get(i);
-            int pixelCountDiff = Math.abs((size.width * size.height) - targetPixelCount);
+            int pixelCountDiff = Math.abs((size.width() * size.height()) - targetPixelCount);
             if (pixelCountDiff < closestMatchPixelCountDiff) {
                 closestMatchIndex = i;
                 closestMatchPixelCountDiff = pixelCountDiff;
diff --git a/src/com/android/camera/ui/ModeListView.java b/src/com/android/camera/ui/ModeListView.java
index 08171ef..ad71530 100644
--- a/src/com/android/camera/ui/ModeListView.java
+++ b/src/com/android/camera/ui/ModeListView.java
@@ -733,8 +733,6 @@
 
         @Override
         public void onCurrentState() {
-          announceForAccessibility(
-                  getContext().getResources().getString(R.string.accessibility_mode_list_shimmy));
         }
 
     }
diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java
index 3a51d29..4aafead 100644
--- a/src/com/android/camera/util/CameraUtil.java
+++ b/src/com/android/camera/util/CameraUtil.java
@@ -35,7 +35,6 @@
 import android.hardware.Camera;
 import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Parameters;
-import android.hardware.Camera.Size;
 import android.location.Location;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
@@ -466,11 +465,11 @@
         return orientationHistory;
     }
 
-    private static Point getDefaultDisplaySize(Context context, Point size) {
+    private static Size getDefaultDisplaySize(Context context, Point size) {
         WindowManager windowManager = (WindowManager) context
                 .getSystemService(Context.WINDOW_SERVICE);
         windowManager.getDefaultDisplay().getSize(size);
-        return size;
+        return new Size(size);
     }
 
     public static Size getOptimalPreviewSize(Context context,
@@ -480,7 +479,7 @@
 
         int index = 0;
         for (Size s : sizes) {
-            points[index++] = new Point(s.width, s.height);
+            points[index++] = new Point(s.width(), s.height());
         }
 
         int optimalPickIndex = getOptimalPreviewSize(context, points, targetRatio);
@@ -503,8 +502,8 @@
         // wrong size of preview surface. When we change the preview size, the
         // new overlay will be created before the old one closed, which causes
         // an exception. For now, just get the screen size.
-        Point point = getDefaultDisplaySize(context, new Point());
-        int targetHeight = Math.min(point.x, point.y);
+        Size defaultDisplaySize = getDefaultDisplaySize(context, new Point());
+        int targetHeight = Math.min(defaultDisplaySize.width(), defaultDisplaySize.height());
         // Try to find an size match aspect ratio and size
         for (int i = 0; i < sizes.length; i++) {
             Point size = sizes[i];
@@ -546,11 +545,11 @@
 
         // Try to find a size matches aspect ratio and has the largest width
         for (Size size : sizes) {
-            double ratio = (double) size.width / size.height;
+            double ratio = (double) size.width() / size.height();
             if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
                 continue;
             }
-            if (optimalSize == null || size.width > optimalSize.width) {
+            if (optimalSize == null || size.width() > optimalSize.width()) {
                 optimalSize = size;
             }
         }
@@ -560,7 +559,7 @@
         if (optimalSize == null) {
             Log.w(TAG, "No picture size match the aspect ratio");
             for (Size size : sizes) {
-                if (optimalSize == null || size.width > optimalSize.width) {
+                if (optimalSize == null || size.width() > optimalSize.width()) {
                     optimalSize = size;
                 }
             }
diff --git a/src/com/android/camera/util/Size.java b/src/com/android/camera/util/Size.java
new file mode 100644
index 0000000..0c54f4f
--- /dev/null
+++ b/src/com/android/camera/util/Size.java
@@ -0,0 +1,117 @@
+/*
+ * 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.util;
+
+import android.graphics.Point;
+import android.hardware.Camera;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An immutable simple size container.
+ */
+public class Size {
+
+    /**
+     * An helper method to build a list of this class from a list of
+     * {@link android.hardware.Camera.Size}.
+     *
+     * @param cameraSizes Source.
+     * @return The built list.
+     */
+    public static List<Size> buildListFromCameraSizes(List<Camera.Size> cameraSizes) {
+        ArrayList<Size> list = new ArrayList<Size>(cameraSizes.size());
+        for (Camera.Size cameraSize : cameraSizes) {
+            list.add(new Size(cameraSize));
+        }
+        return list;
+    }
+
+    private final Point val;
+
+    /**
+     * Constructor.
+     */
+    public Size(int width, int height) {
+        val = new Point(width, height);
+    }
+
+    /**
+     * Copy constructor.
+     */
+    public Size(Size other) {
+        if (other == null) {
+            val = new Point(0, 0);
+        } else {
+            val = new Point(other.width(), other.height());
+        }
+    }
+
+    /**
+     * Constructor from a source {@link android.hardware.Camera.Size}.
+     *
+     * @param other The source size.
+     */
+    public Size(Camera.Size other) {
+        if (other == null) {
+            val = new Point(0, 0);
+        } else {
+            val = new Point(other.width, other.height);
+        }
+    }
+
+    /**
+     * Constructor from a source {@link android.graphics.Point}.
+     *
+     * @param p The source size.
+     */
+    public Size(Point p) {
+        if (p == null) {
+            val = new Point(0, 0);
+        } else {
+            val = new Point(p);
+        }
+    }
+
+    public int width() {
+        return val.x;
+    }
+
+    public int height() {
+        return val.y;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof Size) {
+            Size other = (Size) o;
+            return val.equals(other.val);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return val.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "Size: (" + this.width() + " x " + this.height() + ")";
+    }
+}
diff --git a/src/com/android/camera/widget/FilmstripView.java b/src/com/android/camera/widget/FilmstripView.java
index 460e302..3551eda 100644
--- a/src/com/android/camera/widget/FilmstripView.java
+++ b/src/com/android/camera/widget/FilmstripView.java
@@ -30,6 +30,7 @@
 import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -46,7 +47,9 @@
 import com.android.camera.util.CameraUtil;
 import com.android.camera2.R;
 
+import java.util.ArrayDeque;
 import java.util.Arrays;
+import java.util.Queue;
 
 public class FilmstripView extends ViewGroup {
     private static final Log.Tag TAG = new Log.Tag("FilmstripView");
@@ -105,6 +108,8 @@
     private float mOverScaleFactor = 1f;
 
     private boolean mFullScreenUIHidden = false;
+    private SparseArray<Queue<View>> recycledViews = new SparseArray<Queue<View>>();
+
 
     /**
      * A helper class to tract and calculate the view coordination.
@@ -353,7 +358,8 @@
         public void removeViewFromHierarchy(boolean force) {
             if (force || mData.getViewType() != ImageData.VIEW_TYPE_STICKY) {
                 removeView(mView);
-                mData.recycle();
+                mData.recycle(mView);
+                recycleView(mView, mDataId);
             } else {
                 setVisibility(View.INVISIBLE);
             }
@@ -595,6 +601,26 @@
         }
     }
 
+    private void recycleView(View view, int dataId) {
+        final int viewType = mDataAdapter.getItemViewType(dataId);
+        Queue<View> recycledViewsForType = recycledViews.get(viewType);
+        if (recycledViewsForType == null) {
+            recycledViewsForType = new ArrayDeque<View>();
+            recycledViews.put(viewType, recycledViewsForType);
+        }
+        recycledViewsForType.offer(view);
+    }
+
+    private View getRecycledView(int dataId) {
+        final int viewType = mDataAdapter.getItemViewType(dataId);
+        Queue<View> recycledViewsForType = recycledViews.get(viewType);
+        View result = null;
+        if (recycledViewsForType != null) {
+            result = recycledViewsForType.poll();
+        }
+        return result;
+    }
+
     /**
      * Returns the controller.
      *
@@ -709,11 +735,13 @@
             return null;
         }
 
-        int maxEdge = (int) ((float) Math.max(this.getHeight(), this.getWidth())
-                * FILM_STRIP_SCALE);
-        mDataAdapter.suggestViewSizeBound(maxEdge, maxEdge);
+        int width = Math.round(mScale * getWidth());
+        int height = Math.round(mScale * getHeight());
+        mDataAdapter.suggestViewSizeBound(width, height);
+
         data.prepare();
-        View v = mDataAdapter.getView(mActivity, dataID);
+        View recycled = getRecycledView(dataID);
+        View v = mDataAdapter.getView(mActivity, recycled, dataID);
         if (v == null) {
             return null;
         }
@@ -925,6 +953,7 @@
 
         curr.layoutWithTranslationX(mDrawArea, mCenterX, mScale);
         curr.setAlpha(1f);
+        curr.setVisibility(VISIBLE);
 
         if (inFullScreen()) {
             curr.setTranslationX(translate * (mCenterX - currCenterX) / (nextCenterX - currCenterX));
@@ -1148,10 +1177,10 @@
 
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        mDrawArea.left = l;
-        mDrawArea.top = t;
-        mDrawArea.right = r;
-        mDrawArea.bottom = b;
+        mDrawArea.left = 0;
+        mDrawArea.top = 0;
+        mDrawArea.right = r - l;
+        mDrawArea.bottom = b - t;
         mZoomView.layout(mDrawArea.left, mDrawArea.top, mDrawArea.right, mDrawArea.bottom);
         // TODO: Need a more robust solution to decide when to re-layout
         // If in the middle of zooming, only re-layout when the layout has
@@ -1409,7 +1438,9 @@
 
     private void setDataAdapter(DataAdapter adapter) {
         mDataAdapter = adapter;
-        mDataAdapter.suggestViewSizeBound(getMeasuredWidth(), getMeasuredHeight());
+        int maxEdge = (int) ((float) Math.max(this.getHeight(), this.getWidth())
+                * FILM_STRIP_SCALE);
+        mDataAdapter.suggestViewSizeBound(maxEdge, maxEdge);
         mDataAdapter.setListener(new DataAdapter.Listener() {
             @Override
             public void onDataLoaded() {
diff --git a/src_pd/com/android/camera/util/RemoteShutterHelper.java b/src_pd/com/android/camera/util/RemoteShutterHelper.java
new file mode 100644
index 0000000..78028ad
--- /dev/null
+++ b/src_pd/com/android/camera/util/RemoteShutterHelper.java
@@ -0,0 +1,40 @@
+/*
+ * 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.util;
+
+import android.content.Context;
+
+import com.android.camera.remote.RemoteCameraModule;
+import com.android.camera.remote.RemoteShutterListener;
+
+public class RemoteShutterHelper {
+    public static RemoteShutterListener create(Context context) {
+        return new RemoteShutterListener() {
+            @Override
+            public void onPictureTaken(byte[] photoData) {
+            }
+
+            @Override
+            public void onModuleReady(RemoteCameraModule module) {
+            }
+
+            @Override
+            public void onModuleExit() {
+            }
+        };
+    }
+}
diff --git a/tests_camera/src/com/android/camera/activity/CameraTestCase.java b/tests_camera/src/com/android/camera/activity/CameraTestCase.java
index 38d03e1..44a7139 100644
--- a/tests_camera/src/com/android/camera/activity/CameraTestCase.java
+++ b/tests_camera/src/com/android/camera/activity/CameraTestCase.java
@@ -30,7 +30,7 @@
 import android.view.View;
 
 import com.android.camera.CameraTestDevice;
-import com.android.camera.app.CameraManager.CameraProxy;
+import com.android.camera.cameradevice.CameraManager.CameraProxy;
 import com.android.camera.util.CameraUtil;
 import com.android.gallery3d.R;