• 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 com.android.gallery.R;
20 
21 import android.app.Activity;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.SharedPreferences;
25 import android.graphics.Bitmap;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.preference.PreferenceManager;
29 import android.provider.MediaStore;
30 import android.util.AttributeSet;
31 import android.util.Log;
32 import android.view.GestureDetector;
33 import android.view.KeyEvent;
34 import android.view.Menu;
35 import android.view.MenuItem;
36 import android.view.MotionEvent;
37 import android.view.View;
38 import android.view.Window;
39 import android.view.WindowManager;
40 import android.view.View.OnTouchListener;
41 import android.view.animation.AlphaAnimation;
42 import android.view.animation.Animation;
43 import android.view.animation.AnimationUtils;
44 import android.widget.Toast;
45 import android.widget.ZoomButtonsController;
46 
47 import com.android.camera.gallery.IImage;
48 import com.android.camera.gallery.IImageList;
49 import com.android.camera.gallery.VideoObject;
50 
51 import java.util.Random;
52 
53 // This activity can display a whole picture and navigate them in a specific
54 // gallery. It has two modes: normal mode and slide show mode. In normal mode
55 // the user view one image at a time, and can click "previous" and "next"
56 // button to see the previous or next image. In slide show mode it shows one
57 // image after another, with some transition effect.
58 public class ViewImage extends Activity implements View.OnClickListener {
59     private static final String PREF_SLIDESHOW_REPEAT =
60             "pref_gallery_slideshow_repeat_key";
61     private static final String PREF_SHUFFLE_SLIDESHOW =
62             "pref_gallery_slideshow_shuffle_key";
63     private static final String STATE_URI = "uri";
64     private static final String STATE_SLIDESHOW = "slideshow";
65     private static final String EXTRA_SLIDESHOW = "slideshow";
66     private static final String TAG = "ViewImage";
67 
68     private ImageGetter mGetter;
69     private Uri mSavedUri;
70     boolean mPaused = true;
71     private boolean mShowControls = true;
72 
73     // Choices for what adjacents to load.
74     private static final int[] sOrderAdjacents = new int[] {0, 1, -1};
75     private static final int[] sOrderSlideshow = new int[] {0};
76 
77     final GetterHandler mHandler = new GetterHandler();
78 
79     private final Random mRandom = new Random(System.currentTimeMillis());
80     private int [] mShuffleOrder = null;
81     private boolean mUseShuffleOrder = false;
82     private boolean mSlideShowLoop = false;
83 
84     static final int MODE_NORMAL = 1;
85     static final int MODE_SLIDESHOW = 2;
86     private int mMode = MODE_NORMAL;
87 
88     private boolean mFullScreenInNormalMode;
89     private boolean mShowActionIcons;
90     private View mActionIconPanel;
91 
92     private int mSlideShowInterval;
93     private int mLastSlideShowImage;
94     int mCurrentPosition = 0;
95 
96     // represents which style animation to use
97     private int mAnimationIndex;
98     private Animation [] mSlideShowInAnimation;
99     private Animation [] mSlideShowOutAnimation;
100 
101     private SharedPreferences mPrefs;
102 
103     private View mNextImageView;
104     private View mPrevImageView;
105     private final Animation mHideNextImageViewAnimation =
106             new AlphaAnimation(1F, 0F);
107     private final Animation mHidePrevImageViewAnimation =
108             new AlphaAnimation(1F, 0F);
109     private final Animation mShowNextImageViewAnimation =
110             new AlphaAnimation(0F, 1F);
111     private final Animation mShowPrevImageViewAnimation =
112             new AlphaAnimation(0F, 1F);
113 
114     public static final String KEY_IMAGE_LIST = "image_list";
115     private static final String STATE_SHOW_CONTROLS = "show_controls";
116 
117     IImageList mAllImages;
118 
119     private ImageManager.ImageListParam mParam;
120 
121     private int mSlideShowImageCurrent = 0;
122     private final ImageViewTouchBase [] mSlideShowImageViews =
123             new ImageViewTouchBase[2];
124 
125     GestureDetector mGestureDetector;
126     private ZoomButtonsController mZoomButtonsController;
127 
128     // The image view displayed for normal mode.
129     private ImageViewTouch mImageView;
130     // This is the cache for thumbnail bitmaps.
131     private BitmapCache mCache;
132     private MenuHelper.MenuItemsResult mImageMenuRunnable;
133     private final Runnable mDismissOnScreenControlRunner = new Runnable() {
134         public void run() {
135             hideOnScreenControls();
136         }
137     };
138 
updateNextPrevControls()139     private void updateNextPrevControls() {
140         boolean showPrev = mCurrentPosition > 0;
141         boolean showNext = mCurrentPosition < mAllImages.getCount() - 1;
142 
143         boolean prevIsVisible = mPrevImageView.getVisibility() == View.VISIBLE;
144         boolean nextIsVisible = mNextImageView.getVisibility() == View.VISIBLE;
145 
146         if (showPrev && !prevIsVisible) {
147             Animation a = mShowPrevImageViewAnimation;
148             a.setDuration(500);
149             mPrevImageView.startAnimation(a);
150             mPrevImageView.setVisibility(View.VISIBLE);
151         } else if (!showPrev && prevIsVisible) {
152             Animation a = mHidePrevImageViewAnimation;
153             a.setDuration(500);
154             mPrevImageView.startAnimation(a);
155             mPrevImageView.setVisibility(View.GONE);
156         }
157 
158         if (showNext && !nextIsVisible) {
159             Animation a = mShowNextImageViewAnimation;
160             a.setDuration(500);
161             mNextImageView.startAnimation(a);
162             mNextImageView.setVisibility(View.VISIBLE);
163         } else if (!showNext && nextIsVisible) {
164             Animation a = mHideNextImageViewAnimation;
165             a.setDuration(500);
166             mNextImageView.startAnimation(a);
167             mNextImageView.setVisibility(View.GONE);
168         }
169     }
170 
171     private void hideOnScreenControls() {
172         if (mShowActionIcons
173                 && mActionIconPanel.getVisibility() == View.VISIBLE) {
174             Animation animation = new AlphaAnimation(1, 0);
175             animation.setDuration(500);
176             mActionIconPanel.startAnimation(animation);
177             mActionIconPanel.setVisibility(View.INVISIBLE);
178         }
179 
180         if (mNextImageView.getVisibility() == View.VISIBLE) {
181             Animation a = mHideNextImageViewAnimation;
182             a.setDuration(500);
183             mNextImageView.startAnimation(a);
184             mNextImageView.setVisibility(View.INVISIBLE);
185         }
186 
187         if (mPrevImageView.getVisibility() == View.VISIBLE) {
188             Animation a = mHidePrevImageViewAnimation;
189             a.setDuration(500);
190             mPrevImageView.startAnimation(a);
191             mPrevImageView.setVisibility(View.INVISIBLE);
192         }
193 
194         mZoomButtonsController.setVisible(false);
195     }
196 
197     private void showOnScreenControls() {
198         if (mPaused) return;
199         // If the view has not been attached to the window yet, the
200         // zoomButtonControls will not able to show up. So delay it until the
201         // view has attached to window.
202         if (mActionIconPanel.getWindowToken() == null) {
203             mHandler.postGetterCallback(new Runnable() {
204                 public void run() {
205                     showOnScreenControls();
206                 }
207             });
208             return;
209         }
210         updateNextPrevControls();
211 
212         IImage image = mAllImages.getImageAt(mCurrentPosition);
213         if (image instanceof VideoObject) {
214             mZoomButtonsController.setVisible(false);
215         } else {
216             updateZoomButtonsEnabled();
217             mZoomButtonsController.setVisible(true);
218         }
219 
220         if (mShowActionIcons
221                 && mActionIconPanel.getVisibility() != View.VISIBLE) {
222             Animation animation = new AlphaAnimation(0, 1);
223             animation.setDuration(500);
224             mActionIconPanel.startAnimation(animation);
225             mActionIconPanel.setVisibility(View.VISIBLE);
226         }
227     }
228 
229     @Override
230     public boolean dispatchTouchEvent(MotionEvent m) {
231         if (mPaused) return true;
232         if (mZoomButtonsController.isVisible()) {
233             scheduleDismissOnScreenControls();
234         }
235         return super.dispatchTouchEvent(m);
236     }
237 
238     private void updateZoomButtonsEnabled() {
239         ImageViewTouch imageView = mImageView;
240         float scale = imageView.getScale();
241         mZoomButtonsController.setZoomInEnabled(scale < imageView.mMaxZoom);
242         mZoomButtonsController.setZoomOutEnabled(scale > 1);
243     }
244 
245     @Override
246     protected void onDestroy() {
247         // This is necessary to make the ZoomButtonsController unregister
248         // its configuration change receiver.
249         if (mZoomButtonsController != null) {
250             mZoomButtonsController.setVisible(false);
251         }
252         super.onDestroy();
253     }
254 
255     private void scheduleDismissOnScreenControls() {
256         mHandler.removeCallbacks(mDismissOnScreenControlRunner);
257         mHandler.postDelayed(mDismissOnScreenControlRunner, 2000);
258     }
259 
260     private void setupOnScreenControls(View rootView, View ownerView) {
261         mNextImageView = rootView.findViewById(R.id.next_image);
262         mPrevImageView = rootView.findViewById(R.id.prev_image);
263 
264         mNextImageView.setOnClickListener(this);
265         mPrevImageView.setOnClickListener(this);
266 
267         setupZoomButtonController(ownerView);
268         setupOnTouchListeners(rootView);
269     }
270 
271     private void setupZoomButtonController(final View ownerView) {
272         mZoomButtonsController = new ZoomButtonsController(ownerView);
273         mZoomButtonsController.setAutoDismissed(false);
274         mZoomButtonsController.setZoomSpeed(100);
275         mZoomButtonsController.setOnZoomListener(
276                 new ZoomButtonsController.OnZoomListener() {
277             public void onVisibilityChanged(boolean visible) {
278                 if (visible) {
279                     updateZoomButtonsEnabled();
280                 }
281             }
282 
283             public void onZoom(boolean zoomIn) {
284                 if (zoomIn) {
285                     mImageView.zoomIn();
286                 } else {
287                     mImageView.zoomOut();
288                 }
289                 mZoomButtonsController.setVisible(true);
290                 updateZoomButtonsEnabled();
291             }
292         });
293     }
294 
295     private void setupOnTouchListeners(View rootView) {
296         mGestureDetector = new GestureDetector(this, new MyGestureListener());
297 
298         // If the user touches anywhere on the panel (including the
299         // next/prev button). We show the on-screen controls. In addition
300         // to that, if the touch is not on the prev/next button, we
301         // pass the event to the gesture detector to detect double tap.
302         final OnTouchListener buttonListener = new OnTouchListener() {
303             public boolean onTouch(View v, MotionEvent event) {
304                 scheduleDismissOnScreenControls();
305                 return false;
306             }
307         };
308 
309         OnTouchListener rootListener = new OnTouchListener() {
310             public boolean onTouch(View v, MotionEvent event) {
311                 buttonListener.onTouch(v, event);
312                 mGestureDetector.onTouchEvent(event);
313 
314                 // We do not use the return value of
315                 // mGestureDetector.onTouchEvent because we will not receive
316                 // the "up" event if we return false for the "down" event.
317                 return true;
318             }
319         };
320 
321         mNextImageView.setOnTouchListener(buttonListener);
322         mPrevImageView.setOnTouchListener(buttonListener);
323         rootView.setOnTouchListener(rootListener);
324     }
325 
326     private class MyGestureListener extends
327             GestureDetector.SimpleOnGestureListener {
328 
329         @Override
330         public boolean onScroll(MotionEvent e1, MotionEvent e2,
331                 float distanceX, float distanceY) {
332             ImageViewTouch imageView = mImageView;
333             if (imageView.getScale() > 1F) {
334                 imageView.postTranslateCenter(-distanceX, -distanceY);
335             }
336             return true;
337         }
338 
339         @Override
onSingleTapUp(MotionEvent e)340         public boolean onSingleTapUp(MotionEvent e) {
341             setMode(MODE_NORMAL);
342             return true;
343         }
344 
345         @Override
onSingleTapConfirmed(MotionEvent e)346         public boolean onSingleTapConfirmed(MotionEvent e) {
347             showOnScreenControls();
348             scheduleDismissOnScreenControls();
349             return true;
350         }
351 
352         @Override
onDoubleTap(MotionEvent e)353         public boolean onDoubleTap(MotionEvent e) {
354             ImageViewTouch imageView = mImageView;
355 
356             // Switch between the original scale and 3x scale.
357             if (imageView.getScale() > 2F) {
358                 mImageView.zoomTo(1f);
359             } else {
360                 mImageView.zoomToPoint(3f, e.getX(), e.getY());
361             }
362             return true;
363         }
364     }
365 
isPickIntent()366     boolean isPickIntent() {
367         String action = getIntent().getAction();
368         return (Intent.ACTION_PICK.equals(action)
369                 || Intent.ACTION_GET_CONTENT.equals(action));
370     }
371 
372     @Override
onCreateOptionsMenu(Menu menu)373     public boolean onCreateOptionsMenu(Menu menu) {
374         super.onCreateOptionsMenu(menu);
375 
376         MenuItem item = menu.add(Menu.NONE, Menu.NONE,
377                 MenuHelper.POSITION_SLIDESHOW,
378                 R.string.slide_show);
379         item.setOnMenuItemClickListener(
380                 new MenuItem.OnMenuItemClickListener() {
381             public boolean onMenuItemClick(MenuItem item) {
382                 setMode(MODE_SLIDESHOW);
383                 mLastSlideShowImage = mCurrentPosition;
384                 loadNextImage(mCurrentPosition, 0, true);
385                 return true;
386             }
387         });
388         item.setIcon(android.R.drawable.ic_menu_slideshow);
389 
390         mImageMenuRunnable = MenuHelper.addImageMenuItems(
391                 menu,
392                 MenuHelper.INCLUDE_ALL,
393                 ViewImage.this,
394                 mHandler,
395                 mDeletePhotoRunnable,
396                 new MenuHelper.MenuInvoker() {
397                     public void run(final MenuHelper.MenuCallback cb) {
398                         if (mPaused) return;
399                         setMode(MODE_NORMAL);
400 
401                         IImage image = mAllImages.getImageAt(mCurrentPosition);
402                         Uri uri = image.fullSizeImageUri();
403                         cb.run(uri, image);
404 
405                         mImageView.clear();
406                         setImage(mCurrentPosition, false);
407                     }
408                 });
409 
410         item = menu.add(Menu.NONE, Menu.NONE,
411                 MenuHelper.POSITION_GALLERY_SETTING, R.string.camerasettings);
412         item.setOnMenuItemClickListener(
413                 new MenuItem.OnMenuItemClickListener() {
414             public boolean onMenuItemClick(MenuItem item) {
415                 Intent preferences = new Intent();
416                 preferences.setClass(ViewImage.this, GallerySettings.class);
417                 startActivity(preferences);
418                 return true;
419             }
420         });
421         item.setAlphabeticShortcut('p');
422         item.setIcon(android.R.drawable.ic_menu_preferences);
423 
424         return true;
425     }
426 
427     protected Runnable mDeletePhotoRunnable = new Runnable() {
428         public void run() {
429             mAllImages.removeImageAt(mCurrentPosition);
430             if (mAllImages.getCount() == 0) {
431                 finish();
432                 return;
433             } else {
434                 if (mCurrentPosition == mAllImages.getCount()) {
435                     mCurrentPosition -= 1;
436                 }
437             }
438             mImageView.clear();
439             mCache.clear();  // Because the position number is changed.
440             setImage(mCurrentPosition, true);
441         }
442     };
443 
444     @Override
onPrepareOptionsMenu(Menu menu)445     public boolean onPrepareOptionsMenu(Menu menu) {
446 
447         super.onPrepareOptionsMenu(menu);
448         if (mPaused) return false;
449 
450         setMode(MODE_NORMAL);
451         IImage image = mAllImages.getImageAt(mCurrentPosition);
452 
453         if (mImageMenuRunnable != null) {
454             mImageMenuRunnable.gettingReadyToOpen(menu, image);
455         }
456 
457         Uri uri = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri();
458         MenuHelper.enableShareMenuItem(menu, MenuHelper.isWhiteListUri(uri));
459 
460         MenuHelper.enableShowOnMapMenuItem(menu, MenuHelper.hasLatLngData(image));
461 
462         return true;
463     }
464 
465     @Override
onMenuItemSelected(int featureId, MenuItem item)466     public boolean onMenuItemSelected(int featureId, MenuItem item) {
467         boolean b = super.onMenuItemSelected(featureId, item);
468         if (mImageMenuRunnable != null) {
469             mImageMenuRunnable.aboutToCall(item,
470                     mAllImages.getImageAt(mCurrentPosition));
471         }
472         return b;
473     }
474 
setImage(int pos, boolean showControls)475     void setImage(int pos, boolean showControls) {
476         mCurrentPosition = pos;
477 
478         Bitmap b = mCache.getBitmap(pos);
479         if (b != null) {
480             IImage image = mAllImages.getImageAt(pos);
481             mImageView.setImageRotateBitmapResetBase(
482                     new RotateBitmap(b, image.getDegreesRotated()), true);
483             updateZoomButtonsEnabled();
484         }
485 
486         ImageGetterCallback cb = new ImageGetterCallback() {
487             public void completed() {
488             }
489 
490             public boolean wantsThumbnail(int pos, int offset) {
491                 return !mCache.hasBitmap(pos + offset);
492             }
493 
494             public boolean wantsFullImage(int pos, int offset) {
495                 return offset == 0;
496             }
497 
498             public int fullImageSizeToUse(int pos, int offset) {
499                 // this number should be bigger so that we can zoom.  we may
500                 // need to get fancier and read in the fuller size image as the
501                 // user starts to zoom.
502                 // Originally the value is set to 480 in order to avoid OOM.
503                 // Now we set it to 2048 because of using
504                 // native memory allocation for Bitmaps.
505                 final int imageViewSize = 2048;
506                 return imageViewSize;
507             }
508 
509             public int [] loadOrder() {
510                 return sOrderAdjacents;
511             }
512 
513             public void imageLoaded(int pos, int offset, RotateBitmap bitmap,
514                                     boolean isThumb) {
515                 // shouldn't get here after onPause()
516 
517                 // We may get a result from a previous request. Ignore it.
518                 if (pos != mCurrentPosition) {
519                     bitmap.recycle();
520                     return;
521                 }
522 
523                 if (isThumb) {
524                     mCache.put(pos + offset, bitmap.getBitmap());
525                 }
526                 if (offset == 0) {
527                     // isThumb: We always load thumb bitmap first, so we will
528                     // reset the supp matrix for then thumb bitmap, and keep
529                     // the supp matrix when the full bitmap is loaded.
530                     mImageView.setImageRotateBitmapResetBase(bitmap, isThumb);
531                     updateZoomButtonsEnabled();
532                 }
533             }
534         };
535 
536         // Could be null if we're stopping a slide show in the course of pausing
537         if (mGetter != null) {
538             mGetter.setPosition(pos, cb, mAllImages, mHandler);
539         }
540         updateActionIcons();
541         if (showControls) showOnScreenControls();
542         scheduleDismissOnScreenControls();
543     }
544 
545     @Override
onCreate(Bundle instanceState)546     public void onCreate(Bundle instanceState) {
547         super.onCreate(instanceState);
548 
549         Intent intent = getIntent();
550         mFullScreenInNormalMode = intent.getBooleanExtra(
551                 MediaStore.EXTRA_FULL_SCREEN, true);
552         mShowActionIcons = intent.getBooleanExtra(
553                 MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
554 
555         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
556 
557         setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
558         requestWindowFeature(Window.FEATURE_NO_TITLE);
559         setContentView(R.layout.viewimage);
560 
561         mImageView = (ImageViewTouch) findViewById(R.id.image);
562         mImageView.setEnableTrackballScroll(true);
563         mCache = new BitmapCache(3);
564         mImageView.setRecycler(mCache);
565 
566         makeGetter();
567 
568         mAnimationIndex = -1;
569 
570         mSlideShowInAnimation = new Animation[] {
571             makeInAnimation(R.anim.transition_in),
572             makeInAnimation(R.anim.slide_in),
573             makeInAnimation(R.anim.slide_in_vertical),
574         };
575 
576         mSlideShowOutAnimation = new Animation[] {
577             makeOutAnimation(R.anim.transition_out),
578             makeOutAnimation(R.anim.slide_out),
579             makeOutAnimation(R.anim.slide_out_vertical),
580         };
581 
582         mSlideShowImageViews[0] =
583                 (ImageViewTouchBase) findViewById(R.id.image1_slideShow);
584         mSlideShowImageViews[1] =
585                 (ImageViewTouchBase) findViewById(R.id.image2_slideShow);
586         for (ImageViewTouchBase v : mSlideShowImageViews) {
587             v.setVisibility(View.INVISIBLE);
588             v.setRecycler(mCache);
589         }
590 
591         mActionIconPanel = findViewById(R.id.action_icon_panel);
592 
593         mParam = getIntent().getParcelableExtra(KEY_IMAGE_LIST);
594 
595         boolean slideshow;
596         if (instanceState != null) {
597             mSavedUri = instanceState.getParcelable(STATE_URI);
598             slideshow = instanceState.getBoolean(STATE_SLIDESHOW, false);
599             mShowControls = instanceState.getBoolean(STATE_SHOW_CONTROLS, true);
600         } else {
601             mSavedUri = getIntent().getData();
602             slideshow = intent.getBooleanExtra(EXTRA_SLIDESHOW, false);
603         }
604 
605         // We only show action icons for URIs that we know we can share and
606         // delete. Although we get read permission (for the images) from
607         // applications like MMS, we cannot pass the permission to other
608         // activities due to the current framework design.
609         if (!MenuHelper.isWhiteListUri(mSavedUri)) {
610             mShowActionIcons = false;
611         }
612 
613         if (mShowActionIcons) {
614             int[] pickIds = {R.id.attach, R.id.cancel};
615             int[] normalIds = {R.id.setas, R.id.play, R.id.share, R.id.discard};
616             int[] connectIds = isPickIntent() ? pickIds : normalIds;
617             for (int id : connectIds) {
618                 View view = mActionIconPanel.findViewById(id);
619                 view.setVisibility(View.VISIBLE);
620                 view.setOnClickListener(this);
621             }
622         }
623 
624         // Don't show the "delete" icon for SingleImageList.
625         if (ImageManager.isSingleImageMode(mSavedUri.toString())) {
626             mActionIconPanel.findViewById(R.id.discard)
627                     .setVisibility(View.GONE);
628         }
629 
630         if (slideshow) {
631             setMode(MODE_SLIDESHOW);
632         } else {
633             if (mFullScreenInNormalMode) {
634                 getWindow().addFlags(
635                         WindowManager.LayoutParams.FLAG_FULLSCREEN);
636             }
637             if (mShowActionIcons) {
638                 mActionIconPanel.setVisibility(View.VISIBLE);
639             }
640         }
641 
642         setupOnScreenControls(findViewById(R.id.rootLayout), mImageView);
643     }
644 
updateActionIcons()645     private void updateActionIcons() {
646         if (isPickIntent()) return;
647 
648         IImage image = mAllImages.getImageAt(mCurrentPosition);
649         View panel = mActionIconPanel;
650         if (image instanceof VideoObject) {
651             panel.findViewById(R.id.setas).setVisibility(View.GONE);
652             panel.findViewById(R.id.play).setVisibility(View.VISIBLE);
653         } else {
654             panel.findViewById(R.id.setas).setVisibility(View.VISIBLE);
655             panel.findViewById(R.id.play).setVisibility(View.GONE);
656         }
657     }
658 
makeInAnimation(int id)659     private Animation makeInAnimation(int id) {
660         Animation inAnimation = AnimationUtils.loadAnimation(this, id);
661         return inAnimation;
662     }
663 
makeOutAnimation(int id)664     private Animation makeOutAnimation(int id) {
665         Animation outAnimation = AnimationUtils.loadAnimation(this, id);
666         return outAnimation;
667     }
668 
getPreferencesInteger( SharedPreferences prefs, String key, int defaultValue)669     private static int getPreferencesInteger(
670             SharedPreferences prefs, String key, int defaultValue) {
671         String value = prefs.getString(key, null);
672         try {
673             return value == null ? defaultValue : Integer.parseInt(value);
674         } catch (NumberFormatException ex) {
675             Log.e(TAG, "couldn't parse preference: " + value, ex);
676             return defaultValue;
677         }
678     }
679 
setMode(int mode)680     void setMode(int mode) {
681         if (mMode == mode) {
682             return;
683         }
684         View slideshowPanel = findViewById(R.id.slideShowContainer);
685         View normalPanel = findViewById(R.id.abs);
686 
687         Window win = getWindow();
688         mMode = mode;
689         if (mode == MODE_SLIDESHOW) {
690             slideshowPanel.setVisibility(View.VISIBLE);
691             normalPanel.setVisibility(View.GONE);
692 
693             win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN
694                     | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
695 
696             mImageView.clear();
697             mActionIconPanel.setVisibility(View.GONE);
698 
699             slideshowPanel.getRootView().requestLayout();
700 
701             // The preferences we want to read:
702             //   mUseShuffleOrder
703             //   mSlideShowLoop
704             //   mAnimationIndex
705             //   mSlideShowInterval
706 
707             mUseShuffleOrder = mPrefs.getBoolean(PREF_SHUFFLE_SLIDESHOW, false);
708             mSlideShowLoop = mPrefs.getBoolean(PREF_SLIDESHOW_REPEAT, false);
709             mAnimationIndex = getPreferencesInteger(
710                     mPrefs, "pref_gallery_slideshow_transition_key", 0);
711             mSlideShowInterval = getPreferencesInteger(
712                     mPrefs, "pref_gallery_slideshow_interval_key", 3) * 1000;
713         } else {
714             slideshowPanel.setVisibility(View.GONE);
715             normalPanel.setVisibility(View.VISIBLE);
716 
717             win.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
718             if (mFullScreenInNormalMode) {
719                 win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
720             } else {
721                 win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
722             }
723 
724             if (mGetter != null) {
725                 mGetter.cancelCurrent();
726             }
727 
728             if (mShowActionIcons) {
729                 Animation animation = new AlphaAnimation(0F, 1F);
730                 animation.setDuration(500);
731                 mActionIconPanel.setAnimation(animation);
732                 mActionIconPanel.setVisibility(View.VISIBLE);
733             }
734 
735             ImageViewTouchBase dst = mImageView;
736             dst.mLastXTouchPos = -1;
737             dst.mLastYTouchPos = -1;
738 
739             for (ImageViewTouchBase ivt : mSlideShowImageViews) {
740                 ivt.clear();
741             }
742 
743             mShuffleOrder = null;
744 
745             // mGetter null is a proxy for being paused
746             if (mGetter != null) {
747                 setImage(mCurrentPosition, true);
748             }
749         }
750     }
751 
generateShuffleOrder()752     private void generateShuffleOrder() {
753         if (mShuffleOrder == null
754                 || mShuffleOrder.length != mAllImages.getCount()) {
755             mShuffleOrder = new int[mAllImages.getCount()];
756             for (int i = 0, n = mShuffleOrder.length; i < n; i++) {
757                 mShuffleOrder[i] = i;
758             }
759         }
760 
761         for (int i = mShuffleOrder.length - 1; i >= 0; i--) {
762             int r = mRandom.nextInt(i + 1);
763             if (r != i) {
764                 int tmp = mShuffleOrder[r];
765                 mShuffleOrder[r] = mShuffleOrder[i];
766                 mShuffleOrder[i] = tmp;
767             }
768         }
769     }
770 
loadNextImage(final int requestedPos, final long delay, final boolean firstCall)771     private void loadNextImage(final int requestedPos, final long delay,
772                                final boolean firstCall) {
773         if (firstCall && mUseShuffleOrder) {
774             generateShuffleOrder();
775         }
776 
777         final long targetDisplayTime = System.currentTimeMillis() + delay;
778 
779         ImageGetterCallback cb = new ImageGetterCallback() {
780             public void completed() {
781             }
782 
783             public boolean wantsThumbnail(int pos, int offset) {
784                 return true;
785             }
786 
787             public boolean wantsFullImage(int pos, int offset) {
788                 return false;
789             }
790 
791             public int [] loadOrder() {
792                 return sOrderSlideshow;
793             }
794 
795             public int fullImageSizeToUse(int pos, int offset) {
796                 return 480; // TODO compute this
797             }
798 
799             public void imageLoaded(final int pos, final int offset,
800                     final RotateBitmap bitmap, final boolean isThumb) {
801                 long timeRemaining = Math.max(0,
802                         targetDisplayTime - System.currentTimeMillis());
803                 mHandler.postDelayedGetterCallback(new Runnable() {
804                     public void run() {
805                         if (mMode == MODE_NORMAL) {
806                             return;
807                         }
808 
809                         ImageViewTouchBase oldView =
810                                 mSlideShowImageViews[mSlideShowImageCurrent];
811 
812                         if (++mSlideShowImageCurrent
813                                 == mSlideShowImageViews.length) {
814                             mSlideShowImageCurrent = 0;
815                         }
816 
817                         ImageViewTouchBase newView =
818                                 mSlideShowImageViews[mSlideShowImageCurrent];
819                         newView.setVisibility(View.VISIBLE);
820                         newView.setImageRotateBitmapResetBase(bitmap, true);
821                         newView.bringToFront();
822 
823                         int animation = 0;
824 
825                         if (mAnimationIndex == -1) {
826                             int n = mRandom.nextInt(
827                                     mSlideShowInAnimation.length);
828                             animation = n;
829                         } else {
830                             animation = mAnimationIndex;
831                         }
832 
833                         Animation aIn = mSlideShowInAnimation[animation];
834                         newView.startAnimation(aIn);
835                         newView.setVisibility(View.VISIBLE);
836 
837                         Animation aOut = mSlideShowOutAnimation[animation];
838                         oldView.setVisibility(View.INVISIBLE);
839                         oldView.startAnimation(aOut);
840 
841                         mCurrentPosition = requestedPos;
842 
843                         if (mCurrentPosition == mLastSlideShowImage
844                                 && !firstCall) {
845                             if (mSlideShowLoop) {
846                                 if (mUseShuffleOrder) {
847                                     generateShuffleOrder();
848                                 }
849                             } else {
850                                 setMode(MODE_NORMAL);
851                                 return;
852                             }
853                         }
854 
855                         loadNextImage(
856                                 (mCurrentPosition + 1) % mAllImages.getCount(),
857                                 mSlideShowInterval, false);
858                     }
859                 }, timeRemaining);
860             }
861         };
862         // Could be null if we're stopping a slide show in the course of pausing
863         if (mGetter != null) {
864             int pos = requestedPos;
865             if (mShuffleOrder != null) {
866                 pos = mShuffleOrder[pos];
867             }
868             mGetter.setPosition(pos, cb, mAllImages, mHandler);
869         }
870     }
871 
makeGetter()872     private void makeGetter() {
873         mGetter = new ImageGetter(getContentResolver());
874     }
875 
buildImageListFromUri(Uri uri)876     private IImageList buildImageListFromUri(Uri uri) {
877         String sortOrder = mPrefs.getString(
878                 "pref_gallery_sort_key", "descending");
879         int sort = sortOrder.equals("ascending")
880                 ? ImageManager.SORT_ASCENDING
881                 : ImageManager.SORT_DESCENDING;
882         return ImageManager.makeImageList(getContentResolver(), uri, sort);
883     }
884 
init(Uri uri)885     private boolean init(Uri uri) {
886         if (uri == null) return false;
887         mAllImages = (mParam == null)
888                 ? buildImageListFromUri(uri)
889                 : ImageManager.makeImageList(getContentResolver(), mParam);
890         IImage image = mAllImages.getImageForUri(uri);
891         if (image == null) return false;
892         mCurrentPosition = mAllImages.getImageIndex(image);
893         mLastSlideShowImage = mCurrentPosition;
894         return true;
895     }
896 
getCurrentUri()897     private Uri getCurrentUri() {
898         if (mAllImages.getCount() == 0) return null;
899         IImage image = mAllImages.getImageAt(mCurrentPosition);
900         return image.fullSizeImageUri();
901     }
902 
903     @Override
onSaveInstanceState(Bundle b)904     public void onSaveInstanceState(Bundle b) {
905         super.onSaveInstanceState(b);
906         b.putParcelable(STATE_URI,
907                 mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri());
908         b.putBoolean(STATE_SLIDESHOW, mMode == MODE_SLIDESHOW);
909     }
910 
911     @Override
onStart()912     public void onStart() {
913         super.onStart();
914         mPaused = false;
915 
916         if (!init(mSavedUri)) {
917             Log.w(TAG, "init failed: " + mSavedUri);
918             finish();
919             return;
920         }
921 
922         // normally this will never be zero but if one "backs" into this
923         // activity after removing the sdcard it could be zero.  in that
924         // case just "finish" since there's nothing useful that can happen.
925         int count = mAllImages.getCount();
926         if (count == 0) {
927             finish();
928             return;
929         } else if (count <= mCurrentPosition) {
930             mCurrentPosition = count - 1;
931         }
932 
933         if (mGetter == null) {
934             makeGetter();
935         }
936 
937         if (mMode == MODE_SLIDESHOW) {
938             loadNextImage(mCurrentPosition, 0, true);
939         } else {  // MODE_NORMAL
940             setImage(mCurrentPosition, mShowControls);
941             mShowControls = false;
942         }
943     }
944 
945     @Override
onStop()946     public void onStop() {
947         super.onStop();
948         mPaused = true;
949 
950         // mGetter could be null if we call finish() and leave early in
951         // onStart().
952         if (mGetter != null) {
953             mGetter.cancelCurrent();
954             mGetter.stop();
955             mGetter = null;
956         }
957         setMode(MODE_NORMAL);
958 
959         // removing all callback in the message queue
960         mHandler.removeAllGetterCallbacks();
961 
962         if (mAllImages != null) {
963             mSavedUri = getCurrentUri();
964             mAllImages.close();
965             mAllImages = null;
966         }
967 
968         hideOnScreenControls();
969         mImageView.clear();
970         mCache.clear();
971 
972         for (ImageViewTouchBase iv : mSlideShowImageViews) {
973             iv.clear();
974         }
975     }
976 
startShareMediaActivity(IImage image)977     private void startShareMediaActivity(IImage image) {
978         boolean isVideo = image instanceof VideoObject;
979         Intent intent = new Intent();
980         intent.setAction(Intent.ACTION_SEND);
981         intent.setType(image.getMimeType());
982         intent.putExtra(Intent.EXTRA_STREAM, image.fullSizeImageUri());
983         try {
984             startActivity(Intent.createChooser(intent, getText(
985                     isVideo ? R.string.sendVideo : R.string.sendImage)));
986         } catch (android.content.ActivityNotFoundException ex) {
987             Toast.makeText(this, isVideo
988                     ? R.string.no_way_to_share_image
989                     : R.string.no_way_to_share_video,
990                     Toast.LENGTH_SHORT).show();
991         }
992     }
993 
startPlayVideoActivity()994     private void startPlayVideoActivity() {
995         IImage image = mAllImages.getImageAt(mCurrentPosition);
996         Intent intent = new Intent(
997                 Intent.ACTION_VIEW, image.fullSizeImageUri());
998         try {
999             startActivity(intent);
1000         } catch (android.content.ActivityNotFoundException ex) {
1001             Log.e(TAG, "Couldn't view video " + image.fullSizeImageUri(), ex);
1002         }
1003     }
1004 
onClick(View v)1005     public void onClick(View v) {
1006         switch (v.getId()) {
1007             case R.id.discard:
1008                 MenuHelper.deletePhoto(this, mDeletePhotoRunnable);
1009                 break;
1010             case R.id.play:
1011                 startPlayVideoActivity();
1012                 break;
1013             case R.id.share: {
1014                 IImage image = mAllImages.getImageAt(mCurrentPosition);
1015                 if (!MenuHelper.isWhiteListUri(image.fullSizeImageUri())) {
1016                     return;
1017                 }
1018                 startShareMediaActivity(image);
1019                 break;
1020             }
1021             case R.id.setas: {
1022                 IImage image = mAllImages.getImageAt(mCurrentPosition);
1023                 Intent intent = Util.createSetAsIntent(image);
1024                 try {
1025                     startActivity(Intent.createChooser(
1026                             intent, getText(R.string.setImage)));
1027                 } catch (android.content.ActivityNotFoundException ex) {
1028                     Toast.makeText(this, R.string.no_way_to_share_video,
1029                             Toast.LENGTH_SHORT).show();
1030                 }
1031                 break;
1032             }
1033             case R.id.next_image:
1034                 moveNextOrPrevious(1);
1035                 break;
1036             case R.id.prev_image:
1037                 moveNextOrPrevious(-1);
1038                 break;
1039         }
1040     }
1041 
moveNextOrPrevious(int delta)1042     private void moveNextOrPrevious(int delta) {
1043         int nextImagePos = mCurrentPosition + delta;
1044         if ((0 <= nextImagePos) && (nextImagePos < mAllImages.getCount())) {
1045             setImage(nextImagePos, true);
1046             showOnScreenControls();
1047         }
1048     }
1049 
1050     @Override
onActivityResult(int requestCode, int resultCode, Intent data)1051     protected void onActivityResult(int requestCode, int resultCode,
1052             Intent data) {
1053         switch (requestCode) {
1054             case MenuHelper.RESULT_COMMON_MENU_CROP:
1055                 if (resultCode == RESULT_OK) {
1056                     // The CropImage activity passes back the Uri of the
1057                     // cropped image as the Action rather than the Data.
1058                     mSavedUri = Uri.parse(data.getAction());
1059 
1060                     // if onStart() runs before, then set the returned
1061                     // image as currentImage.
1062                     if (mAllImages != null) {
1063                         IImage image = mAllImages.getImageForUri(mSavedUri);
1064                         // image could be null if SD card is removed.
1065                         if (image == null) {
1066                             finish();
1067                         } else {
1068                             mCurrentPosition = mAllImages.getImageIndex(image);
1069                             setImage(mCurrentPosition, false);
1070                         }
1071                     }
1072                 }
1073                 break;
1074         }
1075     }
1076 }
1077 
1078 class ImageViewTouch extends ImageViewTouchBase {
1079     private final ViewImage mViewImage;
1080     private boolean mEnableTrackballScroll;
1081 
1082     public ImageViewTouch(Context context) {
1083         super(context);
1084         mViewImage = (ViewImage) context;
1085     }
1086 
1087     public ImageViewTouch(Context context, AttributeSet attrs) {
1088         super(context, attrs);
1089         mViewImage = (ViewImage) context;
1090     }
1091 
1092     public void setEnableTrackballScroll(boolean enable) {
1093         mEnableTrackballScroll = enable;
1094     }
1095 
1096     protected void postTranslateCenter(float dx, float dy) {
1097         super.postTranslate(dx, dy);
1098         center(true, true);
1099     }
1100 
1101     private static final float PAN_RATE = 20;
1102 
1103     // This is the time we allow the dpad to change the image position again.
1104     private long mNextChangePositionTime;
1105 
1106     @Override
1107     public boolean onKeyDown(int keyCode, KeyEvent event) {
1108         if (mViewImage.mPaused) return false;
1109 
1110         // Don't respond to arrow keys if trackball scrolling is not enabled
1111         if (!mEnableTrackballScroll) {
1112             if ((keyCode >= KeyEvent.KEYCODE_DPAD_UP)
1113                     && (keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT)) {
1114                 return super.onKeyDown(keyCode, event);
1115             }
1116         }
1117 
1118         int current = mViewImage.mCurrentPosition;
1119 
1120         int nextImagePos = -2; // default no next image
1121         try {
1122             switch (keyCode) {
1123                 case KeyEvent.KEYCODE_DPAD_CENTER: {
1124                     if (mViewImage.isPickIntent()) {
1125                         IImage img = mViewImage.mAllImages
1126                                 .getImageAt(mViewImage.mCurrentPosition);
1127                         mViewImage.setResult(ViewImage.RESULT_OK,
1128                                  new Intent().setData(img.fullSizeImageUri()));
1129                         mViewImage.finish();
1130                     }
1131                     break;
1132                 }
1133                 case KeyEvent.KEYCODE_DPAD_LEFT: {
1134                     if (getScale() <= 1F && event.getEventTime()
1135                             >= mNextChangePositionTime) {
1136                         nextImagePos = current - 1;
1137                         mNextChangePositionTime = event.getEventTime() + 500;
1138                     } else {
1139                         panBy(PAN_RATE, 0);
1140                         center(true, false);
1141                     }
1142                     return true;
1143                 }
1144                 case KeyEvent.KEYCODE_DPAD_RIGHT: {
1145                     if (getScale() <= 1F && event.getEventTime()
1146                             >= mNextChangePositionTime) {
1147                         nextImagePos = current + 1;
1148                         mNextChangePositionTime = event.getEventTime() + 500;
1149                     } else {
1150                         panBy(-PAN_RATE, 0);
1151                         center(true, false);
1152                     }
1153                     return true;
1154                 }
1155                 case KeyEvent.KEYCODE_DPAD_UP: {
1156                     panBy(0, PAN_RATE);
1157                     center(false, true);
1158                     return true;
1159                 }
1160                 case KeyEvent.KEYCODE_DPAD_DOWN: {
1161                     panBy(0, -PAN_RATE);
1162                     center(false, true);
1163                     return true;
1164                 }
1165                 case KeyEvent.KEYCODE_DEL:
1166                     MenuHelper.deletePhoto(
1167                             mViewImage, mViewImage.mDeletePhotoRunnable);
1168                     break;
1169             }
1170         } finally {
1171             if (nextImagePos >= 0
1172                     && nextImagePos < mViewImage.mAllImages.getCount()) {
1173                 synchronized (mViewImage) {
1174                     mViewImage.setMode(ViewImage.MODE_NORMAL);
1175                     mViewImage.setImage(nextImagePos, true);
1176                 }
1177            } else if (nextImagePos != -2) {
1178                center(true, true);
1179            }
1180         }
1181 
1182         return super.onKeyDown(keyCode, event);
1183     }
1184 }
1185 
1186 // This is a cache for Bitmap displayed in ViewImage (normal mode, thumb only).
1187 class BitmapCache implements ImageViewTouchBase.Recycler {
1188     public static class Entry {
1189         int mPos;
1190         Bitmap mBitmap;
1191         public Entry() {
1192             clear();
1193         }
1194         public void clear() {
1195             mPos = -1;
1196             mBitmap = null;
1197         }
1198     }
1199 
1200     private final Entry[] mCache;
1201 
1202     public BitmapCache(int size) {
1203         mCache = new Entry[size];
1204         for (int i = 0; i < mCache.length; i++) {
1205             mCache[i] = new Entry();
1206         }
1207     }
1208 
1209     // Given the position, find the associated entry. Returns null if there is
1210     // no such entry.
1211     private Entry findEntry(int pos) {
1212         for (Entry e : mCache) {
1213             if (pos == e.mPos) {
1214                 return e;
1215             }
1216         }
1217         return null;
1218     }
1219 
1220     // Returns the thumb bitmap if we have it, otherwise return null.
1221     public synchronized Bitmap getBitmap(int pos) {
1222         Entry e = findEntry(pos);
1223         if (e != null) {
1224             return e.mBitmap;
1225         }
1226         return null;
1227     }
1228 
1229     public synchronized void put(int pos, Bitmap bitmap) {
1230         // First see if we already have this entry.
1231         if (findEntry(pos) != null) {
1232             return;
1233         }
1234 
1235         // Find the best entry we should replace.
1236         // See if there is any empty entry.
1237         // Otherwise assuming sequential access, kick out the entry with the
1238         // greatest distance.
1239         Entry best = null;
1240         int maxDist = -1;
1241         for (Entry e : mCache) {
1242             if (e.mPos == -1) {
1243                 best = e;
1244                 break;
1245             } else {
1246                 int dist = Math.abs(pos - e.mPos);
1247                 if (dist > maxDist) {
1248                     maxDist = dist;
1249                     best = e;
1250                 }
1251             }
1252         }
1253 
1254         // Recycle the image being kicked out.
1255         // This only works because our current usage is sequential, so we
1256         // do not happen to recycle the image being displayed.
1257         if (best.mBitmap != null) {
1258             best.mBitmap.recycle();
1259         }
1260 
1261         best.mPos = pos;
1262         best.mBitmap = bitmap;
1263     }
1264 
1265     // Recycle all bitmaps in the cache and clear the cache.
1266     public synchronized void clear() {
1267         for (Entry e : mCache) {
1268             if (e.mBitmap != null) {
1269                 e.mBitmap.recycle();
1270             }
1271             e.clear();
1272         }
1273     }
1274 
1275     // Returns whether the bitmap is in the cache.
1276     public synchronized boolean hasBitmap(int pos) {
1277         Entry e = findEntry(pos);
1278         return (e != null);
1279     }
1280 
1281     // Recycle the bitmap if it's not in the cache.
1282     // The input must be non-null.
1283     public synchronized void recycle(Bitmap b) {
1284         for (Entry e : mCache) {
1285             if (e.mPos != -1) {
1286                 if (e.mBitmap == b) {
1287                     return;
1288                 }
1289             }
1290         }
1291         b.recycle();
1292     }
1293 }
1294