• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.camera;
18 
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.graphics.Bitmap;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.provider.MediaStore;
26 import android.util.AttributeSet;
27 import android.util.Log;
28 import android.view.GestureDetector;
29 import android.view.KeyEvent;
30 import android.view.Menu;
31 import android.view.MenuItem;
32 import android.view.MotionEvent;
33 import android.view.View;
34 import android.view.Window;
35 import android.view.WindowManager;
36 import android.view.View.OnTouchListener;
37 import android.view.animation.AlphaAnimation;
38 import android.view.animation.Animation;
39 import android.widget.Toast;
40 import android.widget.ZoomButtonsController;
41 
42 import com.android.camera.gallery.IImage;
43 import com.android.camera.gallery.IImageList;
44 import com.android.camera.gallery.VideoObject;
45 
46 // 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.
51 public class ReviewImage extends Activity implements View.OnClickListener {
52     private static final String STATE_URI = "uri";
53     private static final String TAG = "ReviewImage";
54     private static final double ASPECT_RATIO = 4.0 / 3.0;
55 
56     private ImageGetter mGetter;
57     private Uri mSavedUri;
58     boolean mPaused = true;
59     private boolean mShowControls = true;
60 
61     // Choices for what adjacents to load.
62     private static final int[] sOrderAdjacents = new int[] {0, 1, -1};
63 
64     final GetterHandler mHandler = new GetterHandler();
65 
66     private boolean mFullScreenInNormalMode;
67 
68     int mCurrentPosition = 0;
69 
70     private View mRootView;
71     private View mControlBar;
72     private View mNextImageView;
73     private View mPrevImageView;
74     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);
82 
83     public static final String KEY_IMAGE_LIST = "image_list";
84     private static final String STATE_SHOW_CONTROLS = "show_controls";
85 
86     ImageManager.ImageListParam mParam;
87     IImageList mAllImages;
88 
89     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;
100     private final Runnable mDismissOnScreenControlRunner = new Runnable() {
101         public void run() {
102             hideOnScreenControls();
103         }
104     };
105 
updateNextPrevControls()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 
138     private void showOnScreenControls() {
139 
140         // 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() {
146                     showOnScreenControls();
147                 }
148             });
149             return;
150         }
151 
152         // we may need to update the next/prev button due to index changing
153         updateNextPrevControls();
154 
155         if (ImageManager.isImage(mAllImages.getImageAt(mCurrentPosition))) {
156             updateZoomButtonsEnabled();
157             mZoomButtonsController.setVisible(true);
158         } else {
159             mZoomButtonsController.setVisible(false);
160         }
161     }
162 
163     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);
184     }
185 
186     @Override
187     public boolean dispatchTouchEvent(MotionEvent m) {
188         if (mZoomButtonsController.isVisible()) {
189             scheduleDismissOnScreenControls();
190         }
191         return super.dispatchTouchEvent(m);
192     }
193 
194     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() {
203         // 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 
212     private void setupOnScreenControls(View rootView, View ownerView) {
213         mNextImageView = rootView.findViewById(R.id.next_image);
214         mPrevImageView = rootView.findViewById(R.id.prev_image);
215 
216         mNextImageView.setOnClickListener(this);
217         mPrevImageView.setOnClickListener(this);
218 
219         setupZoomButtonController(ownerView);
220         setupOnTouchListeners(rootView);
221     }
222 
223     private void setupZoomButtonController(final View ownerView) {
224         mZoomButtonsController = new ZoomButtonsController(ownerView);
225         mZoomButtonsController.setAutoDismissed(false);
226         mZoomButtonsController.setZoomSpeed(100);
227         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                 }
241                 mZoomButtonsController.setVisible(true);
242                 updateZoomButtonsEnabled();
243             }
244         });
245     }
246 
247     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 
278     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
onSingleTapConfirmed(MotionEvent e)292         public boolean onSingleTapConfirmed(MotionEvent e) {
293             showOnScreenControls();
294             scheduleDismissOnScreenControls();
295             return true;
296         }
297 
298         @Override
onDoubleTap(MotionEvent e)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             }
308             return true;
309         }
310     }
311 
isPickIntent()312     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
onCreateOptionsMenu(Menu menu)319     public boolean onCreateOptionsMenu(Menu menu) {
320         super.onCreateOptionsMenu(menu);
321 
322         // 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 
328         mImageMenuRunnable = MenuHelper.addImageMenuItems(
329                 menu,
330                 MenuHelper.INCLUDE_ALL & ~sideButtons,
331                 ReviewImage.this,
332                 mHandler,
333                 mDeletePhotoRunnable,
334                 new MenuHelper.MenuInvoker() {
335                     public void run(final MenuHelper.MenuCallback cb) {
336                         if (mPaused) return;
337                         IImage image = mAllImages.getImageAt(mCurrentPosition);
338                         Uri uri = image.fullSizeImageUri();
339                         cb.run(uri, image);
340 
341                         mImageView.clear();
342                         setImage(mCurrentPosition, false);
343                     }
344                 });
345 
346         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();
354                 return;
355             } else {
356                 if (mCurrentPosition == mAllImages.getCount()) {
357                     mCurrentPosition -= 1;
358                 }
359             }
360             mImageView.clear();
361             mCache.clear();  // Because the position number is changed.
362             setImage(mCurrentPosition, true);
363         }
364     };
365 
366     @Override
onPrepareOptionsMenu(Menu menu)367     public boolean onPrepareOptionsMenu(Menu menu) {
368         super.onPrepareOptionsMenu(menu);
369         if (mPaused) return false;
370 
371         IImage image = mAllImages.getImageAt(mCurrentPosition);
372         if (mImageMenuRunnable != null) {
373             mImageMenuRunnable.gettingReadyToOpen(menu, image);
374         }
375 
376         Uri uri = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri();
377         MenuHelper.enableShareMenuItem(menu, MenuHelper.isWhiteListUri(uri));
378 
379         MenuHelper.enableShowOnMapMenuItem(menu, MenuHelper.hasLatLngData(image));
380 
381         return true;
382     }
383 
384     @Override
onMenuItemSelected(int featureId, MenuItem item)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 
setImage(int pos, boolean showControls)394     void setImage(int pos, boolean showControls) {
395         mCurrentPosition = pos;
396 
397         Bitmap b = mCache.getBitmap(pos);
398         if (b != null) {
399             IImage image = mAllImages.getImageAt(pos);
400             mImageView.setImageRotateBitmapResetBase(
401                     new RotateBitmap(b, image.getDegreesRotated()), true);
402             updateZoomButtonsEnabled();
403         }
404 
405         ImageGetterCallback cb = new ImageGetterCallback() {
406             public void completed() {
407             }
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) {
418                 // 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
420                 // user starts to zoom.
421                 // Originally the value is set to 480 in order to avoid OOM.
422                 // Now we set it to 2048 because of using
423                 // native memory allocation for Bitmaps.
424                 final int imageViewSize = 2048;
425                 return imageViewSize;
426             }
427 
428             public int [] loadOrder() {
429                 return sOrderAdjacents;
430             }
431 
432             public void imageLoaded(int pos, int offset, RotateBitmap bitmap,
433                                     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) {
443                     mCache.put(pos + offset, bitmap.getBitmap());
444                 }
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.
449                     mImageView.setImageRotateBitmapResetBase(bitmap, isThumb);
450                     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) {
457             mGetter.setPosition(pos, cb, mAllImages, mHandler);
458         }
459         updateActionIcons();
460         if (showControls) showOnScreenControls();
461         scheduleDismissOnScreenControls();
462     }
463 
464     @Override
onCreate(Bundle instanceState)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 
472         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 
483         ((PreviewFrameLayout) findViewById(
484                 R.id.frame_layout)).setAspectRatio(ASPECT_RATIO);
485 
486 
487         makeGetter();
488 
489         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 
498         mParam = getIntent().getParcelableExtra(KEY_IMAGE_LIST);
499 
500         if (instanceState != null) {
501             mSavedUri = instanceState.getParcelable(STATE_URI);
502             mShowControls = instanceState.getBoolean(STATE_SHOW_CONTROLS, true);
503         } else {
504             mSavedUri = getIntent().getData();
505         }
506 
507         int[] pickIds = {R.id.attach, R.id.cancel};
508         int[] reviewIds = {R.id.btn_delete, R.id.btn_share, R.id.btn_set_as,
509                 R.id.btn_play, R.id.btn_done};
510         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 
518         if (mFullScreenInNormalMode) {
519             getWindow().addFlags(
520                     WindowManager.LayoutParams.FLAG_FULLSCREEN);
521         }
522 
523         setupOnScreenControls(findViewById(R.id.mainPanel), mImageView);
524     }
525 
setButtonPanelVisibility(int id, int visibility)526     private void setButtonPanelVisibility(int id, int visibility) {
527         View button = mControlBar.findViewById(id);
528         ((View) button.getParent()).setVisibility(visibility);
529     }
530 
updateActionIcons()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 
makeGetter()544     private void makeGetter() {
545         mGetter = new ImageGetter(getContentResolver());
546     }
547 
buildImageListFromUri(Uri uri)548     private IImageList buildImageListFromUri(Uri uri) {
549         int sort = ImageManager.SORT_ASCENDING;
550         return ImageManager.makeImageList(getContentResolver(), uri, sort);
551     }
552 
init(Uri uri)553     private boolean init(Uri uri) {
554         if (uri == null) return false;
555         mAllImages = (mParam == null)
556                 ? buildImageListFromUri(uri)
557                 : ImageManager.makeImageList(getContentResolver(), mParam);
558         IImage image = mAllImages.getImageForUri(uri);
559         if (image == null) return false;
560         mCurrentPosition = mAllImages.getImageIndex(image);
561         return true;
562     }
563 
getCurrentUri()564     private Uri getCurrentUri() {
565         if (mAllImages.getCount() == 0) return null;
566         IImage image = mAllImages.getImageAt(mCurrentPosition);
567         return image.fullSizeImageUri();
568     }
569 
570     @Override
onSaveInstanceState(Bundle b)571     public void onSaveInstanceState(Bundle b) {
572         super.onSaveInstanceState(b);
573         b.putParcelable(STATE_URI,
574                 mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri());
575         b.putBoolean(STATE_SHOW_CONTROLS, mShowControls);
576     }
577 
578     @Override
onStart()579     public void onStart() {
580         super.onStart();
581         mPaused = false;
582 
583         if (!init(mSavedUri)) {
584             Log.w(TAG, "init failed: " + mSavedUri);
585             finish();
586             return;
587         }
588 
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();
595             return;
596         } else if (count <= mCurrentPosition) {
597             mCurrentPosition = count - 1;
598         }
599 
600         if (mGetter == null) {
601             makeGetter();
602         }
603 
604         //show controls only for first time
605         setImage(mCurrentPosition, mShowControls);
606         mShowControls = false;
607     }
608 
609     @Override
onStop()610     public void onStop() {
611         super.onStop();
612         mPaused = true;
613 
614         // 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         }
621 
622         // removing all callback in the message queue
623         mHandler.removeAllGetterCallbacks();
624 
625         if (mAllImages != null) {
626             mSavedUri = getCurrentUri();
627             mAllImages.close();
628             mAllImages = null;
629         }
630 
631         hideOnScreenControls();
632         mImageView.clear();
633         mCache.clear();
634 
635         for (ImageViewTouchBase iv : mSlideShowImageViews) {
636             iv.clear();
637         }
638     }
639 
startShareMediaActivity(IImage image)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 
startPlayVideoActivity()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 
onClick(View v)668     public void onClick(View v) {
669         switch (v.getId()) {
670             case R.id.btn_delete:
671                 MenuHelper.deleteImage(this, mDeletePhotoRunnable,
672                         mAllImages.getImageAt(mCurrentPosition));
673                 break;
674             case R.id.btn_play:
675                 startPlayVideoActivity();
676                 break;
677             case R.id.btn_share: {
678                 IImage image = mAllImages.getImageAt(mCurrentPosition);
679                 if (!MenuHelper.isWhiteListUri(image.fullSizeImageUri())) {
680                     return;
681                 }
682                 startShareMediaActivity(image);
683                 break;
684             }
685             case R.id.btn_set_as: {
686                 IImage image = mAllImages.getImageAt(mCurrentPosition);
687                 Intent intent = Util.createSetAsIntent(image);
688                 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 
moveNextOrPrevious(int delta)709     private void moveNextOrPrevious(int delta) {
710         int nextImagePos = mCurrentPosition + delta;
711         if ((0 <= nextImagePos) && (nextImagePos < mAllImages.getCount())) {
712             setImage(nextImagePos, true);
713             showOnScreenControls();
714         }
715     }
716 
717     @Override
onActivityResult(int requestCode, int resultCode, Intent data)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());
726 
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                     }
739                 }
740                 break;
741         }
742     }
743 }
744 
745 class 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 
768     private static final float PAN_RATE = 20;
769 
770     // This is the time we allow the dpad to change the image position again.
771     private long mNextChangePositionTime;
772 
773     @Override
774     public boolean onKeyDown(int keyCode, KeyEvent event) {
775         if (mViewImage.mPaused) return false;
776 
777         // 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: {
801                     if (getScale() <= 1F && event.getEventTime()
802                             >= mNextChangePositionTime) {
803                         nextImagePos = current - 1;
804                         mNextChangePositionTime = event.getEventTime() + 500;
805                     } else {
806                         panBy(PAN_RATE, 0);
807                         center(true, false);
808                     }
809                     return true;
810                 }
811                 case KeyEvent.KEYCODE_DPAD_RIGHT: {
812                     if (getScale() <= 1F && event.getEventTime()
813                             >= mNextChangePositionTime) {
814                         nextImagePos = current + 1;
815                         mNextChangePositionTime = event.getEventTime() + 500;
816                     } 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) {
841                     mViewImage.setImage(nextImagePos, true);
842                 }
843            } else if (nextImagePos != -2) {
844                center(true, true);
845            }
846         }
847 
848         return super.onKeyDown(keyCode, event);
849     }
850 }
851