This adds the following four CLs to Camera2:

  http://ag/339319
  http://ag/338974
  http://ag/338401
  http://ag/324149

Adapted to work in Camera2 and adding a .gitignore so that temporary
Eclipse files and folders are not submitted.

Change-Id: I40295c7f0139f76270c44f0ca395c0574a288569
diff --git a/src/com/android/camera/AnimationManager.java b/src/com/android/camera/AnimationManager.java
new file mode 100644
index 0000000..41aa4b8
--- /dev/null
+++ b/src/com/android/camera/AnimationManager.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.view.View;
+
+/**
+ * Class to handle animations.
+ */
+
+public class AnimationManager {
+
+    public static final float FLASH_ALPHA_START = 0.3f;
+    public static final float FLASH_ALPHA_END = 0f;
+    public static final int FLASH_DURATION = 300;
+
+    public static final int SHRINK_DURATION = 400;
+    public static final int HOLD_DURATION = 2500;
+    public static final int SLIDE_DURATION = 1100;
+
+    private ObjectAnimator mFlashAnim;
+    private AnimatorSet mCaptureAnimator;
+
+    /**
+     * Starts capture animation.
+     * @param view a thumbnail view that shows a picture captured and gets animated
+     */
+    public void startCaptureAnimation(final View view) {
+        if (mCaptureAnimator != null && mCaptureAnimator.isStarted()) {
+            mCaptureAnimator.cancel();
+        }
+        View parentView = (View) view.getParent();
+        float slideDistance = (float) (parentView.getWidth() - view.getLeft());
+
+        float scaleX = ((float) parentView.getWidth()) / ((float) view.getWidth());
+        float scaleY = ((float) parentView.getHeight()) / ((float) view.getHeight());
+        float scale = scaleX > scaleY ? scaleX : scaleY;
+
+        int centerX = view.getLeft() + view.getWidth() / 2;
+        int centerY = view.getTop() + view.getHeight() / 2;
+
+        ObjectAnimator slide = ObjectAnimator.ofFloat(view, "translationX", 0f, slideDistance)
+                .setDuration(AnimationManager.SLIDE_DURATION);
+        slide.setStartDelay(AnimationManager.SHRINK_DURATION + AnimationManager.HOLD_DURATION);
+        mCaptureAnimator = new AnimatorSet();
+        mCaptureAnimator.playTogether(
+                ObjectAnimator.ofFloat(view, "scaleX", scale, 1f)
+                        .setDuration(AnimationManager.SHRINK_DURATION),
+                ObjectAnimator.ofFloat(view, "scaleY", scale, 1f)
+                        .setDuration(AnimationManager.SHRINK_DURATION),
+                ObjectAnimator.ofFloat(view, "translationX",
+                        parentView.getWidth() / 2 - centerX, 0f)
+                        .setDuration(AnimationManager.SHRINK_DURATION),
+                ObjectAnimator.ofFloat(view, "translationY",
+                        parentView.getHeight() / 2 - centerY, 0f)
+                        .setDuration(AnimationManager.SHRINK_DURATION),
+                slide);
+        mCaptureAnimator.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animator) {
+                view.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                view.setScaleX(1f);
+                view.setScaleX(1f);
+                view.setTranslationX(0f);
+                view.setTranslationY(0f);
+                view.setVisibility(View.INVISIBLE);
+                mCaptureAnimator.removeAllListeners();
+                mCaptureAnimator = null;
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animator) {
+                view.setVisibility(View.INVISIBLE);
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animator) {
+                // Do nothing.
+            }
+        });
+        mCaptureAnimator.start();
+    }
+
+   /**
+    * Starts flash animation.
+    * @params flashOverlay the overlay that will animate on alpha to make the flash impression
+    */
+    public void startFlashAnimation(final View flashOverlay) {
+        // End the previous animation if the previous one is still running
+        if (mFlashAnim != null && mFlashAnim.isRunning()) {
+            mFlashAnim.cancel();
+        }
+        // Start new flash animation.
+        mFlashAnim = ObjectAnimator.ofFloat(flashOverlay, "alpha",
+                AnimationManager.FLASH_ALPHA_START, AnimationManager.FLASH_ALPHA_END);
+        mFlashAnim.setDuration(AnimationManager.FLASH_DURATION);
+        mFlashAnim.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationStart(Animator animator) {
+                flashOverlay.setVisibility(View.VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                flashOverlay.setAlpha(0f);
+                flashOverlay.setVisibility(View.GONE);
+                mFlashAnim.removeAllListeners();
+                mFlashAnim = null;
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animator) {
+                // Do nothing.
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animator) {
+                // Do nothing.
+            }
+        });
+        mFlashAnim.start();
+    }
+
+    /**
+     * Cancels on-going flash animation and capture animation, if any.
+     */
+    public void cancelAnimations() {
+        // End the previous animation if the previous one is still running
+        if (mFlashAnim != null && mFlashAnim.isRunning()) {
+            mFlashAnim.cancel();
+        }
+        if (mCaptureAnimator != null && mCaptureAnimator.isStarted()) {
+            mCaptureAnimator.cancel();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index e110327..57da90e 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -32,6 +32,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.provider.Settings;
+import android.util.Log;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.OrientationEventListener;
@@ -40,6 +41,7 @@
 import android.view.Window;
 import android.view.WindowManager;
 import android.widget.ImageView;
+import android.widget.ProgressBar;
 
 import com.android.camera.data.CameraDataAdapter;
 import com.android.camera.data.CameraPreviewData;
@@ -51,8 +53,8 @@
 import com.android.camera.ui.CameraSwitcher;
 import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
 import com.android.camera.ui.FilmStripView;
-import com.android.camera.util.PhotoSphereHelper;
 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
+import com.android.camera.util.PhotoSphereHelper;
 import com.android.camera.util.RefocusHelper;
 import com.android.camera2.R;
 
@@ -82,6 +84,8 @@
     private CameraModule mCurrentModule;
     private View mRootView;
     private FilmStripView mFilmStripView;
+    private ProgressBar mBottomProgress;
+    private View mPanoStitchingPanel;
     private int mResultCodeForTesting;
     private Intent mResultDataForTesting;
     private OnScreenHint mStorageHint;
@@ -150,26 +154,65 @@
         sFirstStartAfterScreenOn = false;
     }
 
-    private FilmStripView.Listener mFilmStripListener = new FilmStripView.Listener() {
-            @Override
-            public void onDataPromoted(int dataID) {
-                removeData(dataID);
-            }
+    private FilmStripView.Listener mFilmStripListener =
+            new FilmStripView.Listener() {
+                @Override
+                public void onDataPromoted(int dataID) {
+                    removeData(dataID);
+                }
 
-            @Override
-            public void onDataDemoted(int dataID) {
-                removeData(dataID);
-            }
+                @Override
+                public void onDataDemoted(int dataID) {
+                    removeData(dataID);
+                }
 
-            @Override
-            public void onDataFullScreenChange(int dataID, boolean full) {
-            }
+                @Override
+                public void onDataFullScreenChange(int dataID, boolean full) {
+                }
 
-            @Override
-            public void onSwitchMode(boolean toCamera) {
-                mCurrentModule.onSwitchMode(toCamera);
-            }
-        };
+                @Override
+                public void onSwitchMode(boolean toCamera) {
+                    mCurrentModule.onSwitchMode(toCamera);
+                }
+
+                @Override
+                public void onCurrentDataChanged(int dataID, boolean current) {
+                    if (!current) {
+                        hidePanoStitchingProgress();
+                    } else {
+                        LocalData currentData = mDataAdapter.getLocalData(dataID);
+                        if (currentData == null) {
+                            Log.w(TAG, "Current data ID not found.");
+                            hidePanoStitchingProgress();
+                            return;
+                        }
+                        Uri contentUri = currentData.getContentUri();
+                        if (contentUri == null) {
+                            hidePanoStitchingProgress();
+                            return;
+                        }
+                        int panoStitchingProgress = mPanoramaManager.getTaskProgress(contentUri);
+                        if (panoStitchingProgress < 0) {
+                            hidePanoStitchingProgress();
+                            return;
+                        }
+                        showPanoStitchingProgress();
+                        updateStitchingProgress(panoStitchingProgress);
+                    }
+                }
+            };
+
+    private void hidePanoStitchingProgress() {
+        mPanoStitchingPanel.setVisibility(View.GONE);
+    }
+
+    private void showPanoStitchingProgress() {
+        mPanoStitchingPanel.setVisibility(View.VISIBLE);
+    }
+
+    private void updateStitchingProgress(int progress) {
+        mBottomProgress.setProgress(progress);
+    }
 
     private Runnable mDeletionRunnable = new Runnable() {
             @Override
@@ -181,16 +224,50 @@
     private ImageTaskManager.TaskListener mStitchingListener =
             new ImageTaskManager.TaskListener() {
                 @Override
-                public void onTaskQueued(String filePath, Uri imageUri) {
+                public void onTaskQueued(String filePath, final Uri imageUri) {
+                    mMainHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            notifyNewMedia(imageUri);
+                        }
+                    });
                 }
 
                 @Override
-                public void onTaskDone(String filePath, Uri imageUri) {
+                public void onTaskDone(String filePath, final Uri imageUri) {
+                    Log.v(TAG, "onTaskDone:" + filePath);
+                    mMainHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            int doneID = mDataAdapter.findDataByContentUri(imageUri);
+                            int currentDataId = mFilmStripView.getCurrentId();
+
+                            if (currentDataId == doneID) {
+                                hidePanoStitchingProgress();
+                                updateStitchingProgress(0);
+                            }
+
+                            mDataAdapter.refresh(getContentResolver(), imageUri);
+                        }
+                    });
                 }
 
                 @Override
                 public void onTaskProgress(
-                        String filePath, Uri imageUri, int progress) {
+                        String filePath, final Uri imageUri, final int progress) {
+                    mMainHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            int currentDataId = mFilmStripView.getCurrentId();
+                            if (currentDataId == -1) {
+                                return;
+                            }
+                            if (imageUri.equals(
+                                    mDataAdapter.getLocalData(currentDataId).getContentUri())) {
+                                updateStitchingProgress(progress);
+                            }
+                        }
+                    });
                 }
             };
 
@@ -207,6 +284,8 @@
         } else if (mimeType.startsWith("image/")) {
             Util.broadcastNewPicture(this, uri);
             mDataAdapter.addNewPhoto(cr, uri);
+        } else if (mimeType.startsWith("application/stitching-preview")) {
+            mDataAdapter.addNewPhoto(cr, uri);
         } else {
             android.util.Log.w(TAG, "Unknown new media with MIME type:"
                     + mimeType + ", uri:" + uri);
@@ -276,9 +355,12 @@
         LayoutInflater inflater = getLayoutInflater();
         View rootLayout = inflater.inflate(R.layout.camera, null, false);
         mRootView = rootLayout.findViewById(R.id.camera_app_root);
+        mPanoStitchingPanel = (View) findViewById(R.id.pano_stitching_progress_panel);
+        mBottomProgress = (ProgressBar) findViewById(R.id.pano_stitching_progress_bar);
         mCameraPreviewData = new CameraPreviewData(rootLayout,
                 FilmStripView.ImageData.SIZE_FULL,
                 FilmStripView.ImageData.SIZE_FULL);
+        // Put a CameraPreviewData at the first position.
         mWrappedDataAdapter = new FixedFirstDataAdapter(
                 new CameraDataAdapter(new ColorDrawable(
                         getResources().getColor(R.color.photo_placeholder))),
@@ -296,6 +378,25 @@
         mOrientationListener = new MyOrientationEventListener(this);
         mMainHandler = new Handler(getMainLooper());
         bindMediaSaveService();
+
+        if (!mSecureCamera) {
+            mDataAdapter = mWrappedDataAdapter;
+            mDataAdapter.requestLoad(getContentResolver());
+        } else {
+            // Put a lock placeholder as the last image by setting its date to 0.
+            ImageView v = (ImageView) getLayoutInflater().inflate(
+                    R.layout.secure_album_placeholder, null);
+            mDataAdapter = new FixedLastDataAdapter(
+                    mWrappedDataAdapter,
+                    new LocalData.LocalViewData(
+                            v,
+                            v.getDrawable().getIntrinsicWidth(),
+                            v.getDrawable().getIntrinsicHeight(),
+                            0, 0));
+            // Flush out all the original data.
+            mDataAdapter.flush();
+        }
+        mFilmStripView.setDataAdapter(mDataAdapter);
     }
 
     private void setRotationAnimation() {
@@ -343,25 +444,6 @@
     public void onStart() {
         super.onStart();
 
-        // The loading is done in background and will update the filmstrip later.
-        if (!mSecureCamera) {
-            mDataAdapter = mWrappedDataAdapter;
-            mDataAdapter.requestLoad(getContentResolver());
-            mFilmStripView.setDataAdapter(mDataAdapter);
-        } else {
-            // Put a lock placeholder as the last image by setting its date to 0.
-            ImageView v = (ImageView) getLayoutInflater().inflate(
-                    R.layout.secure_album_placeholder, null);
-            mDataAdapter = new FixedLastDataAdapter(
-                    mWrappedDataAdapter,
-                    new LocalData.LocalViewData(
-                            v,
-                            v.getDrawable().getIntrinsicWidth(),
-                            v.getDrawable().getIntrinsicHeight(),
-                            0, 0));
-            // Flush out all the original data.
-            mDataAdapter.flush();
-        }
         mPanoramaViewHelper.onStart();
     }
 
diff --git a/src/com/android/camera/ImageTaskManager.java b/src/com/android/camera/ImageTaskManager.java
index 1324942..601de4c 100644
--- a/src/com/android/camera/ImageTaskManager.java
+++ b/src/com/android/camera/ImageTaskManager.java
@@ -21,7 +21,7 @@
 /**
  * The interface for background image processing task manager.
  */
-interface ImageTaskManager {
+public interface ImageTaskManager {
 
     /**
      * Callback interface for task events.
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 89833b3..a61c267 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -65,13 +65,13 @@
 import com.android.camera.CameraManager.CameraPictureCallback;
 import com.android.camera.CameraManager.CameraProxy;
 import com.android.camera.CameraManager.CameraShutterCallback;
-import com.android.camera.support.common.ApiHelper;
 import com.android.camera.support.filtershow.crop.CropExtras;
 import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
 import com.android.camera.ui.PopupManager;
 import com.android.camera.ui.RotateTextToast;
 import com.android.camera.util.UsageStatistics;
 import com.android.camera2.R;
+import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.exif.ExifInterface;
 import com.android.gallery3d.exif.ExifTag;
 import com.android.gallery3d.exif.Rational;
@@ -103,7 +103,6 @@
     private static final int START_PREVIEW_DONE = 10;
     private static final int OPEN_CAMERA_FAIL = 11;
     private static final int CAMERA_DISABLED = 12;
-    private static final int CAPTURE_ANIMATION_DONE = 13;
 
     // The subset of parameters we need to update in setCameraParameters().
     private static final int UPDATE_PARAM_INITIALIZE = 1;
@@ -170,13 +169,6 @@
         }
     };
 
-    private Runnable mFlashRunnable = new Runnable() {
-        @Override
-        public void run() {
-            animateFlash();
-        }
-    };
-
     private final StringBuilder mBuilder = new StringBuilder();
     private final Formatter mFormatter = new Formatter(mBuilder);
     private final Object[] mFormatterArgs = new Object[1];
@@ -394,10 +386,6 @@
                             R.string.camera_disabled);
                     break;
                 }
-                case CAPTURE_ANIMATION_DONE: {
-                    mUI.enablePreviewThumb(false);
-                    break;
-                }
             }
         }
     }
@@ -684,10 +672,10 @@
     private final class ShutterCallback
             implements CameraShutterCallback {
 
-        private boolean mAnimateFlash;
+        private boolean mNeedsAnimation;
 
-        public ShutterCallback(boolean animateFlash) {
-            mAnimateFlash = animateFlash;
+        public ShutterCallback(boolean needsAnimation) {
+            mNeedsAnimation = needsAnimation;
         }
 
         @Override
@@ -695,8 +683,13 @@
             mShutterCallbackTime = System.currentTimeMillis();
             mShutterLag = mShutterCallbackTime - mCaptureStartTime;
             Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
-            if (mAnimateFlash) {
-                mActivity.runOnUiThread(mFlashRunnable);
+            if (mNeedsAnimation) {
+                mActivity.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        animateAfterShutter();
+                    }
+                });
             }
         }
     }
@@ -735,9 +728,11 @@
             if (mPaused) {
                 return;
             }
-            //TODO: We should show the picture taken rather than frozen preview here
             if (mIsImageCaptureIntent) {
                 stopPreview();
+            } else {
+                // Animate capture with real jpeg data instead of a preview frame.
+                mUI.animateCapture(jpegData);
             }
             if (mSceneMode == Util.SCENE_MODE_HDR) {
                 mUI.showSwitcher();
@@ -762,18 +757,6 @@
             Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
                     + mPictureDisplayedToJpegCallbackTime + "ms");
 
-             /*TODO:
-            // Only animate when in full screen capture mode
-            // i.e. If monkey/a user swipes to the gallery during picture taking,
-            // don't show animation
-            if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
-                    && mActivity.mShowCameraAppView) {
-                // Finish capture animation
-                mHandler.removeMessages(CAPTURE_ANIMATION_DONE);
-                ((CameraScreenNail) mActivity.mCameraScreenNail).animateSlide();
-                mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
-                        CaptureAnimManager.getAnimationDuration());
-            } */
             mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
             if (!mIsImageCaptureIntent) {
                 if (ApiHelper.CAN_START_PREVIEW_IN_JPEG_CALLBACK) {
@@ -919,16 +902,12 @@
         }
     }
 
-    private void animateFlash() {
+    private void animateAfterShutter() {
         // Only animate when in full screen capture mode
         // i.e. If monkey/a user swipes to the gallery during picture taking,
         // don't show animation
         if (!mIsImageCaptureIntent) {
             mUI.animateFlash();
-
-            // TODO: mUI.enablePreviewThumb(true);
-            // mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
-            //        CaptureAnimManager.getAnimationDuration());
         }
     }
 
@@ -948,7 +927,7 @@
         final boolean animateBefore = (mSceneMode == Util.SCENE_MODE_HDR);
 
         if (animateBefore) {
-            animateFlash();
+            animateAfterShutter();
         }
 
         // Set rotation and gps data.
@@ -1130,7 +1109,7 @@
                 newExtras.putBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, true);
             }
 
-            // TODO ...
+            // TODO: Share this constant.
             final String CROP_ACTION = "com.android.camera.action.CROP";
             Intent cropIntent = new Intent(CROP_ACTION);
 
diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java
index e10570a..a0e8b9b 100644
--- a/src/com/android/camera/PhotoUI.java
+++ b/src/com/android/camera/PhotoUI.java
@@ -19,16 +19,15 @@
 
 import java.util.List;
 
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
 import android.app.AlertDialog;
 import android.content.DialogInterface;
+import android.graphics.Bitmap;
 import android.graphics.Matrix;
 import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
 import android.hardware.Camera.Face;
 import android.hardware.Camera.Size;
+import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.Message;
 import android.util.Log;
@@ -41,6 +40,7 @@
 import android.view.ViewStub;
 import android.widget.FrameLayout;
 import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
 import android.widget.Toast;
 
 import com.android.camera.CameraPreference.OnPreferenceChangedListener;
@@ -69,6 +69,8 @@
 
     private static final String TAG = "CAM_UI";
     private static final int UPDATE_TRANSFORM_MATRIX = 1;
+    private static final int DOWN_SAMPLE_FACTOR = 4;
+    private final AnimationManager mAnimationManager;
     private CameraActivity mActivity;
     private PhotoController mController;
     private PreviewGestures mGestures;
@@ -108,8 +110,7 @@
     private float mSurfaceTextureUncroppedWidth;
     private float mSurfaceTextureUncroppedHeight;
 
-    private View mPreviewThumb;
-    private ObjectAnimator mFlashAnim;
+    private ImageView mPreviewThumb;
     private View mFlashOverlay;
 
     private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener;
@@ -156,25 +157,25 @@
         }
     };
 
-    private ValueAnimator.AnimatorListener mAnimatorListener =
-            new ValueAnimator.AnimatorListener() {
+    private class DecodeTask extends AsyncTask<Integer, Void, Bitmap> {
+        private final byte [] mData;
 
-        @Override
-        public void onAnimationCancel(Animator arg0) {}
-
-        @Override
-        public void onAnimationEnd(Animator arg0) {
-            mFlashOverlay.setAlpha(0f);
-            mFlashOverlay.setVisibility(View.GONE);
-            mFlashAnim.removeListener(this);
+        public DecodeTask(byte[] data) {
+            mData = data;
         }
 
         @Override
-        public void onAnimationRepeat(Animator arg0) {}
+        protected Bitmap doInBackground(Integer... params) {
+            // Decode image in background.
+            return Util.downSample(mData, DOWN_SAMPLE_FACTOR);
+        }
 
         @Override
-        public void onAnimationStart(Animator arg0) {}
-    };
+        protected void onPostExecute(Bitmap bitmap) {
+            mPreviewThumb.setImageBitmap(bitmap);
+            mAnimationManager.startCaptureAnimation(mPreviewThumb);
+        }
+    }
 
     public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
         mActivity = activity;
@@ -208,6 +209,7 @@
         }
         mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls);
         ((CameraRootView) mRootView).setDisplayChangeListener(this);
+        mAnimationManager = new AnimationManager();
     }
 
     public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) {
@@ -328,6 +330,12 @@
         updateOnScreenIndicators(params, prefGroup, prefs);
     }
 
+    public void animateCapture(final byte[] jpegData) {
+        // Decode jpeg byte array and then animate the jpeg
+        DecodeTask task = new DecodeTask(jpegData);
+        task.execute();
+    }
+
     private void openMenu() {
         if (mPieRenderer != null) {
             // If autofocus is not finished, cancel autofocus so that the
@@ -341,7 +349,7 @@
 
     public void initializeControlByIntent() {
         mBlocker = mRootView.findViewById(R.id.blocker);
-        mPreviewThumb = mRootView.findViewById(R.id.preview_thumb);
+        mPreviewThumb = (ImageView) mRootView.findViewById(R.id.preview_thumb);
         mPreviewThumb.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
@@ -498,16 +506,7 @@
     }
 
     public void animateFlash() {
-        // End the previous animation if the previous one is still running
-        if (mFlashAnim != null && mFlashAnim.isRunning()) {
-            mFlashAnim.end();
-        }
-        // Start new flash animation.
-        mFlashOverlay.setVisibility(View.VISIBLE);
-        mFlashAnim = ObjectAnimator.ofFloat((Object) mFlashOverlay, "alpha", 0.3f, 0f);
-        mFlashAnim.setDuration(300);
-        mFlashAnim.addListener(mAnimatorListener);
-        mFlashAnim.start();
+        mAnimationManager.startFlashAnimation(mFlashOverlay);
     }
 
     public void enableGestures(boolean enable) {
@@ -570,14 +569,6 @@
         if (!toCamera && mCountDownView != null) mCountDownView.cancelCountDown();
     }
 
-    public void enablePreviewThumb(boolean enabled) {
-        if (enabled) {
-            mPreviewThumb.setVisibility(View.VISIBLE);
-        } else {
-            mPreviewThumb.setVisibility(View.GONE);
-        }
-    }
-
     public boolean removeTopLevelPopup() {
         // Remove the top level popup or dialog box and return true if there's any
         if (mPopup != null) {
diff --git a/src/com/android/camera/Util.java b/src/com/android/camera/Util.java
index 43ad22e..11176a7 100644
--- a/src/com/android/camera/Util.java
+++ b/src/com/android/camera/Util.java
@@ -24,7 +24,6 @@
 import java.util.List;
 import java.util.StringTokenizer;
 
-import com.android.camera2.R;
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.AlertDialog;
@@ -62,7 +61,8 @@
 import android.view.animation.Animation;
 import android.widget.Toast;
 
-import com.android.camera.support.common.ApiHelper;
+import com.android.camera2.R;
+import com.android.gallery3d.common.ApiHelper;
 
 /**
  * Collection of utility functions used in this package.
@@ -89,8 +89,8 @@
     public static final String TRUE = "true";
     public static final String FALSE = "false";
     
-    /** Has to by in sync with the receiving MovieActivity. */
-    public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";
+	/** Has to be in sync with the receiving MovieActivity. */
+	public static final String KEY_TREAT_UP_AS_BACK = "treat-up-as-back";
 
     public static boolean isSupported(String value, List<String> supported) {
         return supported == null ? false : supported.indexOf(value) >= 0;
@@ -711,6 +711,19 @@
         return rotation;
     }
 
+    /**
+     * Down-samples a jpeg byte array.
+     * @param data a byte array of jpeg data
+     * @param downSampleFactor down-sample factor
+     * @return decoded and down-sampled bitmap
+     */
+    public static Bitmap downSample(final byte[] data, int downSampleFactor) {
+        final BitmapFactory.Options opts = new BitmapFactory.Options();
+        // Downsample the image
+        opts.inSampleSize = downSampleFactor;
+        return BitmapFactory.decodeByteArray(data, 0, data.length, opts);
+    }
+
     public static void setGpsParameters(Parameters parameters, Location loc) {
         // Clear previous GPS location from the parameters.
         parameters.removeGpsData();
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index aa085af..1516cb5 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -92,8 +92,6 @@
     private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
     private static final int SWITCH_CAMERA = 8;
     private static final int SWITCH_CAMERA_START_ANIMATION = 9;
-    private static final int HIDE_SURFACE_VIEW = 10;
-    private static final int CAPTURE_ANIMATION_DONE = 11;
 
     private static final int SCREEN_DELAY = 2 * 60 * 1000;
 
@@ -107,7 +105,6 @@
     private static final String EXTRA_QUICK_CAPTURE =
             "android.intent.extra.quickCapture";
 
-    private static final int MIN_THUMB_SIZE = 64;
     // module fields
     private CameraActivity mActivity;
     private boolean mPaused;
@@ -200,9 +197,7 @@
                 @Override
                 public void onMediaSaved(Uri uri) {
                     if (uri != null) {
-                        mActivity.sendBroadcast(
-                                new Intent(Util.ACTION_NEW_VIDEO, uri));
-                        Util.broadcastNewPicture(mActivity, uri);
+                        mActivity.notifyNewMedia(uri);
                     }
                 }
             };
@@ -212,7 +207,7 @@
                 @Override
                 public void onMediaSaved(Uri uri) {
                     if (uri != null) {
-                        Util.broadcastNewPicture(mActivity, uri);
+                        mActivity.notifyNewMedia(uri);
                     }
                 }
             };
@@ -295,11 +290,6 @@
                     break;
                 }
 
-                case CAPTURE_ANIMATION_DONE: {
-                    mUI.enablePreviewThumb(false);
-                    break;
-                }
-
                 default:
                     Log.v(TAG, "Unhandled message: " + msg.what);
                     break;
@@ -555,16 +545,12 @@
                 // back to use SurfaceTexture for preview and we need to stop then start
                 // the preview. This will cause the preview flicker since the preview
                 // will not be continuous for a short period of time.
-                // TODO: need to get the capture animation to work
-                // ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
 
-                mUI.enablePreviewThumb(true);
-
-                // Make sure to disable the thumbnail preview after the
-                // animation is done to disable the click target.
-                mHandler.removeMessages(CAPTURE_ANIMATION_DONE);
-                mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE,
-                        CaptureAnimManager.getAnimationDuration());
+                mUI.animateFlash();
+                Bitmap bitmap = getVideoThumbnail();
+                if (bitmap != null) {
+                    mUI.animateCapture(bitmap);
+                }
             }
         }
     }
@@ -1425,7 +1411,7 @@
 
     private void startVideoRecording() {
         Log.v(TAG, "startVideoRecording");
-        mUI.enablePreviewThumb(false);
+        mUI.cancelAnimations();
         mUI.setSwipingEnabled(false);
 
         mActivity.updateStorageSpaceAndHint();
@@ -1502,8 +1488,7 @@
                 UsageStatistics.ACTION_CAPTURE_START, "Video");
     }
 
-    private void showCaptureResult() {
-        mIsInReviewMode = true;
+    private Bitmap getVideoThumbnail() {
         Bitmap bitmap = null;
         if (mVideoFileDescriptor != null) {
             bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
@@ -1518,9 +1503,16 @@
             CameraInfo[] info = CameraHolder.instance().getCameraInfo();
             boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
             bitmap = Util.rotateAndMirror(bitmap, 0, mirror);
+        }
+        return bitmap;
+    }
+
+    private void showCaptureResult() {
+        mIsInReviewMode = true;
+        Bitmap bitmap = getVideoThumbnail();
+        if (bitmap != null) {
             mUI.showReviewImage(bitmap);
         }
-
         mUI.showReviewControls();
         mUI.enableCameraControls(false);
         mUI.showTimeLapseUI(false);
@@ -2086,7 +2078,7 @@
         if (mParameters == null) return;
         if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
             if (enabled) {
-             // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
+                mUI.animateFlash();
             } else {
                 mUI.showPreviewBorder(enabled);
             }
diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java
index 06c9795..1f6505f 100644
--- a/src/com/android/camera/VideoUI.java
+++ b/src/com/android/camera/VideoUI.java
@@ -91,6 +91,7 @@
     private int mZoomMax;
     private List<Integer> mZoomRatios;
     private View mPreviewThumb;
+    private View mFlashOverlay;
 
     private SurfaceView mSurfaceView = null;
     private int mPreviewWidth = 0;
@@ -99,6 +100,7 @@
     private float mSurfaceTextureUncroppedHeight;
     private float mAspectRatio = 4f / 3f;
     private Matrix mMatrix = null;
+    private final AnimationManager mAnimationManager;
     private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {
@@ -141,6 +143,7 @@
         mTextureView.setSurfaceTextureListener(this);
         mRootView.addOnLayoutChangeListener(mLayoutListener);
         ((CameraRootView) mRootView).setDisplayChangeListener(this);
+        mFlashOverlay = mRootView.findViewById(R.id.flash_overlay);
         mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
         mSwitcher = (CameraSwitcher) mRootView.findViewById(R.id.camera_switcher);
         mSwitcher.setCurrentIndex(CameraSwitcher.VIDEO_MODULE_INDEX);
@@ -148,6 +151,7 @@
         initializeMiscControls();
         initializeControlByIntent();
         initializeOverlay();
+        mAnimationManager = new AnimationManager();
     }
 
 
@@ -266,6 +270,29 @@
         }
     }
 
+    /**
+     * Starts a flash animation
+     */
+    public void animateFlash() {
+        mAnimationManager.startFlashAnimation(mFlashOverlay);
+    }
+
+    /**
+     * Starts a capture animation
+     * @param bitmap the captured image that we shrink and slide in the animation
+     */
+    public void animateCapture(Bitmap bitmap) {
+        ((ImageView) mPreviewThumb).setImageBitmap(bitmap);
+        mAnimationManager.startCaptureAnimation(mPreviewThumb);
+    }
+
+    /**
+     * Cancels on-going animations
+     */
+    public void cancelAnimations() {
+        mAnimationManager.cancelAnimations();
+    }
+
     public void hideUI() {
         mCameraControls.setVisibility(View.INVISIBLE);
         mSwitcher.closePopup();
@@ -623,17 +650,6 @@
         mController.updateCameraOrientation();
     }
 
-    /**
-     * Enable or disable the preview thumbnail for click events.
-     */
-    public void enablePreviewThumb(boolean enabled) {
-        if (enabled) {
-            mPreviewThumb.setVisibility(View.VISIBLE);
-        } else {
-            mPreviewThumb.setVisibility(View.GONE);
-        }
-    }
-
     private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
         @Override
         public void onZoomValueChanged(int index) {
diff --git a/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java b/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java
index 66c5585..5df87f5 100644
--- a/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java
+++ b/src/com/android/camera/data/AbstractLocalDataAdapterWrapper.java
@@ -74,6 +74,11 @@
     }
 
     @Override
+    public void insertData(LocalData data) {
+        mAdapter.insertData(data);
+    }
+
+    @Override
     public void flush() {
         mAdapter.flush();
     }
@@ -87,4 +92,9 @@
     public boolean undoDataRemoval() {
         return mAdapter.undoDataRemoval();
     }
+
+    @Override
+    public void refresh(ContentResolver resolver, Uri uri) {
+        mAdapter.refresh(resolver, uri);
+    }
 }
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
index e384b15..aaf5ebe 100644
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -16,11 +16,6 @@
 
 package com.android.camera.data;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
@@ -34,11 +29,16 @@
 import com.android.camera.Storage;
 import com.android.camera.ui.FilmStripView.ImageData;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
 /**
  * A {@link LocalDataAdapter} that provides data in the camera folder.
  */
 public class CameraDataAdapter implements LocalDataAdapter {
-    private static final String TAG = CameraDataAdapter.class.getSimpleName();
+    private static final String TAG = "CAM_CameraDataAdapter";
 
     private static final int DEFAULT_DECODE_SIZE = 3000;
     private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" };
@@ -64,6 +64,15 @@
     }
 
     @Override
+    public LocalData getLocalData(int dataID) {
+        if (mImages == null || dataID < 0 || dataID >= mImages.size()) {
+            return null;
+        }
+
+        return mImages.get(dataID);
+    }
+
+    @Override
     public int getTotalNumber() {
         if (mImages == null) {
             return 0;
@@ -73,7 +82,7 @@
 
     @Override
     public ImageData getImageData(int id) {
-        return getData(id);
+        return getLocalData(id);
     }
 
     @Override
@@ -116,11 +125,6 @@
     }
 
     @Override
-    public void onDataCentered(int dataID, boolean centered) {
-        // do nothing.
-    }
-
-    @Override
     public boolean canSwipeInFullScreen(int dataID) {
         if (dataID < mImages.size() && dataID > 0) {
             return mImages.get(dataID).canSwipeInFullScreen();
@@ -138,49 +142,60 @@
         mListener.onDataRemoved(dataID, d);
     }
 
-    private void insertData(LocalData data) {
-        if (mImages == null) {
-            mImages = new ArrayList<LocalData>();
-        }
-
-        // Since this function is mostly for adding the newest data,
-        // a simple linear search should yield the best performance over a
-        // binary search.
-        int pos = 0;
-        Comparator<LocalData> comp = new LocalData.NewestFirstComparator();
-        for (; pos < mImages.size()
-                && comp.compare(data, mImages.get(pos)) > 0; pos++);
-        mImages.add(pos, data);
-        if (mListener != null) {
-            mListener.onDataInserted(pos, data);
-        }
-    }
-
+    // TODO: put the database query on background thread
     @Override
     public void addNewVideo(ContentResolver cr, Uri uri) {
         Cursor c = cr.query(uri,
                 LocalData.Video.QUERY_PROJECTION,
                 MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
                 LocalData.Video.QUERY_ORDER);
-        if (c != null && c.moveToFirst()) {
-            insertData(LocalData.Video.buildFromCursor(c));
+        if (c == null || !c.moveToFirst()) {
+            return;
+        }
+        int pos = findDataByContentUri(uri);
+        LocalData.Video newData = LocalData.Video.buildFromCursor(c);
+        if (pos != -1) {
+            // A duplicate one, just do a substitute.
+            updateData(pos, newData);
+        } else {
+            // A new data.
+            insertData(newData);
         }
     }
 
+    // TODO: put the database query on background thread
     @Override
     public void addNewPhoto(ContentResolver cr, Uri uri) {
         Cursor c = cr.query(uri,
                 LocalData.Photo.QUERY_PROJECTION,
                 MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
                 LocalData.Photo.QUERY_ORDER);
-        if (c != null && c.moveToFirst()) {
-            insertData(LocalData.Photo.buildFromCursor(c));
+        if (c == null || !c.moveToFirst()) {
+            return;
+        }
+        int pos = findDataByContentUri(uri);
+        LocalData.Photo newData = LocalData.Photo.buildFromCursor(c);
+        if (pos != -1) {
+            // a duplicate one, just do a substitute.
+            Log.v(TAG, "found duplicate photo");
+            updateData(pos, newData);
+        } else {
+            // a new data.
+            insertData(newData);
         }
     }
 
     @Override
     public int findDataByContentUri(Uri uri) {
-        // TODO: find the data.
+        for (int i = 0; i < mImages.size(); i++) {
+            Uri u = mImages.get(i).getContentUri();
+            if (u == null) {
+                continue;
+            }
+            if (u.equals(uri)) {
+                return i;
+            }
+        }
         return -1;
     }
 
@@ -208,51 +223,62 @@
         replaceData(null);
     }
 
-    private LocalData getData(int id) {
-        if (mImages == null || id >= mImages.size() || id < 0) {
-            return null;
+    @Override
+    public void refresh(ContentResolver resolver, Uri contentUri) {
+        int pos = findDataByContentUri(contentUri);
+        if (pos == -1) {
+            return;
         }
-        return mImages.get(id);
+
+        LocalData data = mImages.get(pos);
+        if (data.refresh(resolver)) {
+            updateData(pos, data);
+        }
     }
 
-    // Update all the data but keep the camera data if already set.
-    private void replaceData(List<LocalData> list) {
-        boolean changed = (list != mImages);
-        LocalData cameraData = null;
-        if (mImages != null && mImages.size() > 0) {
-            cameraData = mImages.get(0);
-            if (cameraData.getType() != ImageData.TYPE_CAMERA_PREVIEW) {
-                cameraData = null;
-            }
+    @Override
+    public void updateData(final int pos, LocalData data) {
+        mImages.set(pos, data);
+        if (mListener != null) {
+            mListener.onDataUpdated(new UpdateReporter() {
+                @Override
+                public boolean isDataRemoved(int dataID) {
+                    return false;
+                }
+
+                @Override
+                public boolean isDataUpdated(int dataID) {
+                    return (dataID == pos);
+                }
+            });
+        }
+    }
+
+    @Override
+    public void insertData(LocalData data) {
+        if (mImages == null) {
+            mImages = new ArrayList<LocalData>();
         }
 
-        mImages = list;
-        if (cameraData != null) {
-            // camera view exists, so we make sure at least 1 data is in the list.
-            if (mImages == null) {
-                mImages = new ArrayList<LocalData>();
-            }
-            mImages.add(0, cameraData);
-            if (mListener != null) {
-                // Only the camera data is not changed, everything else is changed.
-                mListener.onDataUpdated(new UpdateReporter() {
-                    @Override
-                    public boolean isDataRemoved(int id) {
-                        return false;
-                    }
+        // Since this function is mostly for adding the newest data,
+        // a simple linear search should yield the best performance over a
+        // binary search.
+        int pos = 0;
+        Comparator<LocalData> comp = new LocalData.NewestFirstComparator();
+        for (; pos < mImages.size()
+                && comp.compare(data, mImages.get(pos)) > 0; pos++);
+        mImages.add(pos, data);
+        if (mListener != null) {
+            mListener.onDataInserted(pos, data);
+        }
+    }
 
-                    @Override
-                    public boolean isDataUpdated(int id) {
-                        if (id == 0) return false;
-                        return true;
-                    }
-                });
-            }
-        } else {
-            // both might be null.
-            if (changed) {
-                mListener.onDataLoaded();
-            }
+    /** Update all the data */
+    private void replaceData(List<LocalData> list) {
+        boolean changed = (list != mImages);
+        mImages = list;
+        if (changed) {
+            mListener.onDataLoaded();
         }
     }
 
diff --git a/src/com/android/camera/data/FixedFirstDataAdapter.java b/src/com/android/camera/data/FixedFirstDataAdapter.java
index 34ba0a1..2bff22a 100644
--- a/src/com/android/camera/data/FixedFirstDataAdapter.java
+++ b/src/com/android/camera/data/FixedFirstDataAdapter.java
@@ -32,7 +32,7 @@
 public class FixedFirstDataAdapter extends AbstractLocalDataAdapterWrapper
         implements DataAdapter.Listener {
 
-    private final LocalData mFirstData;
+    private LocalData mFirstData;
     private Listener mListener;
 
     /**
@@ -53,6 +53,14 @@
     }
 
     @Override
+    public LocalData getLocalData(int dataID) {
+        if (dataID == 0) {
+            return mFirstData;
+        }
+        return mAdapter.getLocalData(dataID - 1);
+    }
+
+    @Override
     public void removeData(Context context, int dataID) {
         if (dataID > 0) {
             mAdapter.removeData(context, dataID - 1);
@@ -69,6 +77,28 @@
     }
 
     @Override
+    public void updateData(int pos, LocalData data) {
+        if (pos == 0) {
+            mFirstData = data;
+            if (mListener != null) {
+                mListener.onDataUpdated(new UpdateReporter() {
+                    @Override
+                    public boolean isDataRemoved(int dataID) {
+                        return false;
+                    }
+
+                    @Override
+                    public boolean isDataUpdated(int dataID) {
+                        return (dataID == 0);
+                    }
+                });
+            }
+        } else {
+            mAdapter.updateData(pos - 1, data);
+        }
+    }
+
+    @Override
     public int getTotalNumber() {
         return (mAdapter.getTotalNumber() + 1);
     }
@@ -100,15 +130,6 @@
     }
 
     @Override
-    public void onDataCentered(int dataID, boolean centered) {
-        if (dataID != 0) {
-            mAdapter.onDataCentered(dataID, centered);
-        } else {
-            // TODO: notify the data
-        }
-    }
-
-    @Override
     public void setListener(Listener listener) {
         mListener = listener;
         mAdapter.setListener((listener == null) ? null : this);
@@ -132,12 +153,12 @@
         mListener.onDataUpdated(new UpdateReporter() {
             @Override
             public boolean isDataRemoved(int dataID) {
-                return reporter.isDataRemoved(dataID + 1);
+                return reporter.isDataRemoved(dataID - 1);
             }
 
             @Override
             public boolean isDataUpdated(int dataID) {
-                return reporter.isDataUpdated(dataID + 1);
+                return reporter.isDataUpdated(dataID - 1);
             }
         });
     }
diff --git a/src/com/android/camera/data/FixedLastDataAdapter.java b/src/com/android/camera/data/FixedLastDataAdapter.java
index 16c047d..b8325ec 100644
--- a/src/com/android/camera/data/FixedLastDataAdapter.java
+++ b/src/com/android/camera/data/FixedLastDataAdapter.java
@@ -29,7 +29,8 @@
  */
 public class FixedLastDataAdapter extends AbstractLocalDataAdapterWrapper {
 
-    private final LocalData mLastData;
+    private LocalData mLastData;
+    private Listener mListener;
 
     /**
      * Constructor.
@@ -48,6 +49,25 @@
     }
 
     @Override
+    public void setListener(Listener listener) {
+        super.setListener(listener);
+        mListener = listener;
+    }
+
+    @Override
+    public LocalData getLocalData(int dataID) {
+        int totalNumber = mAdapter.getTotalNumber();
+
+        if (dataID < totalNumber) {
+            return mAdapter.getLocalData(dataID);
+        } else if (dataID == totalNumber) {
+            return mLastData;
+        }
+
+        return null;
+    }
+
+    @Override
     public void removeData(Context context, int dataID) {
         if (dataID < mAdapter.getTotalNumber()) {
             mAdapter.removeData(context, dataID);
@@ -60,6 +80,30 @@
     }
 
     @Override
+    public void updateData(final int pos, LocalData data) {
+        int totalNumber = mAdapter.getTotalNumber();
+
+        if (pos < totalNumber) {
+            mAdapter.updateData(pos, data);
+        } else if (pos == totalNumber) {
+            mLastData = data;
+            if (mListener != null) {
+                mListener.onDataUpdated(new UpdateReporter() {
+                    @Override
+                    public boolean isDataRemoved(int dataID) {
+                        return false;
+                    }
+
+                    @Override
+                    public boolean isDataUpdated(int dataID) {
+                        return (dataID == pos);
+                    }
+                });
+            }
+        }
+    }
+
+    @Override
     public int getTotalNumber() {
         return mAdapter.getTotalNumber() + 1;
     }
@@ -102,17 +146,6 @@
     }
 
     @Override
-    public void onDataCentered(int dataID, boolean centered) {
-        int totalNumber = mAdapter.getTotalNumber();
-
-        if (dataID < totalNumber) {
-            mAdapter.onDataCentered(dataID, centered);
-        } else if (dataID == totalNumber) {
-            // TODO: notify the data
-        }
-    }
-
-    @Override
     public boolean canSwipeInFullScreen(int dataID) {
         int totalNumber = mAdapter.getTotalNumber();
 
diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java
index eb2212c..10cf9ae 100644
--- a/src/com/android/camera/data/LocalData.java
+++ b/src/com/android/camera/data/LocalData.java
@@ -32,7 +32,6 @@
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.provider.MediaStore;
-import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Images.ImageColumns;
 import android.provider.MediaStore.Video.VideoColumns;
 import android.util.Log;
@@ -62,15 +61,65 @@
     public static final int ACTION_DELETE = (1 << 1);
 
     View getView(Context c, int width, int height, Drawable placeHolder);
+
+    /**
+     * Gets the date when this data is created. The returned date is also used
+     * for sorting data.
+     *
+     * @return The date when this data is created.
+     * @see {@link NewestFirstComparator}
+     */
     long getDateTaken();
+
+    /**
+     * Gets the date when this data is modified. The returned date is also used
+     * for sorting data.
+     *
+     * @return The date when this data is modified.
+     * @see {@link NewestFirstComparator}
+     */
     long getDateModified();
+
+    /** Gets the title of this data */
     String getTitle();
-    boolean isDataActionSupported(int action);
+
+    /**
+     * Checks if the data actions (delete/play ...) can be applied on this data.
+     *
+     * @param actions The actions to check.
+     * @return Whether all the actions are supported.
+     */
+    boolean isDataActionSupported(int actions);
+
     boolean delete(Context c);
+
     void onFullScreen(boolean fullScreen);
+
+    /** Returns {@code true} if it allows swipe to filmstrip in full screen. */
     boolean canSwipeInFullScreen();
+
+    /**
+     * Returns the path to the data on the storage.
+     *
+     * @return Empty path if there's none.
+     */
     String getPath();
 
+    /**
+     * Returns the content URI of this data item.
+     *
+     * @return {@code Uri.EMPTY} if not valid.
+     */
+    Uri getContentUri();
+
+    /**
+     * Refresh the data content.
+     *
+     * @param resolver {@link ContentResolver} to refresh the data.
+     * @return {@code true} if success, {@code false} otherwise.
+     */
+    boolean refresh(ContentResolver resolver);
+
     static class NewestFirstComparator implements Comparator<LocalData> {
 
         /** Compare taken/modified date of LocalData in descent order to make
@@ -103,7 +152,7 @@
     /**
      * A base class for all the local media files. The bitmap is loaded in
      * background thread. Subclasses should implement their own background
-     * loading thread by subclassing BitmapLoadTask and overriding
+     * loading thread by sub-classing BitmapLoadTask and overriding
      * doInBackground() to return a bitmap.
      */
     abstract static class LocalMediaData implements LocalData {
@@ -250,14 +299,6 @@
             }
         }
 
-        /**
-         * Returns the content URI of this data item.
-         */
-        private Uri getContentUri() {
-            Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI;
-            return baseUri.buildUpon().appendPath(String.valueOf(id)).build();
-        }
-
         @Override
         public abstract int getType();
 
@@ -399,6 +440,32 @@
         }
 
         @Override
+        public Uri getContentUri() {
+            Uri baseUri = CONTENT_URI;
+            return baseUri.buildUpon().appendPath(String.valueOf(id)).build();
+        }
+
+        @Override
+        public boolean refresh(ContentResolver resolver) {
+            Cursor c = resolver.query(
+                    getContentUri(), QUERY_PROJECTION, null, null, null);
+            if (c == null || !c.moveToFirst()) {
+                return false;
+            }
+            Photo newData = buildFromCursor(c);
+            id = newData.id;
+            title = newData.title;
+            mimeType = newData.mimeType;
+            dateTaken = newData.dateTaken;
+            dateModified = newData.dateModified;
+            path = newData.path;
+            orientation = newData.orientation;
+            width = newData.width;
+            height = newData.height;
+            return true;
+        }
+
+        @Override
         protected BitmapLoadTask getBitmapLoadTask(
                 ImageView v, int decodeWidth, int decodeHeight) {
             return new PhotoBitmapLoadTask(v, decodeWidth, decodeHeight);
@@ -492,8 +559,7 @@
             d.path = c.getString(COL_DATA);
             d.width = c.getInt(COL_WIDTH);
             d.height = c.getInt(COL_HEIGHT);
-            d.mPlayUri = CONTENT_URI.buildUpon()
-                    .appendPath(String.valueOf(d.id)).build();
+            d.mPlayUri = d.getContentUri();
             MediaMetadataRetriever retriever = new MediaMetadataRetriever();
             retriever.setDataSource(d.path);
             String rotation = retriever.extractMetadata(
@@ -543,6 +609,32 @@
         }
 
         @Override
+        public Uri getContentUri() {
+            Uri baseUri = CONTENT_URI;
+            return baseUri.buildUpon().appendPath(String.valueOf(id)).build();
+        }
+
+        @Override
+        public boolean refresh(ContentResolver resolver) {
+            Cursor c = resolver.query(
+                    getContentUri(), QUERY_PROJECTION, null, null, null);
+            if (c == null && !c.moveToFirst()) {
+                return false;
+            }
+            Video newData = buildFromCursor(c);
+            id = newData.id;
+            title = newData.title;
+            mimeType = newData.mimeType;
+            dateTaken = newData.dateTaken;
+            dateModified = newData.dateModified;
+            path = newData.path;
+            width = newData.width;
+            height = newData.height;
+            mPlayUri = newData.mPlayUri;
+            return true;
+        }
+
+        @Override
         public View getView(final Context ctx,
                 int decodeWidth, int decodeHeight, Drawable placeHolder) {
 
@@ -666,6 +758,16 @@
         }
 
         @Override
+        public Uri getContentUri() {
+            return Uri.EMPTY;
+        }
+
+        @Override
+        public boolean refresh(ContentResolver resolver) {
+            return false;
+        }
+
+        @Override
         public boolean isUIActionSupported(int action) {
             return false;
         }
diff --git a/src/com/android/camera/data/LocalDataAdapter.java b/src/com/android/camera/data/LocalDataAdapter.java
index 3b4f07d..0a5fde0 100644
--- a/src/com/android/camera/data/LocalDataAdapter.java
+++ b/src/com/android/camera/data/LocalDataAdapter.java
@@ -36,6 +36,14 @@
     public void requestLoad(ContentResolver resolver);
 
     /**
+     * Returns the specified {@link LocalData}.
+     *
+     * @param dataID The ID of the {@link LocalData} to get.
+     * @return The {@link LocalData} to get. {@code null} if not available.
+     */
+    public LocalData getLocalData(int dataID);
+
+    /**
      * Remove the data in the local camera folder.
      *
      * @param context       {@link Context} used to remove the data.
@@ -60,6 +68,14 @@
     public void addNewPhoto(ContentResolver resolver, Uri uri);
 
     /**
+     * Refresh the data by {@link Uri}.
+     *
+     * @param resolver {@link ContentResolver} used to refresh the data.
+     * @param uri The {@link Uri} of the data to refresh.
+     */
+    public void refresh(ContentResolver resolver, Uri uri);
+
+    /**
      * Finds the {@link LocalData} of the specified content Uri.
      *
      * @param Uri  The content Uri of the {@link LocalData}.
@@ -88,4 +104,15 @@
      * @return {@code true} if there are items in the queue, {@code false} otherwise.
      */
     public boolean undoDataRemoval();
+
+    /**
+     * Update the data in a specific position.
+     *
+     * @param pos The position of the data to be updated.
+     * @param data The new data.
+     */
+    public void updateData(int pos, LocalData data);
+
+    /** Insert a data. */
+    public void insertData(LocalData data);
 }
diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java
index 79f58c7..c21f2b2 100644
--- a/src/com/android/camera/ui/FilmStripView.java
+++ b/src/com/android/camera/ui/FilmStripView.java
@@ -38,7 +38,7 @@
 
 public class FilmStripView extends ViewGroup {
     @SuppressWarnings("unused")
-    private static final String TAG = "FilmStripView";
+    private static final String TAG = "CAM_FilmStripView";
 
     private static final int BUFFER_SIZE = 5;
     private static final int DURATION_GEOMETRY_ADJUST = 200;
@@ -102,7 +102,6 @@
         public static final int TYPE_NONE = 0;
         public static final int TYPE_CAMERA_PREVIEW = 1;
         public static final int TYPE_PHOTO = 2;
-        public static final int TYPE_VIDEO = 3;
 
         // Actions allowed to be performed on the image data.
         // The actions are defined bit-wise so we can use bit operations like
@@ -197,7 +196,7 @@
         }
 
         /**
-         * An interface which defines the listener for UI actions over
+         * An interface which defines the listener for data events over
          * {@link ImageData}.
          */
         public interface Listener {
@@ -246,7 +245,7 @@
         public void suggestViewSizeBound(int w, int h);
 
         /**
-         * Sets the listener for FilmStripView UI actions over the ImageData.
+         * Sets the listener for data events over the ImageData.
          *
          * @param listener The listener to use.
          */
@@ -263,16 +262,6 @@
         public void onDataFullScreen(int dataID, boolean fullScreen);
 
         /**
-         * The callback when the item is centered/off-centered.
-         * TODO: Calls this function actually.
-         *
-         * @param dataID      The ID of the image data.
-         * @param centered    {@code true} if the data is centered.
-         *                    {@code false} otherwise.
-         */
-        public void onDataCentered(int dataID, boolean centered);
-
-        /**
          * Returns {@code true} if the view of the data can be moved by swipe
          * gesture when in full-screen.
          *
@@ -310,6 +299,15 @@
          *                 {@code false}
          */
         public void onSwitchMode(boolean toCamera);
+
+        /**
+         * The callback when the item is centered/off-centered.
+         *
+         * @param dataID      The ID of the image data.
+         * @param current     {@code true} if the data is the current one.
+         *                    {@code false} otherwise.
+         */
+        public void onCurrentDataChanged(int dataID, boolean current);
     }
 
     /**
@@ -500,7 +498,7 @@
         return false;
     }
 
-    public int getCurrentType() {
+    private int getCurrentType() {
         if (mDataAdapter == null) {
             return ImageData.TYPE_NONE;
         }
@@ -653,8 +651,7 @@
     private void stepIfNeeded() {
         if (!inFilmStrip() && !inFullScreen()) {
             // The good timing to step to the next view is when everything is
-            // not in
-            // transition.
+            // not in transition.
             return;
         }
         int nearest = findTheNearestView(mCenterX);
@@ -662,6 +659,10 @@
         if (nearest == -1 || nearest == mCurrentInfo)
             return;
 
+        // Going to change the current info, notify the listener.
+        if (mListener != null) {
+            mListener.onCurrentDataChanged(mViewInfo[mCurrentInfo].getID(), false);
+        }
         int adjust = nearest - mCurrentInfo;
         if (adjust > 0) {
             for (int k = 0; k < adjust; k++) {
@@ -690,6 +691,9 @@
                 }
             }
         }
+        if (mListener != null) {
+            mListener.onCurrentDataChanged(mViewInfo[mCurrentInfo].getID(), true);
+        }
     }
 
     /** Don't go beyond the bound. */
@@ -741,7 +745,7 @@
     /**
      * @return The ID of the current item, or -1.
      */
-    private int getCurrentId() {
+    public int getCurrentId() {
         ViewInfo current = mViewInfo[mCurrentInfo];
         if (current == null) {
             return -1;