Integrate camcorder review mode to ReviewImage.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 2db2e97..44c2f4a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -127,7 +127,14 @@
                 <category android:name="android.intent.category.SELECTED_ALTERNATIVE" />
             </intent-filter>
         </activity>
-
+        <activity android:name="ReviewImage"
+                android:label="@string/view_label"
+                android:screenOrientation="behind"
+                android:configChanges="orientation|keyboardHidden">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <activity android:name="ViewImage" 
                 android:label="@string/view_label"
                 android:screenOrientation="behind"
diff --git a/res/drawable/btn_camera_review.xml b/res/drawable/btn_camera_review.xml
new file mode 100644
index 0000000..777cf40
--- /dev/null
+++ b/res/drawable/btn_camera_review.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/btn_camera_review_pressed" />
+    <item android:state_window_focused="true"
+            android:state_focused="true"
+            android:drawable="@drawable/btn_camera_review_highlight" />
+    <item android:drawable="@drawable/btn_camera_review_normal" />
+</selector>
+
+
diff --git a/res/layout/camera.xml b/res/layout/camera.xml
index 5994bae..7fd57c7 100644
--- a/res/layout/camera.xml
+++ b/res/layout/camera.xml
@@ -18,8 +18,7 @@
         android:background="@drawable/camera_background"
         android:id="@+id/camera"
         android:layout_width="fill_parent"
-        android:layout_height="fill_parent"
-        android:orientation="horizontal">
+        android:layout_height="fill_parent">
 
     <include android:id="@+id/control_bar" layout="@layout/camera_control"/>
 
diff --git a/res/layout/review_control.xml b/res/layout/review_control.xml
new file mode 100644
index 0000000..aa44dd7
--- /dev/null
+++ b/res/layout/review_control.xml
@@ -0,0 +1,123 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/button_bar"
+        android:gravity="center_horizontal"
+        android:layout_height="fill_parent"
+        android:layout_width="wrap_content"
+        android:layout_marginRight="6dp"
+        android:layout_marginTop="13dp"
+        android:layout_marginBottom="10dp"
+        android:layout_alignParentRight="true">
+    <LinearLayout android:orientation="vertical"
+            android:gravity="top|center_horizontal"
+            android:layout_alignParentTop="true"
+            android:layout_height="wrap_content"
+            android:layout_width="wrap_content">
+        <LinearLayout android:orientation="vertical"
+                android:gravity="top|center_horizontal"
+                android:layout_marginBottom="15dp"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content">
+            <ImageView android:id="@+id/btn_delete"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:scaleType="center"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:src="@drawable/btn_ic_review_delete"
+                    android:background="@drawable/btn_camera_review"/>
+            <TextView android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:textSize="12sp"
+                    android:text="@string/camera_toss" />
+        </LinearLayout>
+        <LinearLayout android:orientation="vertical"
+                android:gravity="top|center_horizontal"
+                android:layout_marginBottom="15dp"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content">
+            <ImageView android:id="@+id/btn_share"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:scaleType="center"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:src="@drawable/btn_ic_review_share"
+                    android:background="@drawable/btn_camera_review"/>
+            <TextView android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:textSize="12sp"
+                    android:text="@string/camera_share" />
+        </LinearLayout>
+        <LinearLayout android:orientation="vertical"
+                android:gravity="top|center_horizontal"
+                android:layout_marginBottom="15dp"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content">
+            <ImageView android:id="@+id/btn_play"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:scaleType="center"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:src="@drawable/btn_ic_review_play"
+                    android:background="@drawable/btn_camera_review"/>
+            <TextView android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:textSize="12sp"
+                    android:text="@string/camera_play" />
+        </LinearLayout>
+        <LinearLayout android:orientation="vertical"
+                android:gravity="top|center_horizontal"
+                android:layout_marginBottom="15dp"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content">
+            <ImageView android:id="@+id/btn_set_as"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:scaleType="center"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:src="@drawable/btn_ic_review_set_as"
+                    android:background="@drawable/btn_camera_review"/>
+            <TextView android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:textSize="12sp"
+                    android:text="@string/camera_set" />
+        </LinearLayout>
+        <LinearLayout android:orientation="vertical"
+                android:gravity="top|center_horizontal"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content">
+            <ImageView android:id="@+id/btn_done"
+                    android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:scaleType="center"
+                    android:clickable="true"
+                    android:focusable="true"
+                    android:src="@drawable/btn_ic_review_done"
+                    android:background="@drawable/btn_camera_review"/>
+            <TextView android:layout_height="wrap_content"
+                    android:layout_width="wrap_content"
+                    android:textStyle="bold"
+                    android:textSize="12sp"
+                    android:text="@string/camera_done" />
+        </LinearLayout>
+    </LinearLayout>
+</RelativeLayout>
+
diff --git a/res/layout/review_image.xml b/res/layout/review_image.xml
new file mode 100644
index 0000000..e59ea58
--- /dev/null
+++ b/res/layout/review_image.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:background="@drawable/camera_background"
+        android:id="@+id/root"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent">
+
+    <include android:id="@+id/control_bar"
+            layout="@layout/review_control" />
+
+    <FrameLayout android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentLeft="true"
+            android:layout_centerVertical="true"
+            android:layout_marginLeft="13dp"
+            android:background="@drawable/border_view_finder">
+        <RelativeLayout android:id="@+id/mainPanel"
+                android:layout_toLeftOf="@id/button_bar"
+                android:layout_width="384dp"
+                android:layout_height="288dp">
+            <AbsoluteLayout android:id="@+id/slideShowContainer"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent">
+                <com.android.camera.ImageViewTouch2 android:id="@+id/image1_slideShow"
+                        android:background="#00000000"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                />
+                <com.android.camera.ImageViewTouch2 android:id="@+id/image2_slideShow"
+                        android:background="#00000000"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                />
+            </AbsoluteLayout>
+            <AbsoluteLayout android:id="@+id/abs"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent">
+                <com.android.camera.ImageViewTouch2 android:id="@+id/image"
+                            android:background="#FF000000"
+                            android:layout_width="fill_parent"
+                            android:layout_height="fill_parent" />
+            </AbsoluteLayout>
+            <ImageView android:id="@+id/prev_image"
+                    android:clickable="true"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:layout_alignParentLeft="true"
+                    android:layout_centerVertical="true"
+                    android:visibility="invisible"
+                    android:src="@drawable/btn_camera_arrow_left"
+            />
+            <ImageView android:id="@+id/next_image"
+                    android:clickable="true"
+                    android:layout_width="wrap_content"
+                    android:layout_height="fill_parent"
+                    android:layout_alignParentRight="true"
+                    android:layout_centerVertical="true"
+                    android:visibility="invisible"
+                    android:src="@drawable/btn_camera_arrow_right"
+            />
+        </RelativeLayout>
+    </FrameLayout>
+</RelativeLayout>
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 538f7f3..5ab7fa5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -131,25 +131,28 @@
     <!-- Confirmation dialog message after deleting a video -->
     <string name="confirm_delete_video_message">The video will be deleted.</string>
     <!-- button indicating that the picture just taken should be deleted -->
-    <string name="camera_toss">Delete</string>
+    <string name="camera_toss">DELETE</string>
 
     <!-- Lable for the button that takes the user to the camera pictures Gallery -->
     <string name="camera_gallery">Gallery</string>
 
     <!-- button indicating that the picture just taken should be shared by email, mms, etc -->
-    <string name="camera_share">Share</string>
+    <string name="camera_share">SHARE</string>
 
     <!-- button indicating that the picture just taken should be set as a contact photo, wallpaper, etc -->
-    <string name="camera_set">Set as</string>
+    <string name="camera_set">SET AS</string>
 
     <!-- button indicating that the video just taken should be played -->
-    <string name="camera_play">Play</string>
+    <string name="camera_play">PLAY</string>
 
     <!-- button indicating that the video just taken should be accepted as an attachment -->
-    <string name="camera_attach">Attach</string>
+    <string name="camera_attach">ATTACH</string>
 
     <!-- button indicating that the video recording session should be canceled -->
-    <string name="camera_cancel">Cancel</string>
+    <string name="camera_cancel">CANCEL</string>
+
+    <!-- button indicating that the the review activity should be finished -->
+    <string name="camera_done">DONE</string>
 
     <!-- button indicating that the picture just taken should be cropped -->
     <string name="camera_crop">Crop</string>
diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java
index 7c4a330..39c6cea 100644
--- a/src/com/android/camera/Camera.java
+++ b/src/com/android/camera/Camera.java
@@ -1534,7 +1534,8 @@
             Uri targetUri = mThumbController.getUri();
             targetUri = targetUri.buildUpon().appendQueryParameter(
                     "bucketId", ImageManager.CAMERA_IMAGE_BUCKET_ID).build();
-            Intent intent = new Intent(Intent.ACTION_VIEW, targetUri);
+            Intent intent = new Intent(this, ReviewImage.class);
+            intent.setData(targetUri);
             intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true);
             intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
             intent.putExtra("com.android.camera.ReviewMode", true);
diff --git a/src/com/android/camera/ImageManager.java b/src/com/android/camera/ImageManager.java
index d0ca0f8..7a7767b 100755
--- a/src/com/android/camera/ImageManager.java
+++ b/src/com/android/camera/ImageManager.java
@@ -279,6 +279,10 @@
             imageList = ImageManager.allImages(
                     cr, ImageManager.DataLocation.ALL,
                     ImageManager.INCLUDE_DRM_IMAGES, sort);
+        } else if (uriString.startsWith("content://media/external/video")) {
+            imageList = ImageManager.allImages(
+                cr, ImageManager.DataLocation.EXTERNAL,
+                ImageManager.INCLUDE_VIDEOS, sort);
         } else if (isSingleImageMode(uriString)) {
             imageList = new SingleImageList(uri);
             ((SingleImageList) imageList).open(cr);
diff --git a/src/com/android/camera/ReviewImage.java b/src/com/android/camera/ReviewImage.java
new file mode 100644
index 0000000..46afc65
--- /dev/null
+++ b/src/com/android/camera/ReviewImage.java
@@ -0,0 +1,1368 @@
+/*
+ * Copyright (C) 2007 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 com.android.camera.gallery.Cancelable;
+import com.android.camera.gallery.IImage;
+import com.android.camera.gallery.IImageList;
+import com.android.camera.gallery.VideoObject;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.GestureDetector;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.widget.Toast;
+import android.widget.ZoomButtonsController;
+
+import java.util.Random;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.ExecutionException;
+
+// This activity can display a whole picture and navigate them in a specific
+// gallery. It has two modes: normal mode and slide show mode. In normal mode
+// the user view one image at a time, and can click "previous" and "next"
+// button to see the previous or next image. In slide show mode it shows one
+// image after another, with some transition effect.
+public class ReviewImage extends Activity implements View.OnClickListener {
+    private static final String PREF_SLIDESHOW_REPEAT =
+            "pref_gallery_slideshow_repeat_key";
+    private static final String PREF_SHUFFLE_SLIDESHOW =
+            "pref_gallery_slideshow_shuffle_key";
+    private static final String STATE_URI = "uri";
+    private static final String STATE_SLIDESHOW = "slideshow";
+    private static final String EXTRA_SLIDESHOW = "slideshow";
+    private static final String TAG = "ReviewImage";
+
+    private static final boolean AUTO_DISMISS = true;
+    private static final boolean NO_AUTO_DISMISS = false;
+
+    private ReviewImageGetter mGetter;
+    private Uri mSavedUri;
+
+    // Choices for what adjacents to load.
+    private static final int[] sOrderAdjacents = new int[] {0, 1, -1};
+    private static final int[] sOrderSlideshow = new int[] {0};
+
+    final LocalHandler mHandler = new LocalHandler();
+
+    private final Random mRandom = new Random(System.currentTimeMillis());
+    private int [] mShuffleOrder = null;
+    private boolean mUseShuffleOrder = false;
+    private boolean mSlideShowLoop = false;
+
+    static final int MODE_NORMAL = 1;
+    static final int MODE_SLIDESHOW = 2;
+    private int mMode = MODE_NORMAL;
+
+    private boolean mFullScreenInNormalMode;
+
+    private int mSlideShowInterval;
+    private int mLastSlideShowImage;
+    int mCurrentPosition = 0;
+
+    // represents which style animation to use
+    private int mAnimationIndex;
+    private Animation [] mSlideShowInAnimation;
+    private Animation [] mSlideShowOutAnimation;
+
+    private SharedPreferences mPrefs;
+
+    private View mRootView;
+    private View mControlBar;
+    private View mNextImageView;
+    private View mPrevImageView;
+    private final Animation mHideNextImageViewAnimation = new AlphaAnimation(1F, 0F);
+    private final Animation mHidePrevImageViewAnimation = new AlphaAnimation(1F, 0F);
+    private final Animation mShowNextImageViewAnimation = new AlphaAnimation(0F, 1F);
+    private final Animation mShowPrevImageViewAnimation = new AlphaAnimation(0F, 1F);
+
+    static final int PADDING = 20;
+    static final int HYSTERESIS = PADDING * 2;
+    static final int BASE_SCROLL_DURATION = 1000; // ms
+
+    public static final String KEY_IMAGE_LIST = "image_list";
+
+    IImageList mAllImages;
+
+    private int mSlideShowImageCurrent = 0;
+    private final ImageViewTouchBase [] mSlideShowImageViews =
+            new ImageViewTouchBase[2];
+
+    GestureDetector mGestureDetector;
+    private ZoomButtonsController mZoomButtonsController;
+
+    // The image view displayed for normal mode.
+    private ImageViewTouch2 mImageView;
+    // This is the cache for thumbnail bitmaps.
+    private BitmapCache mCache;
+    private MenuHelper.MenuItemsResult mImageMenuRunnable;
+
+    private Runnable mDismissOnScreenControlsRunnable;
+
+    private void updateNextPrevControls() {
+        boolean showPrev = mCurrentPosition > 0;
+        boolean showNext = mCurrentPosition < mAllImages.getCount() - 1;
+
+        boolean prevIsVisible = mPrevImageView.getVisibility() == View.VISIBLE;
+        boolean nextIsVisible = mNextImageView.getVisibility() == View.VISIBLE;
+
+        if (showPrev && !prevIsVisible) {
+            Animation a = mShowPrevImageViewAnimation;
+            a.setDuration(500);
+            mPrevImageView.startAnimation(a);
+            mPrevImageView.setVisibility(View.VISIBLE);
+        } else if (!showPrev && prevIsVisible) {
+            Animation a = mHidePrevImageViewAnimation;
+            a.setDuration(500);
+            mPrevImageView.startAnimation(a);
+            mPrevImageView.setVisibility(View.GONE);
+        }
+
+        if (showNext && !nextIsVisible) {
+            Animation a = mShowNextImageViewAnimation;
+            a.setDuration(500);
+            mNextImageView.startAnimation(a);
+            mNextImageView.setVisibility(View.VISIBLE);
+        } else if (!showNext && nextIsVisible) {
+            Animation a = mHideNextImageViewAnimation;
+            a.setDuration(500);
+            mNextImageView.startAnimation(a);
+            mNextImageView.setVisibility(View.GONE);
+        }
+    }
+
+    private void showOnScreenControls(final boolean autoDismiss) {
+        // If the view has not been attached to the window yet, the
+        // zoomButtonControls will not able to show up. So delay it until the
+        // view has attached to window.
+        if (mRootView.getWindowToken() == null) {
+            mHandler.postGetterCallback(new Runnable() {
+                public void run() {
+                    showOnScreenControls(autoDismiss);
+                }
+            });
+            return;
+        }
+        mHandler.removeCallbacks(mDismissOnScreenControlsRunnable);
+        updateNextPrevControls();
+
+        IImage image = mAllImages.getImageAt(mCurrentPosition);
+        if (image instanceof VideoObject) {
+            mZoomButtonsController.setVisible(false);
+        } else {
+            updateZoomButtonsEnabled();
+            mZoomButtonsController.setVisible(true);
+        }
+        if (autoDismiss) scheduleDismissOnScreenControls();
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent m) {
+        boolean sup = super.dispatchTouchEvent(m);
+
+        // This is a hack to show the on screen controls. We should make sure
+        // this event is not handled by others(ie. sup == false), and listen for
+        // the events on zoom/prev/next buttons.
+        // However, since we have no other pressable views, it is OK now.
+        // TODO: Fix the above issue.
+        if (mMode == MODE_NORMAL) {
+            switch (m.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    showOnScreenControls(NO_AUTO_DISMISS);
+                    break;
+                case MotionEvent.ACTION_UP:
+                    scheduleDismissOnScreenControls();
+                    break;
+            }
+        }
+
+        if (sup == false) {
+            mGestureDetector.onTouchEvent(m);
+            return true;
+        }
+        return true;
+    }
+
+    private void scheduleDismissOnScreenControls() {
+        mHandler.removeCallbacks(mDismissOnScreenControlsRunnable);
+        mHandler.postDelayed(mDismissOnScreenControlsRunnable, 1500);
+    }
+
+    private void updateZoomButtonsEnabled() {
+        ImageViewTouch2 imageView = mImageView;
+        float scale = imageView.getScale();
+        mZoomButtonsController.setZoomInEnabled(scale < imageView.mMaxZoom);
+        mZoomButtonsController.setZoomOutEnabled(scale > 1);
+    }
+
+    @Override
+    protected void onDestroy() {
+
+        // This is necessary to make the ZoomButtonsController unregister
+        // its configuration change receiver.
+        if (mZoomButtonsController != null) {
+            mZoomButtonsController.setVisible(false);
+        }
+
+        super.onDestroy();
+    }
+
+    private void setupZoomButtonController(View rootView) {
+        mGestureDetector = new GestureDetector(this, new MyGestureListener());
+        mZoomButtonsController = new ZoomButtonsController(rootView);
+        mZoomButtonsController.setAutoDismissed(false);
+        mZoomButtonsController.setOnZoomListener(
+                new ZoomButtonsController.OnZoomListener() {
+            public void onVisibilityChanged(boolean visible) {
+                if (visible) {
+                    updateZoomButtonsEnabled();
+                }
+            }
+
+            public void onZoom(boolean zoomIn) {
+                if (zoomIn) {
+                    mImageView.zoomIn();
+                } else {
+                    mImageView.zoomOut();
+                }
+                updateZoomButtonsEnabled();
+            }
+        });
+    }
+
+    private class MyGestureListener extends
+            GestureDetector.SimpleOnGestureListener {
+
+        @Override
+        public boolean onScroll(MotionEvent e1, MotionEvent e2,
+                float distanceX, float distanceY) {
+            ImageViewTouch2 imageView = mImageView;
+            if (imageView.getScale() > 1F) {
+                imageView.postTranslateCenter(-distanceX, -distanceY);
+            }
+            return true;
+        }
+
+        @Override
+        public boolean onSingleTapUp(MotionEvent e) {
+            setMode(MODE_NORMAL);
+            return true;
+        }
+    }
+
+    private void setupDismissOnScreenControlRunnable() {
+        mDismissOnScreenControlsRunnable = new Runnable() {
+            public void run() {
+                if (mNextImageView.getVisibility() == View.VISIBLE) {
+                    Animation a = mHideNextImageViewAnimation;
+                    a.setDuration(500);
+                    mNextImageView.startAnimation(a);
+                    mNextImageView.setVisibility(View.INVISIBLE);
+                }
+
+                if (mPrevImageView.getVisibility() == View.VISIBLE) {
+                    Animation a = mHidePrevImageViewAnimation;
+                    a.setDuration(500);
+                    mPrevImageView.startAnimation(a);
+                    mPrevImageView.setVisibility(View.INVISIBLE);
+                }
+                mZoomButtonsController.setVisible(false);
+            }
+        };
+    }
+
+    boolean isPickIntent() {
+        String action = getIntent().getAction();
+        return (Intent.ACTION_PICK.equals(action)
+                || Intent.ACTION_GET_CONTENT.equals(action));
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        final SelectedImageGetter selectedImageGetter =
+                new SelectedImageGetter() {
+            public IImage getCurrentImage() {
+                return mAllImages.getImageAt(mCurrentPosition);
+            }
+
+            public Uri getCurrentImageUri() {
+                return mAllImages.getImageAt(mCurrentPosition)
+                        .fullSizeImageUri();
+            }
+        };
+
+        mImageMenuRunnable = MenuHelper.addImageMenuItems(
+                menu,
+                MenuHelper.INCLUDE_ALL,
+                true,
+                ReviewImage.this,
+                mHandler,
+                mDeletePhotoRunnable,
+                new MenuHelper.MenuInvoker() {
+                    public void run(final MenuHelper.MenuCallback cb) {
+                        setMode(MODE_NORMAL);
+                        Thread t = new Thread() {
+                            @Override
+                            public void run() {
+                               cb.run(selectedImageGetter.getCurrentImageUri(),
+                                       selectedImageGetter.getCurrentImage());
+                               mHandler.post(new Runnable() {
+                                 public void run() {
+                                     mImageView.clear();
+                                     setImage(mCurrentPosition);
+                                 }
+                               });
+                            }
+                        };
+                        t.start();
+                    }
+                });
+
+        if (true) {
+            MenuItem item = menu.add(Menu.CATEGORY_SECONDARY, 203, 1000,
+                    R.string.camerasettings);
+            item.setOnMenuItemClickListener(
+                    new MenuItem.OnMenuItemClickListener() {
+                public boolean onMenuItemClick(MenuItem item) {
+                    Intent preferences = new Intent();
+                    preferences.setClass(ReviewImage.this, GallerySettings.class);
+                    startActivity(preferences);
+                    return true;
+                }
+            });
+            item.setAlphabeticShortcut('p');
+            item.setIcon(android.R.drawable.ic_menu_preferences);
+        }
+
+        // Hidden menu just so the shortcut will bring up the zoom controls
+        // the string resource is a placeholder
+        menu.add(Menu.CATEGORY_SECONDARY, 203, 0, R.string.camerasettings)
+                .setOnMenuItemClickListener(
+                new MenuItem.OnMenuItemClickListener() {
+            public boolean onMenuItemClick(MenuItem item) {
+                showOnScreenControls(AUTO_DISMISS);
+                return true;
+            }
+        })
+        .setAlphabeticShortcut('z')
+        .setVisible(false);
+
+        return true;
+    }
+
+    protected Runnable mDeletePhotoRunnable = new Runnable() {
+        public void run() {
+            mAllImages.removeImageAt(mCurrentPosition);
+            if (mAllImages.getCount() == 0) {
+                finish();
+            } else {
+                if (mCurrentPosition == mAllImages.getCount()) {
+                    mCurrentPosition -= 1;
+                }
+            }
+            mImageView.clear();
+            mCache.clear();  // Because the position number is changed.
+            setImage(mCurrentPosition);
+        }
+    };
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        setMode(MODE_NORMAL);
+
+        if (mImageMenuRunnable != null) {
+            mImageMenuRunnable.gettingReadyToOpen(menu,
+                    mAllImages.getImageAt(mCurrentPosition));
+        }
+
+        Uri uri = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri();
+        MenuHelper.enableShareMenuItem(menu, !MenuHelper.isMMSUri(uri));
+
+        return true;
+    }
+
+    @Override
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        boolean b = super.onMenuItemSelected(featureId, item);
+        if (mImageMenuRunnable != null) {
+            mImageMenuRunnable.aboutToCall(item,
+                    mAllImages.getImageAt(mCurrentPosition));
+        }
+        return b;
+    }
+
+    void setImage(int pos) {
+        mCurrentPosition = pos;
+
+        Bitmap b = mCache.getBitmap(pos);
+        if (b != null) {
+            mImageView.setImageBitmapResetBase(b, true);
+            updateZoomButtonsEnabled();
+        }
+
+        ImageGetterCallback cb = new ImageGetterCallback() {
+            public void completed(boolean wasCanceled) {
+                mImageView.setFocusableInTouchMode(true);
+                mImageView.requestFocus();
+            }
+
+            public boolean wantsThumbnail(int pos, int offset) {
+                return !mCache.hasBitmap(pos + offset);
+            }
+
+            public boolean wantsFullImage(int pos, int offset) {
+                return offset == 0;
+            }
+
+            public int fullImageSizeToUse(int pos, int offset) {
+                // TODO
+                // this number should be bigger so that we can zoom.  we may
+                // need to get fancier and read in the fuller size image as the
+                // user starts to zoom.  use -1 to get the full full size image.
+                // for now use 480 so we don't run out of memory
+                final int imageViewSize = 480;
+                return imageViewSize;
+            }
+
+            public int [] loadOrder() {
+                return sOrderAdjacents;
+            }
+
+            public void imageLoaded(int pos, int offset, Bitmap bitmap,
+                                    boolean isThumb) {
+                // shouldn't get here after onPause()
+
+                // We may get a result from a previous request. Ignore it.
+                if (pos != mCurrentPosition) {
+                    bitmap.recycle();
+                    return;
+                }
+
+                if (isThumb) {
+                    mCache.put(pos + offset, bitmap);
+                }
+                if (offset == 0) {
+                    // isThumb: We always load thumb bitmap first, so we will
+                    // reset the supp matrix for then thumb bitmap, and keep
+                    // the supp matrix when the full bitmap is loaded.
+                    mImageView.setImageBitmapResetBase(bitmap, isThumb);
+                    updateZoomButtonsEnabled();
+                }
+            }
+        };
+
+        // Could be null if we're stopping a slide show in the course of pausing
+        if (mGetter != null) {
+            mGetter.setPosition(pos, cb);
+        }
+        updateActionIcons();
+        showOnScreenControls(AUTO_DISMISS);
+    }
+
+    @Override
+    public void onCreate(Bundle instanceState) {
+        super.onCreate(instanceState);
+
+        Intent intent = getIntent();
+        mFullScreenInNormalMode = intent.getBooleanExtra(
+                MediaStore.EXTRA_FULL_SCREEN, true);
+
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+        setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+        setContentView(R.layout.review_image);
+
+        mRootView = findViewById(R.id.root);
+        mControlBar = findViewById(R.id.control_bar);
+        mImageView = (ImageViewTouch2) findViewById(R.id.image);
+        mImageView.setEnableTrackballScroll(true);
+        mCache = new BitmapCache(3);
+        mImageView.setRecycler(mCache);
+
+        makeGetter();
+
+        mAnimationIndex = -1;
+
+        mSlideShowInAnimation = new Animation[] {
+            makeInAnimation(R.anim.transition_in),
+            makeInAnimation(R.anim.slide_in),
+            makeInAnimation(R.anim.slide_in_vertical),
+        };
+
+        mSlideShowOutAnimation = new Animation[] {
+            makeOutAnimation(R.anim.transition_out),
+            makeOutAnimation(R.anim.slide_out),
+            makeOutAnimation(R.anim.slide_out_vertical),
+        };
+
+        mSlideShowImageViews[0] =
+                (ImageViewTouchBase) findViewById(R.id.image1_slideShow);
+        mSlideShowImageViews[1] =
+                (ImageViewTouchBase) findViewById(R.id.image2_slideShow);
+        for (ImageViewTouchBase v : mSlideShowImageViews) {
+            v.setVisibility(View.INVISIBLE);
+            v.setRecycler(mCache);
+        }
+
+        Uri uri = getIntent().getData();
+        IImageList imageList = getIntent().getParcelableExtra(KEY_IMAGE_LIST);
+        boolean slideshow = intent.getBooleanExtra(EXTRA_SLIDESHOW, false);
+
+        if (instanceState != null) {
+            uri = instanceState.getParcelable(STATE_URI);
+            slideshow = instanceState.getBoolean(STATE_SLIDESHOW, false);
+        }
+
+        if (!init(uri, imageList)) {
+            finish();
+            return;
+        }
+
+        int[] pickIds = {R.id.attach, R.id.cancel};
+        int[] reviewIds = {R.id.btn_delete, R.id.btn_share, R.id.btn_set_as, R.id.btn_play,
+                R.id.btn_done};
+        int[] connectIds = isPickIntent() ? pickIds : reviewIds;
+        for (int id : connectIds) {
+            View view = mControlBar.findViewById(id);
+            view.setOnClickListener(this);
+            // Set the LinearLayout of the given button to visible
+            ((View) view.getParent()).setVisibility(View.VISIBLE);
+        }
+
+        if (slideshow) {
+            setMode(MODE_SLIDESHOW);
+        } else {
+            if (mFullScreenInNormalMode) {
+                getWindow().addFlags(
+                        WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            }
+        }
+
+        setupZoomButtonController(findViewById(R.id.mainPanel));
+        setupDismissOnScreenControlRunnable();
+
+        mNextImageView = findViewById(R.id.next_image);
+        mPrevImageView = findViewById(R.id.prev_image);
+        mNextImageView.setOnClickListener(this);
+        mPrevImageView.setOnClickListener(this);
+
+        mNextImageView.setFocusable(true);
+        mPrevImageView.setFocusable(true);
+
+    }
+
+    private void setButtonPanelVisibility(int id, int visibility) {
+        View button = mControlBar.findViewById(id);
+        ((View) button.getParent()).setVisibility(visibility);
+    }
+
+    private void updateActionIcons() {
+        if (isPickIntent()) return;
+
+        IImage image = mAllImages.getImageAt(mCurrentPosition);
+        if (image instanceof VideoObject) {
+            setButtonPanelVisibility(R.id.btn_set_as, View.GONE);
+            setButtonPanelVisibility(R.id.btn_play, View.VISIBLE);
+        } else {
+            setButtonPanelVisibility(R.id.btn_set_as, View.VISIBLE);
+            setButtonPanelVisibility(R.id.btn_play, View.GONE);
+        }
+    }
+
+    private Animation makeInAnimation(int id) {
+        Animation inAnimation = AnimationUtils.loadAnimation(this, id);
+        return inAnimation;
+    }
+
+    private Animation makeOutAnimation(int id) {
+        Animation outAnimation = AnimationUtils.loadAnimation(this, id);
+        return outAnimation;
+    }
+
+    private static int getPreferencesInteger(
+            SharedPreferences prefs, String key, int defaultValue) {
+        String value = prefs.getString(key, null);
+        try {
+            return value == null ? defaultValue : Integer.parseInt(value);
+        } catch (NumberFormatException ex) {
+            Log.e(TAG, "couldn't parse preference: " + value, ex);
+            return defaultValue;
+        }
+    }
+
+    void setMode(int mode) {
+        if (mMode == mode) {
+            return;
+        }
+        View slideshowPanel = findViewById(R.id.slideShowContainer);
+        View normalPanel = findViewById(R.id.abs);
+
+        Window win = getWindow();
+        mMode = mode;
+        if (mode == MODE_SLIDESHOW) {
+            slideshowPanel.setVisibility(View.VISIBLE);
+            normalPanel.setVisibility(View.GONE);
+
+            win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN
+                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+            mImageView.clear();
+
+            slideshowPanel.getRootView().requestLayout();
+
+            // The preferences we want to read:
+            //   mUseShuffleOrder
+            //   mSlideShowLoop
+            //   mAnimationIndex
+            //   mSlideShowInterval
+
+            mUseShuffleOrder = mPrefs.getBoolean(PREF_SHUFFLE_SLIDESHOW, false);
+            mSlideShowLoop = mPrefs.getBoolean(PREF_SLIDESHOW_REPEAT, false);
+            mAnimationIndex = getPreferencesInteger(
+                    mPrefs, "pref_gallery_slideshow_transition_key", 0);
+            mSlideShowInterval = getPreferencesInteger(
+                    mPrefs, "pref_gallery_slideshow_interval_key", 3) * 1000;
+            if (mUseShuffleOrder) {
+                generateShuffleOrder();
+            }
+        } else {
+            slideshowPanel.setVisibility(View.GONE);
+            normalPanel.setVisibility(View.VISIBLE);
+
+            win.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            if (mFullScreenInNormalMode) {
+                win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            } else {
+                win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+            }
+
+            if (mGetter != null) {
+                mGetter.cancelCurrent();
+            }
+
+            ImageViewTouchBase dst = mImageView;
+            dst.mLastXTouchPos = -1;
+            dst.mLastYTouchPos = -1;
+
+            for (ImageViewTouchBase ivt : mSlideShowImageViews) {
+                ivt.clear();
+            }
+
+            mShuffleOrder = null;
+
+            // mGetter null is a proxy for being paused
+            if (mGetter != null) {
+                setImage(mCurrentPosition);
+            }
+        }
+    }
+
+    private void generateShuffleOrder() {
+        if (mShuffleOrder == null
+                || mShuffleOrder.length != mAllImages.getCount()) {
+            mShuffleOrder = new int[mAllImages.getCount()];
+            for (int i = 0, n = mShuffleOrder.length; i < n; i++) {
+                mShuffleOrder[i] = i;
+            }
+        }
+
+        for (int i = mShuffleOrder.length - 1; i >= 0; i--) {
+            int r = mRandom.nextInt(i + 1);
+            if (r != i) {
+                int tmp = mShuffleOrder[r];
+                mShuffleOrder[r] = mShuffleOrder[i];
+                mShuffleOrder[i] = tmp;
+            }
+        }
+    }
+
+    private void loadNextImage(final int requestedPos, final long delay,
+                               final boolean firstCall) {
+        if (firstCall && mUseShuffleOrder) {
+            generateShuffleOrder();
+        }
+
+        final long targetDisplayTime = System.currentTimeMillis() + delay;
+
+        ImageGetterCallback cb = new ImageGetterCallback() {
+            public void completed(boolean wasCanceled) {
+            }
+
+            public boolean wantsThumbnail(int pos, int offset) {
+                return true;
+            }
+
+            public boolean wantsFullImage(int pos, int offset) {
+                return false;
+            }
+
+            public int [] loadOrder() {
+                return sOrderSlideshow;
+            }
+
+            public int fullImageSizeToUse(int pos, int offset) {
+                return 480; // TODO compute this
+            }
+
+            public void imageLoaded(final int pos, final int offset,
+                    final Bitmap bitmap, final boolean isThumb) {
+                long timeRemaining = Math.max(0,
+                        targetDisplayTime - System.currentTimeMillis());
+                mHandler.postDelayedGetterCallback(new Runnable() {
+                    public void run() {
+                        if (mMode == MODE_NORMAL) {
+                            return;
+                        }
+
+                        ImageViewTouchBase oldView =
+                                mSlideShowImageViews[mSlideShowImageCurrent];
+
+                        if (++mSlideShowImageCurrent
+                                == mSlideShowImageViews.length) {
+                            mSlideShowImageCurrent = 0;
+                        }
+
+                        ImageViewTouchBase newView =
+                                mSlideShowImageViews[mSlideShowImageCurrent];
+                        newView.setVisibility(View.VISIBLE);
+                        newView.setImageBitmapResetBase(bitmap, true);
+                        newView.bringToFront();
+
+                        int animation = 0;
+
+                        if (mAnimationIndex == -1) {
+                            int n = mRandom.nextInt(
+                                    mSlideShowInAnimation.length);
+                            animation = n;
+                        } else {
+                            animation = mAnimationIndex;
+                        }
+
+                        Animation aIn = mSlideShowInAnimation[animation];
+                        newView.startAnimation(aIn);
+                        newView.setVisibility(View.VISIBLE);
+
+                        Animation aOut = mSlideShowOutAnimation[animation];
+                        oldView.setVisibility(View.INVISIBLE);
+                        oldView.startAnimation(aOut);
+
+                        mCurrentPosition = requestedPos;
+
+                        if (mCurrentPosition == mLastSlideShowImage
+                                && !firstCall) {
+                            if (mSlideShowLoop) {
+                                if (mUseShuffleOrder) {
+                                    generateShuffleOrder();
+                                }
+                            } else {
+                                setMode(MODE_NORMAL);
+                                return;
+                            }
+                        }
+
+                        loadNextImage(
+                                (mCurrentPosition + 1) % mAllImages.getCount(),
+                                mSlideShowInterval, false);
+                    }
+                }, timeRemaining);
+            }
+        };
+        // Could be null if we're stopping a slide show in the course of pausing
+        if (mGetter != null) {
+            int pos = requestedPos;
+            if (mShuffleOrder != null) {
+                pos = mShuffleOrder[pos];
+            }
+            mGetter.setPosition(pos, cb);
+        }
+    }
+
+    private void makeGetter() {
+        mGetter = new ReviewImageGetter(this);
+    }
+
+    private IImageList buildImageListFromUri(Uri uri) {
+        String sortOrder = mPrefs.getString(
+                "pref_gallery_sort_key", "descending");
+        int sort = ImageManager.SORT_ASCENDING;
+        return ImageManager.makeImageList(uri, getContentResolver(), sort);
+    }
+
+    private boolean init(Uri uri, IImageList imageList) {
+        if (uri == null) return false;
+        mAllImages = (imageList == null)
+                ? buildImageListFromUri(uri)
+                : imageList;
+        mAllImages.open(getContentResolver());
+        IImage image = mAllImages.getImageForUri(uri);
+        if (image == null) return false;
+        mCurrentPosition = mAllImages.getImageIndex(image);
+        mLastSlideShowImage = mCurrentPosition;
+        return true;
+    }
+
+    private Uri getCurrentUri() {
+        IImage image = mAllImages.getImageAt(mCurrentPosition);
+        return image.fullSizeImageUri();
+    }
+
+    @Override
+    public void onSaveInstanceState(Bundle b) {
+        super.onSaveInstanceState(b);
+        b.putParcelable(STATE_URI,
+                mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri());
+        b.putBoolean(STATE_SLIDESHOW, mMode == MODE_SLIDESHOW);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        init(mSavedUri, mAllImages);
+
+        // normally this will never be zero but if one "backs" into this
+        // activity after removing the sdcard it could be zero.  in that
+        // case just "finish" since there's nothing useful that can happen.
+        int count = mAllImages.getCount();
+        if (count == 0) {
+            finish();
+        } else if (count <= mCurrentPosition) {
+            mCurrentPosition = count - 1;
+        }
+
+        if (mGetter == null) {
+            makeGetter();
+        }
+
+        if (mMode == MODE_SLIDESHOW) {
+            loadNextImage(mCurrentPosition, 0, true);
+        } else {  // MODE_NORMAL
+            setImage(mCurrentPosition);
+        }
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+
+        mGetter.cancelCurrent();
+        mGetter.stop();
+        mGetter = null;
+        setMode(MODE_NORMAL);
+
+        // removing all callback in the message queue
+        mHandler.removeAllGetterCallbacks();
+
+        mSavedUri = getCurrentUri();
+
+        mAllImages.deactivate();
+        mDismissOnScreenControlsRunnable.run();
+        if (mDismissOnScreenControlsRunnable != null) {
+            mHandler.removeCallbacks(mDismissOnScreenControlsRunnable);
+        }
+
+        mImageView.clear();
+        mCache.clear();
+
+        for (ImageViewTouchBase iv : mSlideShowImageViews) {
+            iv.clear();
+        }
+    }
+
+    private void startShareMediaActivity(IImage image) {
+        boolean isVideo = image instanceof VideoObject;
+        Intent intent = new Intent();
+        intent.setAction(Intent.ACTION_SEND);
+        intent.setType(image.getMimeType());
+        intent.putExtra(Intent.EXTRA_STREAM, image.fullSizeImageUri());
+        try {
+            startActivity(Intent.createChooser(intent, getText(
+                    isVideo ? R.string.sendVideo : R.string.sendImage)));
+        } catch (android.content.ActivityNotFoundException ex) {
+            Toast.makeText(this, isVideo
+                    ? R.string.no_way_to_share_image
+                    : R.string.no_way_to_share_video,
+                    Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    private void startPlayVideoActivity() {
+        IImage image = mAllImages.getImageAt(mCurrentPosition);
+        Intent intent = new Intent(
+                Intent.ACTION_VIEW, image.fullSizeImageUri());
+        try {
+            startActivity(intent);
+        } catch (android.content.ActivityNotFoundException ex) {
+            Log.e(TAG, "Couldn't view video " + image.fullSizeImageUri(), ex);
+        }
+    }
+
+    public void onClick(View v) {
+        switch (v.getId()) {
+            case R.id.btn_delete:
+                MenuHelper.deletePhoto(this, mDeletePhotoRunnable);
+                break;
+            case R.id.btn_play:
+                startPlayVideoActivity();
+                break;
+            case R.id.btn_share: {
+                IImage image = mAllImages.getImageAt(mCurrentPosition);
+                if (MenuHelper.isMMSUri(image.fullSizeImageUri())) {
+                    return;
+                }
+                startShareMediaActivity(image);
+                break;
+            }
+            case R.id.btn_set_as: {
+                Uri u = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri();
+                Intent intent = new Intent(Intent.ACTION_ATTACH_DATA, u);
+                try {
+                    startActivity(Intent.createChooser(
+                            intent, getText(R.string.setImage)));
+                } catch (android.content.ActivityNotFoundException ex) {
+                    Toast.makeText(this, R.string.no_way_to_share_video,
+                            Toast.LENGTH_SHORT).show();
+                }
+                break;
+            }
+            case R.id.btn_done:
+                finish();
+                break;
+            case R.id.next_image:
+                moveNextOrPrevious(1);
+                break;
+            case R.id.prev_image:
+                moveNextOrPrevious(-1);
+                break;
+        }
+    }
+
+    private void moveNextOrPrevious(int delta) {
+        int nextImagePos = mCurrentPosition + delta;
+        if ((0 <= nextImagePos) && (nextImagePos < mAllImages.getCount())) {
+            setImage(nextImagePos);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode,
+            Intent data) {
+        switch (requestCode) {
+            case MenuHelper.RESULT_COMMON_MENU_CROP:
+                if (resultCode == RESULT_OK) {
+                    // The CropImage activity passes back the Uri of the
+                    // cropped image as the Action rather than the Data.
+                    mSavedUri = Uri.parse(data.getAction());
+                }
+                break;
+        }
+    }
+
+    static class LocalHandler extends Handler {
+        private static final int IMAGE_GETTER_CALLBACK = 1;
+
+        @Override
+        public void handleMessage(Message message) {
+            switch(message.what) {
+                case IMAGE_GETTER_CALLBACK:
+                    ((Runnable) message.obj).run();
+                    break;
+            }
+        }
+
+        public void postGetterCallback(Runnable callback) {
+           postDelayedGetterCallback(callback, 0);
+        }
+
+        public void postDelayedGetterCallback(Runnable callback, long delay) {
+            if (callback == null) {
+                throw new NullPointerException();
+            }
+            Message message = Message.obtain();
+            message.what = IMAGE_GETTER_CALLBACK;
+            message.obj = callback;
+            sendMessageDelayed(message, delay);
+        }
+
+        public void removeAllGetterCallbacks() {
+            removeMessages(IMAGE_GETTER_CALLBACK);
+        }
+    }
+}
+
+class ReviewImageGetter {
+
+    @SuppressWarnings("unused")
+    private static final String TAG = "ImageGetter";
+
+    // The thread which does the work.
+    private final Thread mGetterThread;
+
+    // The base position that's being retrieved.  The actual images retrieved
+    // are this base plus each of the offets.
+    private int mCurrentPosition = -1;
+
+    // The callback to invoke for each image.
+    private ImageGetterCallback mCB;
+
+    // This is the loader cancelable that gets set while we're loading an image.
+    // If we change position we can cancel the current load using this.
+    private Cancelable<Bitmap> mLoad;
+
+    // True if we're canceling the current load.
+    private boolean mCancelCurrent = false;
+
+    // True when the therad should exit.
+    private boolean mDone = false;
+
+    // True when the loader thread is waiting for work.
+    private boolean mReady = false;
+
+    // The ViewImage this ImageGetter belongs to
+    ReviewImage mViewImage;
+
+    void cancelCurrent() {
+        synchronized (this) {
+            if (!mReady) {
+                mCancelCurrent = true;
+                Cancelable<Bitmap> load = mLoad;
+                if (load != null) {
+                    load.requestCancel();
+                }
+                mCancelCurrent = false;
+            }
+        }
+    }
+
+    private class ImageGetterRunnable implements Runnable {
+        private Runnable callback(final int position, final int offset,
+                                  final boolean isThumb, final Bitmap bitmap) {
+            return new Runnable() {
+                public void run() {
+                    // check for inflight callbacks that aren't applicable
+                    // any longer before delivering them
+                    if (!isCanceled() && position == mCurrentPosition) {
+                        mCB.imageLoaded(position, offset, bitmap, isThumb);
+                    } else if (bitmap != null) {
+                        bitmap.recycle();
+                    }
+                }
+            };
+        }
+
+        private Runnable completedCallback(final boolean wasCanceled) {
+            return new Runnable() {
+                public void run() {
+                    mCB.completed(wasCanceled);
+                }
+            };
+        }
+
+        public void run() {
+            int lastPosition = -1;
+            while (!mDone) {
+                synchronized (ReviewImageGetter.this) {
+                    mReady = true;
+                    ReviewImageGetter.this.notify();
+
+                    if (mCurrentPosition == -1
+                            || lastPosition == mCurrentPosition) {
+                        try {
+                            ReviewImageGetter.this.wait();
+                        } catch (InterruptedException ex) {
+                            continue;
+                        }
+                    }
+
+                    lastPosition = mCurrentPosition;
+                    mReady = false;
+                }
+
+                if (lastPosition != -1) {
+                    int imageCount = mViewImage.mAllImages.getCount();
+
+                    int [] order = mCB.loadOrder();
+                    for (int i = 0; i < order.length; i++) {
+                        int offset = order[i];
+                        int imageNumber = lastPosition + offset;
+                        if (imageNumber >= 0 && imageNumber < imageCount) {
+                            IImage image = mViewImage.mAllImages
+                                    .getImageAt(lastPosition + offset);
+                            if (image == null || isCanceled()) {
+                                break;
+                            }
+                            if (mCB.wantsThumbnail(lastPosition, offset)) {
+                                Bitmap b = image.thumbBitmap();
+                                mViewImage.mHandler.postGetterCallback(
+                                        callback(lastPosition, offset,
+                                        true, b));
+                            }
+                        }
+                    }
+
+                    for (int i = 0; i < order.length; i++) {
+                        int offset = order[i];
+                        int imageNumber = lastPosition + offset;
+                        if (imageNumber >= 0 && imageNumber < imageCount) {
+                            IImage image = mViewImage.mAllImages
+                                    .getImageAt(lastPosition + offset);
+                            if (mCB.wantsFullImage(lastPosition, offset)
+                                    && !(image instanceof VideoObject)) {
+                                int sizeToUse = mCB.fullImageSizeToUse(
+                                        lastPosition, offset);
+                                if (image != null && !isCanceled()) {
+                                    mLoad = image.fullSizeBitmapCancelable(
+                                            sizeToUse);
+                                }
+                                if (mLoad != null) {
+                                    // The return value could be null if the
+                                    // bitmap is too big, or we cancelled it.
+                                    Bitmap b;
+                                    try {
+                                        b = mLoad.get();
+                                    } catch (InterruptedException e) {
+                                        b = null;
+                                    } catch (ExecutionException e) {
+                                        throw new RuntimeException(e);
+                                    } catch (CancellationException e) {
+                                        b = null;
+                                    }
+                                    mLoad = null;
+                                    if (b != null) {
+                                        if (isCanceled()) {
+                                            b.recycle();
+                                        } else {
+                                            Runnable cb = callback(
+                                                    lastPosition, offset,
+                                                    false, b);
+                                            mViewImage.mHandler
+                                                    .postGetterCallback(cb);
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    mViewImage.mHandler.postGetterCallback(
+                            completedCallback(isCanceled()));
+                }
+            }
+        }
+    }
+
+    public ReviewImageGetter(ReviewImage viewImage) {
+        mViewImage = viewImage;
+        mGetterThread = new Thread(new ImageGetterRunnable());
+        mGetterThread.setName("ImageGettter");
+        mGetterThread.start();
+    }
+
+    private boolean isCanceled() {
+        synchronized (this) {
+            return mCancelCurrent;
+        }
+    }
+
+    public void setPosition(int position, ImageGetterCallback cb) {
+        synchronized (this) {
+            if (!mReady) {
+                try {
+                    mCancelCurrent = true;
+                    // if the thread is waiting before loading the full size
+                    // image then this will free it up
+                    BitmapManager.instance()
+                            .cancelThreadDecoding(mGetterThread);
+                    ReviewImageGetter.this.notify();
+                    ReviewImageGetter.this.wait();
+                    BitmapManager.instance()
+                            .allowThreadDecoding(mGetterThread);
+                    mCancelCurrent = false;
+                } catch (InterruptedException ex) {
+                    // not sure what to do here
+                }
+            }
+        }
+
+        mCurrentPosition = position;
+        mCB = cb;
+
+        synchronized (this) {
+            ReviewImageGetter.this.notify();
+        }
+    }
+
+    public void stop() {
+        synchronized (this) {
+            mDone = true;
+            ReviewImageGetter.this.notify();
+        }
+        try {
+            BitmapManager.instance().cancelThreadDecoding(mGetterThread);
+            mGetterThread.join();
+        } catch (InterruptedException ex) {
+            // Ignore the exception
+        }
+    }
+}
+
+class ImageViewTouch2 extends ImageViewTouchBase {
+    private final ReviewImage mViewImage;
+    private boolean mEnableTrackballScroll;
+
+    public ImageViewTouch2(Context context) {
+        super(context);
+        mViewImage = (ReviewImage) context;
+    }
+
+    public ImageViewTouch2(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mViewImage = (ReviewImage) context;
+    }
+
+    public void setEnableTrackballScroll(boolean enable) {
+        mEnableTrackballScroll = enable;
+    }
+
+    protected void postTranslateCenter(float dx, float dy) {
+        super.postTranslate(dx, dy);
+        center(true, true);
+    }
+
+    static final float PAN_RATE = 20;
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // Don't respond to arrow keys if trackball scrolling is not enabled
+        if (!mEnableTrackballScroll) {
+            if ((keyCode >= KeyEvent.KEYCODE_DPAD_UP)
+                    && (keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT)) {
+                return super.onKeyDown(keyCode, event);
+            }
+        }
+
+        int current = mViewImage.mCurrentPosition;
+
+        int nextImagePos = -2; // default no next image
+        try {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_DPAD_CENTER: {
+                    if (mViewImage.isPickIntent()) {
+                        IImage img = mViewImage.mAllImages
+                                .getImageAt(mViewImage.mCurrentPosition);
+                        mViewImage.setResult(ReviewImage.RESULT_OK,
+                                 new Intent().setData(img.fullSizeImageUri()));
+                        mViewImage.finish();
+                    }
+                    break;
+                }
+                case KeyEvent.KEYCODE_DPAD_LEFT: {
+                    int maxOffset = (current == 0) ? 0 : ReviewImage.HYSTERESIS;
+                    if (getScale() <= 1F
+                            || isShiftedToNextImage(true, maxOffset)) {
+                        nextImagePos = current - 1;
+                    } else {
+                        panBy(PAN_RATE, 0);
+                        center(true, false);
+                    }
+                    return true;
+                }
+                case KeyEvent.KEYCODE_DPAD_RIGHT: {
+                    int maxOffset =
+                            (current == mViewImage.mAllImages.getCount() - 1)
+                            ? 0
+                            : ReviewImage.HYSTERESIS;
+                    if (getScale() <= 1F
+                            || isShiftedToNextImage(false, maxOffset)) {
+                        nextImagePos = current + 1;
+                    } else {
+                        panBy(-PAN_RATE, 0);
+                        center(true, false);
+                    }
+                    return true;
+                }
+                case KeyEvent.KEYCODE_DPAD_UP: {
+                    panBy(0, PAN_RATE);
+                    center(false, true);
+                    return true;
+                }
+                case KeyEvent.KEYCODE_DPAD_DOWN: {
+                    panBy(0, -PAN_RATE);
+                    center(false, true);
+                    return true;
+                }
+                case KeyEvent.KEYCODE_DEL:
+                    MenuHelper.deletePhoto(
+                            mViewImage, mViewImage.mDeletePhotoRunnable);
+                    break;
+            }
+        } finally {
+            if (nextImagePos >= 0
+                    && nextImagePos < mViewImage.mAllImages.getCount()) {
+                synchronized (mViewImage) {
+                    mViewImage.setMode(ReviewImage.MODE_NORMAL);
+                    mViewImage.setImage(nextImagePos);
+                }
+           } else if (nextImagePos != -2) {
+               center(true, true);
+           }
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    protected boolean isShiftedToNextImage(boolean left, int maxOffset) {
+        boolean retval;
+        Bitmap bitmap = mBitmapDisplayed;
+        Matrix m = getImageViewMatrix();
+        if (left) {
+            float [] t1 = new float[] { 0, 0 };
+            m.mapPoints(t1);
+            retval = t1[0] > maxOffset;
+        } else {
+            int width = bitmap != null ? bitmap.getWidth() : getWidth();
+            float [] t1 = new float[] { width, 0 };
+            m.mapPoints(t1);
+            retval = t1[0] + maxOffset < getWidth();
+        }
+        return retval;
+    }
+}
diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java
index 0a0d4b5..e8e98a0 100644
--- a/src/com/android/camera/VideoCamera.java
+++ b/src/com/android/camera/VideoCamera.java
@@ -20,6 +20,7 @@
 import com.android.camera.gallery.IImageList;
 
 import android.app.Activity;
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -302,7 +303,7 @@
                 break;
             }
             case R.id.review_thumbnail: {
-                stopVideoRecordingAndShowAlert();
+                stopVideoRecordingAndShowReview();
                 break;
             }
         }
@@ -317,7 +318,7 @@
             case R.id.shutter_button:
                 if (mMediaRecorderRecording) {
                     if (mIsVideoCaptureIntent) {
-                        stopVideoRecordingAndShowAlert();
+                        stopVideoRecordingAndShowReview();
                     } else {
                         stopVideoRecordingAndGetThumbnail();
                         mRecorderInitialized = false;
@@ -515,7 +516,7 @@
         // but not quite the same.
         if (mMediaRecorderRecording) {
             if (mIsVideoCaptureIntent) {
-                stopVideoRecordingAndShowAlert();
+                stopVideoRecordingAndShowReview();
             } else {
                 stopVideoRecordingAndGetThumbnail();
             }
@@ -1009,7 +1010,7 @@
             }
             mMediaRecorderRecording = true;
             mRecordingStartTime = SystemClock.uptimeMillis();
-            updateRecordingIndicator(true);
+            updateRecordingIndicator(false);
             mRecordingTimeView.setText("");
             mRecordingTimeView.setVisibility(View.VISIBLE);
             mHandler.sendEmptyMessage(UPDATE_RECORD_TIME);
@@ -1030,8 +1031,23 @@
         acquireVideoThumb();
     }
 
-    private void stopVideoRecordingAndShowAlert() {
+    private void stopVideoRecordingAndShowReview() {
         stopVideoRecording();
+        if (mThumbController.isUriValid()) {
+            Uri targetUri = mThumbController.getUri();
+            Intent intent = new Intent(this, ReviewImage.class);
+            intent.setData(targetUri);
+            intent.putExtra(MediaStore.EXTRA_FULL_SCREEN, true);
+            intent.putExtra(MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
+            intent.putExtra("com.android.camera.ReviewMode", true);
+            try {
+                startActivity(intent);
+            } catch (ActivityNotFoundException ex) {
+                Log.e(TAG, "review video fail", ex);
+            }
+        } else {
+            Log.e(TAG, "Can't view last video.");
+        }
     }
 
     private void stopVideoRecording() {
@@ -1054,7 +1070,7 @@
                 mMediaRecorderRecording = false;
             }
             releaseMediaRecorder();
-            updateRecordingIndicator(false);
+            updateRecordingIndicator(true);
             mRecordingTimeView.setVisibility(View.GONE);
             setScreenTimeoutLong();
         }
diff --git a/src/com/android/camera/gallery/SingleImageList.java b/src/com/android/camera/gallery/SingleImageList.java
index f0af653..11e108d 100644
--- a/src/com/android/camera/gallery/SingleImageList.java
+++ b/src/com/android/camera/gallery/SingleImageList.java
@@ -42,6 +42,7 @@
 
     @Override
     public void open(ContentResolver resolver) {
+        mContentResolver = resolver;
         mSingleImage = new UriImage(this, resolver, mBaseUri);
     }
 
@@ -80,6 +81,15 @@
     }
 
     @Override
+    public boolean removeImage(IImage image) {
+        if (mContentResolver.delete(image.fullSizeImageUri(), null, null) > 0) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
     public IImage getImageForUri(Uri uri) {
         return uri.equals(mBaseUri) ? mSingleImage : null;
     }