Merge "Show PhotoSphere stitching progress." into gb-ub-photos-carlsbad
diff --git a/res/layout/camera_filmstrip.xml b/res/layout/camera_filmstrip.xml
index 935f38a..d94a9d2 100644
--- a/res/layout/camera_filmstrip.xml
+++ b/res/layout/camera_filmstrip.xml
@@ -38,4 +38,41 @@
         android:visibility="gone"
         android:src="@drawable/ic_view_photosphere" />
 
-</FrameLayout>
\ No newline at end of file
+    <LinearLayout
+        android:id="@+id/pano_stitching_progress_panel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|center_horizontal"
+        android:paddingBottom="52dp"
+        android:paddingLeft="5dp"
+        android:paddingRight="5dp"
+        android:paddingTop="5dp"
+        android:visibility="gone"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/pano_stitching_progress_text"
+            android:text="@string/pano_progress_text"
+            android:textColor="#ffffffff"
+            android:textSize="14dp"
+            android:shadowColor="#ff000000"
+            android:shadowDx="0"
+            android:shadowDy="0"
+            android:shadowRadius="2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentRight="true"
+            android:paddingBottom="8dp"
+            android:visibility="visible"
+            android:layout_gravity="right"/>
+
+        <ProgressBar
+            android:id="@+id/pano_stitching_progress_bar"
+            style="@android:style/Widget.Holo.Light.ProgressBar.Horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="visible"
+            android:layout_gravity="bottom|center_horizontal" />
+    </LinearLayout>
+
+</FrameLayout>
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 7f71d5f..7114f61 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;
@@ -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,6 +355,8 @@
         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);
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/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 3605f71..aaf5ebe 100644
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -28,7 +28,6 @@
 
 import com.android.camera.Storage;
 import com.android.camera.ui.FilmStripView.ImageData;
-import com.android.gallery3d.util.LightCycleHelper.PanoramaViewHelper;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -39,7 +38,7 @@
  * 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 + "%" };
@@ -65,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;
@@ -74,7 +82,7 @@
 
     @Override
     public ImageData getImageData(int id) {
-        return getData(id);
+        return getLocalData(id);
     }
 
     @Override
@@ -117,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();
@@ -139,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;
     }
 
@@ -209,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 efccfe3..852066b 100644
--- a/src/com/android/camera/data/LocalData.java
+++ b/src/com/android/camera/data/LocalData.java
@@ -62,15 +62,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
@@ -101,16 +151,10 @@
     // Implementations below.
 
     /**
-<<<<<<< HEAD
      * 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.
-=======
-     * 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
-     * sub-classing BitmapLoadTask and overriding doInBackground() to return a bitmap.
->>>>>>> Add LocalDataAdapter and wrappers.
      */
     abstract static class LocalMediaData implements LocalData {
         protected long id;
@@ -256,14 +300,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();
 
@@ -405,6 +441,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);
@@ -498,8 +560,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(
@@ -549,6 +610,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) {
 
@@ -672,6 +759,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 8a1a85a..f7f3883 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;