blob: 0633b181a0243a72c7287d7e4457ab043f260ddf [file] [log] [blame]
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera;
18
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080019import android.app.Activity;
20import android.content.Context;
21import android.content.Intent;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080022import android.graphics.Bitmap;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080023import android.net.Uri;
24import android.os.Bundle;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080025import android.provider.MediaStore;
26import android.util.AttributeSet;
27import android.util.Log;
28import android.view.GestureDetector;
29import android.view.KeyEvent;
30import android.view.Menu;
31import android.view.MenuItem;
32import android.view.MotionEvent;
33import android.view.View;
34import android.view.Window;
35import android.view.WindowManager;
Owen Lin71ea3a32009-07-30 10:23:30 +080036import android.view.View.OnTouchListener;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080037import android.view.animation.AlphaAnimation;
38import android.view.animation.Animation;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080039import android.widget.Toast;
40import android.widget.ZoomButtonsController;
41
Owen Lin8b3ecb22009-09-02 16:25:08 +080042import com.android.camera.gallery.IImage;
43import com.android.camera.gallery.IImageList;
44import com.android.camera.gallery.VideoObject;
45
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080046// This activity can display a whole picture and navigate them in a specific
47// gallery. It has two modes: normal mode and slide show mode. In normal mode
48// the user view one image at a time, and can click "previous" and "next"
49// button to see the previous or next image. In slide show mode it shows one
50// image after another, with some transition effect.
51public class ReviewImage extends Activity implements View.OnClickListener {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080052 private static final String STATE_URI = "uri";
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080053 private static final String TAG = "ReviewImage";
Cheng-Ru Linffcca742009-09-28 03:21:25 +080054 private static final double ASPECT_RATIO = 4.0 / 3.0;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080055
Chih-Chung Chang4250e212009-07-24 10:58:40 +080056 private ImageGetter mGetter;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080057 private Uri mSavedUri;
Chih-Chung Chang6a1abe12009-09-24 21:17:51 -070058 boolean mPaused = true;
Owen Lin69bd2712009-08-06 15:29:34 +080059 private boolean mShowControls = true;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080060
61 // Choices for what adjacents to load.
62 private static final int[] sOrderAdjacents = new int[] {0, 1, -1};
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080063
Chih-Chung Chang4250e212009-07-24 10:58:40 +080064 final GetterHandler mHandler = new GetterHandler();
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080065
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080066 private boolean mFullScreenInNormalMode;
67
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080068 int mCurrentPosition = 0;
69
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080070 private View mRootView;
71 private View mControlBar;
72 private View mNextImageView;
73 private View mPrevImageView;
Chih-Chung Chang91acfc92009-07-06 15:37:24 +080074 private final Animation mHideNextImageViewAnimation =
75 new AlphaAnimation(1F, 0F);
76 private final Animation mHidePrevImageViewAnimation =
77 new AlphaAnimation(1F, 0F);
78 private final Animation mShowNextImageViewAnimation =
79 new AlphaAnimation(0F, 1F);
80 private final Animation mShowPrevImageViewAnimation =
81 new AlphaAnimation(0F, 1F);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080082
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080083 public static final String KEY_IMAGE_LIST = "image_list";
Owen Lin69bd2712009-08-06 15:29:34 +080084 private static final String STATE_SHOW_CONTROLS = "show_controls";
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080085
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +080086 ImageManager.ImageListParam mParam;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080087 IImageList mAllImages;
88
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +080089 private final ImageViewTouchBase [] mSlideShowImageViews =
90 new ImageViewTouchBase[2];
91
92 GestureDetector mGestureDetector;
93 private ZoomButtonsController mZoomButtonsController;
94
95 // The image view displayed for normal mode.
96 private ImageViewTouch2 mImageView;
97 // This is the cache for thumbnail bitmaps.
98 private BitmapCache mCache;
99 private MenuHelper.MenuItemsResult mImageMenuRunnable;
Owen Lin71ea3a32009-07-30 10:23:30 +0800100 private final Runnable mDismissOnScreenControlRunner = new Runnable() {
101 public void run() {
102 hideOnScreenControls();
103 }
104 };
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800105
106 private void updateNextPrevControls() {
107 boolean showPrev = mCurrentPosition > 0;
108 boolean showNext = mCurrentPosition < mAllImages.getCount() - 1;
109
110 boolean prevIsVisible = mPrevImageView.getVisibility() == View.VISIBLE;
111 boolean nextIsVisible = mNextImageView.getVisibility() == View.VISIBLE;
112
113 if (showPrev && !prevIsVisible) {
114 Animation a = mShowPrevImageViewAnimation;
115 a.setDuration(500);
116 mPrevImageView.startAnimation(a);
117 mPrevImageView.setVisibility(View.VISIBLE);
118 } else if (!showPrev && prevIsVisible) {
119 Animation a = mHidePrevImageViewAnimation;
120 a.setDuration(500);
121 mPrevImageView.startAnimation(a);
122 mPrevImageView.setVisibility(View.GONE);
123 }
124
125 if (showNext && !nextIsVisible) {
126 Animation a = mShowNextImageViewAnimation;
127 a.setDuration(500);
128 mNextImageView.startAnimation(a);
129 mNextImageView.setVisibility(View.VISIBLE);
130 } else if (!showNext && nextIsVisible) {
131 Animation a = mHideNextImageViewAnimation;
132 a.setDuration(500);
133 mNextImageView.startAnimation(a);
134 mNextImageView.setVisibility(View.GONE);
135 }
136 }
137
Owen Lin71ea3a32009-07-30 10:23:30 +0800138 private void showOnScreenControls() {
139
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800140 // If the view has not been attached to the window yet, the
141 // zoomButtonControls will not able to show up. So delay it until the
142 // view has attached to window.
143 if (mRootView.getWindowToken() == null) {
144 mHandler.postGetterCallback(new Runnable() {
145 public void run() {
Owen Lin71ea3a32009-07-30 10:23:30 +0800146 showOnScreenControls();
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800147 }
148 });
149 return;
150 }
Owen Lin71ea3a32009-07-30 10:23:30 +0800151
152 // we may need to update the next/prev button due to index changing
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800153 updateNextPrevControls();
154
Owen Lin71ea3a32009-07-30 10:23:30 +0800155 if (ImageManager.isImage(mAllImages.getImageAt(mCurrentPosition))) {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800156 updateZoomButtonsEnabled();
157 mZoomButtonsController.setVisible(true);
Owen Lin71ea3a32009-07-30 10:23:30 +0800158 } else {
159 mZoomButtonsController.setVisible(false);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800160 }
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800161 }
162
Owen Lin71ea3a32009-07-30 10:23:30 +0800163 private void hideOnScreenControls() {
164 if (mNextImageView.getVisibility() == View.VISIBLE) {
165 Animation a = mHideNextImageViewAnimation;
166 a.setDuration(500);
167 mNextImageView.startAnimation(a);
168 mNextImageView.setVisibility(View.INVISIBLE);
169 }
170
171 if (mPrevImageView.getVisibility() == View.VISIBLE) {
172 Animation a = mHidePrevImageViewAnimation;
173 a.setDuration(500);
174 mPrevImageView.startAnimation(a);
175 mPrevImageView.setVisibility(View.INVISIBLE);
176 }
177
178 mZoomButtonsController.setVisible(false);
179 }
180
181 private void scheduleDismissOnScreenControls() {
182 mHandler.removeCallbacks(mDismissOnScreenControlRunner);
183 mHandler.postDelayed(mDismissOnScreenControlRunner, 2000);
Owen Lin0ac86072009-07-30 10:23:30 +0800184 }
185
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800186 @Override
187 public boolean dispatchTouchEvent(MotionEvent m) {
Owen Lin8b3ecb22009-09-02 16:25:08 +0800188 if (mZoomButtonsController.isVisible()) {
Owen Lin71ea3a32009-07-30 10:23:30 +0800189 scheduleDismissOnScreenControls();
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800190 }
Chih-Chung Changeb9b5372009-09-10 12:36:52 +0800191 return super.dispatchTouchEvent(m);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800192 }
193
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800194 private void updateZoomButtonsEnabled() {
195 ImageViewTouch2 imageView = mImageView;
196 float scale = imageView.getScale();
197 mZoomButtonsController.setZoomInEnabled(scale < imageView.mMaxZoom);
198 mZoomButtonsController.setZoomOutEnabled(scale > 1);
199 }
200
201 @Override
202 protected void onDestroy() {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800203 // This is necessary to make the ZoomButtonsController unregister
204 // its configuration change receiver.
205 if (mZoomButtonsController != null) {
206 mZoomButtonsController.setVisible(false);
207 }
208
209 super.onDestroy();
210 }
211
Chih-Chung Changeb9b5372009-09-10 12:36:52 +0800212 private void setupOnScreenControls(View rootView, View ownerView) {
Owen Lin71ea3a32009-07-30 10:23:30 +0800213 mNextImageView = rootView.findViewById(R.id.next_image);
214 mPrevImageView = rootView.findViewById(R.id.prev_image);
215
216 mNextImageView.setOnClickListener(this);
217 mPrevImageView.setOnClickListener(this);
Chih-Chung Changeb9b5372009-09-10 12:36:52 +0800218
219 setupZoomButtonController(ownerView);
220 setupOnTouchListeners(rootView);
Owen Lin71ea3a32009-07-30 10:23:30 +0800221 }
222
Chih-Chung Changeb9b5372009-09-10 12:36:52 +0800223 private void setupZoomButtonController(final View ownerView) {
224 mZoomButtonsController = new ZoomButtonsController(ownerView);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800225 mZoomButtonsController.setAutoDismissed(false);
Wu-cheng Li559899a2009-06-30 15:26:48 +0800226 mZoomButtonsController.setZoomSpeed(100);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800227 mZoomButtonsController.setOnZoomListener(
228 new ZoomButtonsController.OnZoomListener() {
229 public void onVisibilityChanged(boolean visible) {
230 if (visible) {
231 updateZoomButtonsEnabled();
232 }
233 }
234
235 public void onZoom(boolean zoomIn) {
236 if (zoomIn) {
237 mImageView.zoomIn();
238 } else {
239 mImageView.zoomOut();
240 }
Owen Lin71ea3a32009-07-30 10:23:30 +0800241 mZoomButtonsController.setVisible(true);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800242 updateZoomButtonsEnabled();
243 }
244 });
245 }
246
Chih-Chung Changeb9b5372009-09-10 12:36:52 +0800247 private void setupOnTouchListeners(View rootView) {
248 mGestureDetector = new GestureDetector(this, new MyGestureListener());
249
250 // If the user touches anywhere on the panel (including the
251 // next/prev button). We show the on-screen controls. In addition
252 // to that, if the touch is not on the prev/next button, we
253 // pass the event to the gesture detector to detect double tap.
254 final OnTouchListener buttonListener = new OnTouchListener() {
255 public boolean onTouch(View v, MotionEvent event) {
256 scheduleDismissOnScreenControls();
257 return false;
258 }
259 };
260
261 OnTouchListener rootListener = new OnTouchListener() {
262 public boolean onTouch(View v, MotionEvent event) {
263 buttonListener.onTouch(v, event);
264 mGestureDetector.onTouchEvent(event);
265
266 // We do not use the return value of
267 // mGestureDetector.onTouchEvent because we will not receive
268 // the "up" event if we return false for the "down" event.
269 return true;
270 }
271 };
272
273 mNextImageView.setOnTouchListener(buttonListener);
274 mPrevImageView.setOnTouchListener(buttonListener);
275 rootView.setOnTouchListener(rootListener);
276 }
277
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800278 private class MyGestureListener extends
279 GestureDetector.SimpleOnGestureListener {
280
281 @Override
282 public boolean onScroll(MotionEvent e1, MotionEvent e2,
283 float distanceX, float distanceY) {
284 ImageViewTouch2 imageView = mImageView;
285 if (imageView.getScale() > 1F) {
286 imageView.postTranslateCenter(-distanceX, -distanceY);
287 }
288 return true;
289 }
290
291 @Override
Chih-Chung Changeb9b5372009-09-10 12:36:52 +0800292 public boolean onSingleTapConfirmed(MotionEvent e) {
293 showOnScreenControls();
294 scheduleDismissOnScreenControls();
295 return true;
296 }
297
298 @Override
299 public boolean onDoubleTap(MotionEvent e) {
300 ImageViewTouch2 imageView = mImageView;
301
302 // Switch between the original scale and 3x scale.
303 if (imageView.getScale() > 2F) {
304 mImageView.zoomTo(1f);
305 } else {
306 mImageView.zoomToPoint(3f, e.getX(), e.getY());
307 }
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800308 return true;
309 }
310 }
311
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800312 boolean isPickIntent() {
313 String action = getIntent().getAction();
314 return (Intent.ACTION_PICK.equals(action)
315 || Intent.ACTION_GET_CONTENT.equals(action));
316 }
317
318 @Override
319 public boolean onCreateOptionsMenu(Menu menu) {
320 super.onCreateOptionsMenu(menu);
321
Chih-Chung Changcd65ac72009-09-08 14:36:48 +0800322 // These are the menu items already covered by the side buttons.
323 int sideButtons = MenuHelper.INCLUDE_DELETE_MENU
324 | MenuHelper.INCLUDE_SHARE_MENU
325 | MenuHelper.INCLUDE_SET_MENU
326 | MenuHelper.INCLUDE_VIEWPLAY_MENU;
327
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800328 mImageMenuRunnable = MenuHelper.addImageMenuItems(
329 menu,
Chih-Chung Changcd65ac72009-09-08 14:36:48 +0800330 MenuHelper.INCLUDE_ALL & ~sideButtons,
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800331 ReviewImage.this,
332 mHandler,
333 mDeletePhotoRunnable,
334 new MenuHelper.MenuInvoker() {
335 public void run(final MenuHelper.MenuCallback cb) {
Chih-Chung Chang06f43ba2009-07-02 17:07:20 +0800336 if (mPaused) return;
Chih-Chung Chang06f43ba2009-07-02 17:07:20 +0800337 IImage image = mAllImages.getImageAt(mCurrentPosition);
338 Uri uri = image.fullSizeImageUri();
339 cb.run(uri, image);
Chih-Chung Chang35f8af02009-07-01 14:52:24 +0800340
Chih-Chung Chang06f43ba2009-07-02 17:07:20 +0800341 mImageView.clear();
Owen Lin69bd2712009-08-06 15:29:34 +0800342 setImage(mCurrentPosition, false);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800343 }
344 });
345
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800346 return true;
347 }
348
349 protected Runnable mDeletePhotoRunnable = new Runnable() {
350 public void run() {
351 mAllImages.removeImageAt(mCurrentPosition);
352 if (mAllImages.getCount() == 0) {
353 finish();
Owen Lin71ea3a32009-07-30 10:23:30 +0800354 return;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800355 } else {
356 if (mCurrentPosition == mAllImages.getCount()) {
357 mCurrentPosition -= 1;
358 }
359 }
360 mImageView.clear();
361 mCache.clear(); // Because the position number is changed.
Owen Lin69bd2712009-08-06 15:29:34 +0800362 setImage(mCurrentPosition, true);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800363 }
364 };
365
366 @Override
367 public boolean onPrepareOptionsMenu(Menu menu) {
368 super.onPrepareOptionsMenu(menu);
Chih-Chung Changcb3e7692009-09-12 01:30:42 +0800369 if (mPaused) return false;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800370
Chih-Chung Chang120bf582009-09-07 18:51:47 +0800371 IImage image = mAllImages.getImageAt(mCurrentPosition);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800372 if (mImageMenuRunnable != null) {
Chih-Chung Chang120bf582009-09-07 18:51:47 +0800373 mImageMenuRunnable.gettingReadyToOpen(menu, image);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800374 }
375
376 Uri uri = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri();
Chih-Chung Changb2a97652009-07-10 18:39:49 +0800377 MenuHelper.enableShareMenuItem(menu, MenuHelper.isWhiteListUri(uri));
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800378
Chih-Chung Chang120bf582009-09-07 18:51:47 +0800379 MenuHelper.enableShowOnMapMenuItem(menu, MenuHelper.hasLatLngData(image));
Chih-Chung Changeb9b5372009-09-10 12:36:52 +0800380
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800381 return true;
382 }
383
384 @Override
385 public boolean onMenuItemSelected(int featureId, MenuItem item) {
386 boolean b = super.onMenuItemSelected(featureId, item);
387 if (mImageMenuRunnable != null) {
388 mImageMenuRunnable.aboutToCall(item,
389 mAllImages.getImageAt(mCurrentPosition));
390 }
391 return b;
392 }
393
Owen Lin69bd2712009-08-06 15:29:34 +0800394 void setImage(int pos, boolean showControls) {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800395 mCurrentPosition = pos;
396
397 Bitmap b = mCache.getBitmap(pos);
398 if (b != null) {
Ray Chen012d0f32009-07-20 16:33:41 +0800399 IImage image = mAllImages.getImageAt(pos);
400 mImageView.setImageRotateBitmapResetBase(
401 new RotateBitmap(b, image.getDegreesRotated()), true);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800402 updateZoomButtonsEnabled();
403 }
404
405 ImageGetterCallback cb = new ImageGetterCallback() {
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800406 public void completed() {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800407 }
408
409 public boolean wantsThumbnail(int pos, int offset) {
410 return !mCache.hasBitmap(pos + offset);
411 }
412
413 public boolean wantsFullImage(int pos, int offset) {
414 return offset == 0;
415 }
416
417 public int fullImageSizeToUse(int pos, int offset) {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800418 // this number should be bigger so that we can zoom. we may
419 // need to get fancier and read in the fuller size image as the
Wei-Ta Chend5d74642009-06-11 16:13:13 +0800420 // user starts to zoom.
421 // Originally the value is set to 480 in order to avoid OOM.
Wei-Ta Chenf2c7e982009-07-29 17:24:50 +0800422 // Now we set it to 2048 because of using
423 // native memory allocation for Bitmaps.
Wei-Ta Chend5d74642009-06-11 16:13:13 +0800424 final int imageViewSize = 2048;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800425 return imageViewSize;
426 }
427
428 public int [] loadOrder() {
429 return sOrderAdjacents;
430 }
431
Ray Chen012d0f32009-07-20 16:33:41 +0800432 public void imageLoaded(int pos, int offset, RotateBitmap bitmap,
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800433 boolean isThumb) {
434 // shouldn't get here after onPause()
435
436 // We may get a result from a previous request. Ignore it.
437 if (pos != mCurrentPosition) {
438 bitmap.recycle();
439 return;
440 }
441
442 if (isThumb) {
Ray Chen012d0f32009-07-20 16:33:41 +0800443 mCache.put(pos + offset, bitmap.getBitmap());
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800444 }
445 if (offset == 0) {
446 // isThumb: We always load thumb bitmap first, so we will
447 // reset the supp matrix for then thumb bitmap, and keep
448 // the supp matrix when the full bitmap is loaded.
Ray Chen012d0f32009-07-20 16:33:41 +0800449 mImageView.setImageRotateBitmapResetBase(bitmap, isThumb);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800450 updateZoomButtonsEnabled();
451 }
452 }
453 };
454
455 // Could be null if we're stopping a slide show in the course of pausing
456 if (mGetter != null) {
Chih-Chung Chang4250e212009-07-24 10:58:40 +0800457 mGetter.setPosition(pos, cb, mAllImages, mHandler);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800458 }
459 updateActionIcons();
Owen Lin69bd2712009-08-06 15:29:34 +0800460 if (showControls) showOnScreenControls();
Owen Lin71ea3a32009-07-30 10:23:30 +0800461 scheduleDismissOnScreenControls();
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800462 }
463
464 @Override
465 public void onCreate(Bundle instanceState) {
466 super.onCreate(instanceState);
467
468 Intent intent = getIntent();
469 mFullScreenInNormalMode = intent.getBooleanExtra(
470 MediaStore.EXTRA_FULL_SCREEN, true);
471
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800472 setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
473 requestWindowFeature(Window.FEATURE_NO_TITLE);
474 setContentView(R.layout.review_image);
475
476 mRootView = findViewById(R.id.root);
477 mControlBar = findViewById(R.id.control_bar);
478 mImageView = (ImageViewTouch2) findViewById(R.id.image);
479 mImageView.setEnableTrackballScroll(true);
480 mCache = new BitmapCache(3);
481 mImageView.setRecycler(mCache);
482
Cheng-Ru Linffcca742009-09-28 03:21:25 +0800483 ((PreviewFrameLayout) findViewById(
484 R.id.frame_layout)).setAspectRatio(ASPECT_RATIO);
485
Owen Lin69bd2712009-08-06 15:29:34 +0800486
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800487 makeGetter();
488
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800489 mSlideShowImageViews[0] =
490 (ImageViewTouchBase) findViewById(R.id.image1_slideShow);
491 mSlideShowImageViews[1] =
492 (ImageViewTouchBase) findViewById(R.id.image2_slideShow);
493 for (ImageViewTouchBase v : mSlideShowImageViews) {
494 v.setVisibility(View.INVISIBLE);
495 v.setRecycler(mCache);
496 }
497
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800498 mParam = getIntent().getParcelableExtra(KEY_IMAGE_LIST);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800499
500 if (instanceState != null) {
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800501 mSavedUri = instanceState.getParcelable(STATE_URI);
Owen Lin69bd2712009-08-06 15:29:34 +0800502 mShowControls = instanceState.getBoolean(STATE_SHOW_CONTROLS, true);
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800503 } else {
504 mSavedUri = getIntent().getData();
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800505 }
506
507 int[] pickIds = {R.id.attach, R.id.cancel};
Chih-Chung Chang91acfc92009-07-06 15:37:24 +0800508 int[] reviewIds = {R.id.btn_delete, R.id.btn_share, R.id.btn_set_as,
509 R.id.btn_play, R.id.btn_done};
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800510 int[] connectIds = isPickIntent() ? pickIds : reviewIds;
511 for (int id : connectIds) {
512 View view = mControlBar.findViewById(id);
513 view.setOnClickListener(this);
514 // Set the LinearLayout of the given button to visible
515 ((View) view.getParent()).setVisibility(View.VISIBLE);
516 }
517
Owen Lin71ea3a32009-07-30 10:23:30 +0800518 if (mFullScreenInNormalMode) {
519 getWindow().addFlags(
520 WindowManager.LayoutParams.FLAG_FULLSCREEN);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800521 }
522
Chih-Chung Changeb9b5372009-09-10 12:36:52 +0800523 setupOnScreenControls(findViewById(R.id.mainPanel), mImageView);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800524 }
525
526 private void setButtonPanelVisibility(int id, int visibility) {
527 View button = mControlBar.findViewById(id);
528 ((View) button.getParent()).setVisibility(visibility);
529 }
530
531 private void updateActionIcons() {
532 if (isPickIntent()) return;
533
534 IImage image = mAllImages.getImageAt(mCurrentPosition);
535 if (image instanceof VideoObject) {
536 setButtonPanelVisibility(R.id.btn_set_as, View.GONE);
537 setButtonPanelVisibility(R.id.btn_play, View.VISIBLE);
538 } else {
539 setButtonPanelVisibility(R.id.btn_set_as, View.VISIBLE);
540 setButtonPanelVisibility(R.id.btn_play, View.GONE);
541 }
542 }
543
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800544 private void makeGetter() {
Ray Chenbde544f2009-09-30 14:33:15 -0700545 mGetter = new ImageGetter(getContentResolver());
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800546 }
547
548 private IImageList buildImageListFromUri(Uri uri) {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800549 int sort = ImageManager.SORT_ASCENDING;
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800550 return ImageManager.makeImageList(getContentResolver(), uri, sort);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800551 }
552
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800553 private boolean init(Uri uri) {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800554 if (uri == null) return false;
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800555 mAllImages = (mParam == null)
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800556 ? buildImageListFromUri(uri)
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800557 : ImageManager.makeImageList(getContentResolver(), mParam);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800558 IImage image = mAllImages.getImageForUri(uri);
559 if (image == null) return false;
560 mCurrentPosition = mAllImages.getImageIndex(image);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800561 return true;
562 }
563
564 private Uri getCurrentUri() {
Owen Lin387833a2009-06-29 17:30:24 -0700565 if (mAllImages.getCount() == 0) return null;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800566 IImage image = mAllImages.getImageAt(mCurrentPosition);
567 return image.fullSizeImageUri();
568 }
569
570 @Override
571 public void onSaveInstanceState(Bundle b) {
572 super.onSaveInstanceState(b);
573 b.putParcelable(STATE_URI,
574 mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri());
Owen Lin69bd2712009-08-06 15:29:34 +0800575 b.putBoolean(STATE_SHOW_CONTROLS, mShowControls);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800576 }
577
578 @Override
579 public void onStart() {
580 super.onStart();
Chih-Chung Chang06f43ba2009-07-02 17:07:20 +0800581 mPaused = false;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800582
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800583 if (!init(mSavedUri)) {
Chih-Chung Changca1f8202009-09-14 12:46:16 +0800584 Log.w(TAG, "init failed: " + mSavedUri);
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800585 finish();
586 return;
587 }
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800588
589 // normally this will never be zero but if one "backs" into this
590 // activity after removing the sdcard it could be zero. in that
591 // case just "finish" since there's nothing useful that can happen.
592 int count = mAllImages.getCount();
593 if (count == 0) {
594 finish();
Owen Lin71ea3a32009-07-30 10:23:30 +0800595 return;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800596 } else if (count <= mCurrentPosition) {
597 mCurrentPosition = count - 1;
598 }
599
600 if (mGetter == null) {
601 makeGetter();
602 }
603
Owen Lin69bd2712009-08-06 15:29:34 +0800604 //show controls only for first time
605 setImage(mCurrentPosition, mShowControls);
606 mShowControls = false;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800607 }
608
609 @Override
610 public void onStop() {
611 super.onStop();
Chih-Chung Chang06f43ba2009-07-02 17:07:20 +0800612 mPaused = true;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800613
Chih-Chung Changca1f8202009-09-14 12:46:16 +0800614 // mGetter could be null if we call finish() and leave early in
615 // onStart().
616 if (mGetter != null) {
617 mGetter.cancelCurrent();
618 mGetter.stop();
619 mGetter = null;
620 }
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800621
622 // removing all callback in the message queue
623 mHandler.removeAllGetterCallbacks();
624
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800625 if (mAllImages != null) {
626 mSavedUri = getCurrentUri();
627 mAllImages.close();
628 mAllImages = null;
629 }
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800630
Owen Lin71ea3a32009-07-30 10:23:30 +0800631 hideOnScreenControls();
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800632 mImageView.clear();
633 mCache.clear();
634
635 for (ImageViewTouchBase iv : mSlideShowImageViews) {
636 iv.clear();
637 }
638 }
639
640 private void startShareMediaActivity(IImage image) {
641 boolean isVideo = image instanceof VideoObject;
642 Intent intent = new Intent();
643 intent.setAction(Intent.ACTION_SEND);
644 intent.setType(image.getMimeType());
645 intent.putExtra(Intent.EXTRA_STREAM, image.fullSizeImageUri());
646 try {
647 startActivity(Intent.createChooser(intent, getText(
648 isVideo ? R.string.sendVideo : R.string.sendImage)));
649 } catch (android.content.ActivityNotFoundException ex) {
650 Toast.makeText(this, isVideo
651 ? R.string.no_way_to_share_image
652 : R.string.no_way_to_share_video,
653 Toast.LENGTH_SHORT).show();
654 }
655 }
656
657 private void startPlayVideoActivity() {
658 IImage image = mAllImages.getImageAt(mCurrentPosition);
659 Intent intent = new Intent(
660 Intent.ACTION_VIEW, image.fullSizeImageUri());
661 try {
662 startActivity(intent);
663 } catch (android.content.ActivityNotFoundException ex) {
664 Log.e(TAG, "Couldn't view video " + image.fullSizeImageUri(), ex);
665 }
666 }
667
668 public void onClick(View v) {
669 switch (v.getId()) {
670 case R.id.btn_delete:
repo sync4adbd032009-06-25 10:56:45 +0800671 MenuHelper.deleteImage(this, mDeletePhotoRunnable,
672 mAllImages.getImageAt(mCurrentPosition));
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800673 break;
674 case R.id.btn_play:
675 startPlayVideoActivity();
676 break;
677 case R.id.btn_share: {
678 IImage image = mAllImages.getImageAt(mCurrentPosition);
Chih-Chung Changb2a97652009-07-10 18:39:49 +0800679 if (!MenuHelper.isWhiteListUri(image.fullSizeImageUri())) {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800680 return;
681 }
682 startShareMediaActivity(image);
683 break;
684 }
685 case R.id.btn_set_as: {
Chih-Chung Changbb187782009-07-02 16:46:30 +0800686 IImage image = mAllImages.getImageAt(mCurrentPosition);
687 Intent intent = Util.createSetAsIntent(image);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800688 try {
689 startActivity(Intent.createChooser(
690 intent, getText(R.string.setImage)));
691 } catch (android.content.ActivityNotFoundException ex) {
692 Toast.makeText(this, R.string.no_way_to_share_video,
693 Toast.LENGTH_SHORT).show();
694 }
695 break;
696 }
697 case R.id.btn_done:
698 finish();
699 break;
700 case R.id.next_image:
701 moveNextOrPrevious(1);
702 break;
703 case R.id.prev_image:
704 moveNextOrPrevious(-1);
705 break;
706 }
707 }
708
709 private void moveNextOrPrevious(int delta) {
710 int nextImagePos = mCurrentPosition + delta;
711 if ((0 <= nextImagePos) && (nextImagePos < mAllImages.getCount())) {
Owen Lin69bd2712009-08-06 15:29:34 +0800712 setImage(nextImagePos, true);
Owen Lin71ea3a32009-07-30 10:23:30 +0800713 showOnScreenControls();
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800714 }
715 }
716
717 @Override
718 protected void onActivityResult(int requestCode, int resultCode,
719 Intent data) {
720 switch (requestCode) {
721 case MenuHelper.RESULT_COMMON_MENU_CROP:
722 if (resultCode == RESULT_OK) {
723 // The CropImage activity passes back the Uri of the
724 // cropped image as the Action rather than the Data.
725 mSavedUri = Uri.parse(data.getAction());
Chih-Chung Changca1f8202009-09-14 12:46:16 +0800726
727 // if onStart() runs before, then set the returned
728 // image as currentImage.
729 if (mAllImages != null) {
730 IImage image = mAllImages.getImageForUri(mSavedUri);
731 // image could be null if SD card is removed.
732 if (image == null) {
733 finish();
734 } else {
735 mCurrentPosition = mAllImages.getImageIndex(image);
736 setImage(mCurrentPosition, false);
737 }
738 }
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800739 }
740 break;
741 }
742 }
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800743}
744
745class ImageViewTouch2 extends ImageViewTouchBase {
746 private final ReviewImage mViewImage;
747 private boolean mEnableTrackballScroll;
748
749 public ImageViewTouch2(Context context) {
750 super(context);
751 mViewImage = (ReviewImage) context;
752 }
753
754 public ImageViewTouch2(Context context, AttributeSet attrs) {
755 super(context, attrs);
756 mViewImage = (ReviewImage) context;
757 }
758
759 public void setEnableTrackballScroll(boolean enable) {
760 mEnableTrackballScroll = enable;
761 }
762
763 protected void postTranslateCenter(float dx, float dy) {
764 super.postTranslate(dx, dy);
765 center(true, true);
766 }
767
Chih-Chung Chang522e8362009-08-26 16:12:34 +0800768 private static final float PAN_RATE = 20;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800769
Chih-Chung Chang1f463a62009-07-30 17:59:09 +0800770 // This is the time we allow the dpad to change the image position again.
Chih-Chung Chang522e8362009-08-26 16:12:34 +0800771 private long mNextChangePositionTime;
Chih-Chung Chang1f463a62009-07-30 17:59:09 +0800772
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800773 @Override
774 public boolean onKeyDown(int keyCode, KeyEvent event) {
Chih-Chung Chang6a1abe12009-09-24 21:17:51 -0700775 if (mViewImage.mPaused) return false;
776
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800777 // Don't respond to arrow keys if trackball scrolling is not enabled
778 if (!mEnableTrackballScroll) {
779 if ((keyCode >= KeyEvent.KEYCODE_DPAD_UP)
780 && (keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT)) {
781 return super.onKeyDown(keyCode, event);
782 }
783 }
784
785 int current = mViewImage.mCurrentPosition;
786
787 int nextImagePos = -2; // default no next image
788 try {
789 switch (keyCode) {
790 case KeyEvent.KEYCODE_DPAD_CENTER: {
791 if (mViewImage.isPickIntent()) {
792 IImage img = mViewImage.mAllImages
793 .getImageAt(mViewImage.mCurrentPosition);
794 mViewImage.setResult(ReviewImage.RESULT_OK,
795 new Intent().setData(img.fullSizeImageUri()));
796 mViewImage.finish();
797 }
798 break;
799 }
800 case KeyEvent.KEYCODE_DPAD_LEFT: {
Chih-Chung Chang1f463a62009-07-30 17:59:09 +0800801 if (getScale() <= 1F && event.getEventTime()
Chih-Chung Chang522e8362009-08-26 16:12:34 +0800802 >= mNextChangePositionTime) {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800803 nextImagePos = current - 1;
Chih-Chung Chang522e8362009-08-26 16:12:34 +0800804 mNextChangePositionTime = event.getEventTime() + 500;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800805 } else {
806 panBy(PAN_RATE, 0);
807 center(true, false);
808 }
809 return true;
810 }
811 case KeyEvent.KEYCODE_DPAD_RIGHT: {
Chih-Chung Chang1f463a62009-07-30 17:59:09 +0800812 if (getScale() <= 1F && event.getEventTime()
Chih-Chung Chang522e8362009-08-26 16:12:34 +0800813 >= mNextChangePositionTime) {
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800814 nextImagePos = current + 1;
Chih-Chung Chang522e8362009-08-26 16:12:34 +0800815 mNextChangePositionTime = event.getEventTime() + 500;
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800816 } else {
817 panBy(-PAN_RATE, 0);
818 center(true, false);
819 }
820 return true;
821 }
822 case KeyEvent.KEYCODE_DPAD_UP: {
823 panBy(0, PAN_RATE);
824 center(false, true);
825 return true;
826 }
827 case KeyEvent.KEYCODE_DPAD_DOWN: {
828 panBy(0, -PAN_RATE);
829 center(false, true);
830 return true;
831 }
832 case KeyEvent.KEYCODE_DEL:
833 MenuHelper.deletePhoto(
834 mViewImage, mViewImage.mDeletePhotoRunnable);
835 break;
836 }
837 } finally {
838 if (nextImagePos >= 0
839 && nextImagePos < mViewImage.mAllImages.getCount()) {
840 synchronized (mViewImage) {
Owen Lin69bd2712009-08-06 15:29:34 +0800841 mViewImage.setImage(nextImagePos, true);
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800842 }
843 } else if (nextImagePos != -2) {
844 center(true, true);
845 }
846 }
847
848 return super.onKeyDown(keyCode, event);
849 }
Wu-cheng Li46fc7ae2009-06-19 19:28:47 +0800850}