summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml9
-rw-r--r--res/drawable/btn_camera_review.xml25
-rw-r--r--res/layout/camera.xml3
-rw-r--r--res/layout/review_control.xml123
-rw-r--r--res/layout/review_image.xml80
-rw-r--r--res/values/strings.xml15
-rw-r--r--src/com/android/camera/Camera.java3
-rwxr-xr-xsrc/com/android/camera/ImageManager.java4
-rw-r--r--src/com/android/camera/ReviewImage.java1368
-rw-r--r--src/com/android/camera/VideoCamera.java28
-rw-r--r--src/com/android/camera/gallery/SingleImageList.java10
11 files changed, 1652 insertions, 16 deletions
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 @@ public class Camera extends Activity implements View.OnClickListener,
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 @@ public class ImageManager {
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.IImage;
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 @@ public class VideoCamera extends Activity implements View.OnClickListener,
break;
}
case R.id.review_thumbnail: {
- stopVideoRecordingAndShowAlert();
+ stopVideoRecordingAndShowReview();
break;
}
}
@@ -317,7 +318,7 @@ public class VideoCamera extends Activity implements View.OnClickListener,
case R.id.shutter_button:
if (mMediaRecorderRecording) {
if (mIsVideoCaptureIntent) {
- stopVideoRecordingAndShowAlert();
+ stopVideoRecordingAndShowReview();
} else {
stopVideoRecordingAndGetThumbnail();
mRecorderInitialized = false;
@@ -515,7 +516,7 @@ public class VideoCamera extends Activity implements View.OnClickListener,
// but not quite the same.
if (mMediaRecorderRecording) {
if (mIsVideoCaptureIntent) {
- stopVideoRecordingAndShowAlert();
+ stopVideoRecordingAndShowReview();
} else {
stopVideoRecordingAndGetThumbnail();
}
@@ -1009,7 +1010,7 @@ public class VideoCamera extends Activity implements View.OnClickListener,
}
mMediaRecorderRecording = true;
mRecordingStartTime = SystemClock.uptimeMillis();
- updateRecordingIndicator(true);
+ updateRecordingIndicator(false);
mRecordingTimeView.setText("");
mRecordingTimeView.setVisibility(View.VISIBLE);
mHandler.sendEmptyMessage(UPDATE_RECORD_TIME);
@@ -1030,8 +1031,23 @@ public class VideoCamera extends Activity implements View.OnClickListener,
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 @@ public class VideoCamera extends Activity implements View.OnClickListener,
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 @@ public class SingleImageList extends BaseImageList {
@Override
public void open(ContentResolver resolver) {
+ mContentResolver = resolver;
mSingleImage = new UriImage(this, resolver, mBaseUri);
}
@@ -80,6 +81,15 @@ public class SingleImageList extends BaseImageList {
}
@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;
}