Home | History | Annotate | Download | only in camera
      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 NoSearchActivity 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 
    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             if (mPaused) return false;
    333             ImageViewTouch imageView = mImageView;
    334             if (imageView.getScale() > 1F) {
    335                 imageView.postTranslateCenter(-distanceX, -distanceY);
    336             }
    337             return true;
    338         }
    339 
    340         @Override
    341         public boolean onSingleTapUp(MotionEvent e) {
    342             if (mPaused) return false;
    343             setMode(MODE_NORMAL);
    344             return true;
    345         }
    346 
    347         @Override
    348         public boolean onSingleTapConfirmed(MotionEvent e) {
    349             if (mPaused) return false;
    350             showOnScreenControls();
    351             scheduleDismissOnScreenControls();
    352             return true;
    353         }
    354 
    355         @Override
    356         public boolean onDoubleTap(MotionEvent e) {
    357             if (mPaused) return false;
    358             ImageViewTouch imageView = mImageView;
    359 
    360             // Switch between the original scale and 3x scale.
    361             if (imageView.getScale() > 2F) {
    362                 mImageView.zoomTo(1f);
    363             } else {
    364                 mImageView.zoomToPoint(3f, e.getX(), e.getY());
    365             }
    366             return true;
    367         }
    368     }
    369 
    370     boolean isPickIntent() {
    371         String action = getIntent().getAction();
    372         return (Intent.ACTION_PICK.equals(action)
    373                 || Intent.ACTION_GET_CONTENT.equals(action));
    374     }
    375 
    376     @Override
    377     public boolean onCreateOptionsMenu(Menu menu) {
    378         super.onCreateOptionsMenu(menu);
    379 
    380         MenuItem item = menu.add(Menu.NONE, Menu.NONE,
    381                 MenuHelper.POSITION_SLIDESHOW,
    382                 R.string.slide_show);
    383         item.setOnMenuItemClickListener(
    384                 new MenuItem.OnMenuItemClickListener() {
    385             public boolean onMenuItemClick(MenuItem item) {
    386                 setMode(MODE_SLIDESHOW);
    387                 mLastSlideShowImage = mCurrentPosition;
    388                 loadNextImage(mCurrentPosition, 0, true);
    389                 return true;
    390             }
    391         });
    392         item.setIcon(android.R.drawable.ic_menu_slideshow);
    393 
    394         mImageMenuRunnable = MenuHelper.addImageMenuItems(
    395                 menu,
    396                 MenuHelper.INCLUDE_ALL,
    397                 ViewImage.this,
    398                 mHandler,
    399                 mDeletePhotoRunnable,
    400                 new MenuHelper.MenuInvoker() {
    401                     public void run(final MenuHelper.MenuCallback cb) {
    402                         if (mPaused) return;
    403                         setMode(MODE_NORMAL);
    404 
    405                         IImage image = mAllImages.getImageAt(mCurrentPosition);
    406                         Uri uri = image.fullSizeImageUri();
    407                         cb.run(uri, image);
    408 
    409                         // We might have deleted all images in the callback, so
    410                         // call setImage() only if we still have some images.
    411                         if (mAllImages.getCount() > 0) {
    412                             mImageView.clear();
    413                             setImage(mCurrentPosition, false);
    414                         }
    415                     }
    416                 });
    417 
    418         item = menu.add(Menu.NONE, Menu.NONE,
    419                 MenuHelper.POSITION_GALLERY_SETTING, R.string.camerasettings);
    420         item.setOnMenuItemClickListener(
    421                 new MenuItem.OnMenuItemClickListener() {
    422             public boolean onMenuItemClick(MenuItem item) {
    423                 Intent preferences = new Intent();
    424                 preferences.setClass(ViewImage.this, GallerySettings.class);
    425                 startActivity(preferences);
    426                 return true;
    427             }
    428         });
    429         item.setAlphabeticShortcut('p');
    430         item.setIcon(android.R.drawable.ic_menu_preferences);
    431 
    432         return true;
    433     }
    434 
    435     protected Runnable mDeletePhotoRunnable = new Runnable() {
    436         public void run() {
    437             mAllImages.removeImageAt(mCurrentPosition);
    438             if (mAllImages.getCount() == 0) {
    439                 finish();
    440                 return;
    441             } else {
    442                 if (mCurrentPosition == mAllImages.getCount()) {
    443                     mCurrentPosition -= 1;
    444                 }
    445             }
    446             mImageView.clear();
    447             mCache.clear();  // Because the position number is changed.
    448             setImage(mCurrentPosition, true);
    449         }
    450     };
    451 
    452     @Override
    453     public boolean onPrepareOptionsMenu(Menu menu) {
    454 
    455         super.onPrepareOptionsMenu(menu);
    456         if (mPaused) return false;
    457 
    458         setMode(MODE_NORMAL);
    459         IImage image = mAllImages.getImageAt(mCurrentPosition);
    460 
    461         if (mImageMenuRunnable != null) {
    462             mImageMenuRunnable.gettingReadyToOpen(menu, image);
    463         }
    464 
    465         Uri uri = mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri();
    466         MenuHelper.enableShareMenuItem(menu, MenuHelper.isWhiteListUri(uri));
    467 
    468         MenuHelper.enableShowOnMapMenuItem(menu, MenuHelper.hasLatLngData(image));
    469 
    470         return true;
    471     }
    472 
    473     @Override
    474     public boolean onMenuItemSelected(int featureId, MenuItem item) {
    475         boolean b = super.onMenuItemSelected(featureId, item);
    476         if (mImageMenuRunnable != null) {
    477             mImageMenuRunnable.aboutToCall(item,
    478                     mAllImages.getImageAt(mCurrentPosition));
    479         }
    480         return b;
    481     }
    482 
    483     void setImage(int pos, boolean showControls) {
    484         mCurrentPosition = pos;
    485 
    486         Bitmap b = mCache.getBitmap(pos);
    487         if (b != null) {
    488             IImage image = mAllImages.getImageAt(pos);
    489             mImageView.setImageRotateBitmapResetBase(
    490                     new RotateBitmap(b, image.getDegreesRotated()), true);
    491             updateZoomButtonsEnabled();
    492         }
    493 
    494         ImageGetterCallback cb = new ImageGetterCallback() {
    495             public void completed() {
    496             }
    497 
    498             public boolean wantsThumbnail(int pos, int offset) {
    499                 return !mCache.hasBitmap(pos + offset);
    500             }
    501 
    502             public boolean wantsFullImage(int pos, int offset) {
    503                 return offset == 0;
    504             }
    505 
    506             public int fullImageSizeToUse(int pos, int offset) {
    507                 // this number should be bigger so that we can zoom.  we may
    508                 // need to get fancier and read in the fuller size image as the
    509                 // user starts to zoom.
    510                 // Originally the value is set to 480 in order to avoid OOM.
    511                 // Now we set it to 2048 because of using
    512                 // native memory allocation for Bitmaps.
    513                 final int imageViewSize = 2048;
    514                 return imageViewSize;
    515             }
    516 
    517             public int [] loadOrder() {
    518                 return sOrderAdjacents;
    519             }
    520 
    521             public void imageLoaded(int pos, int offset, RotateBitmap bitmap,
    522                                     boolean isThumb) {
    523                 // shouldn't get here after onPause()
    524 
    525                 // We may get a result from a previous request. Ignore it.
    526                 if (pos != mCurrentPosition) {
    527                     bitmap.recycle();
    528                     return;
    529                 }
    530 
    531                 if (isThumb) {
    532                     mCache.put(pos + offset, bitmap.getBitmap());
    533                 }
    534                 if (offset == 0) {
    535                     // isThumb: We always load thumb bitmap first, so we will
    536                     // reset the supp matrix for then thumb bitmap, and keep
    537                     // the supp matrix when the full bitmap is loaded.
    538                     mImageView.setImageRotateBitmapResetBase(bitmap, isThumb);
    539                     updateZoomButtonsEnabled();
    540                 }
    541             }
    542         };
    543 
    544         // Could be null if we're stopping a slide show in the course of pausing
    545         if (mGetter != null) {
    546             mGetter.setPosition(pos, cb, mAllImages, mHandler);
    547         }
    548         updateActionIcons();
    549         if (showControls) showOnScreenControls();
    550         scheduleDismissOnScreenControls();
    551     }
    552 
    553     @Override
    554     public void onCreate(Bundle instanceState) {
    555         super.onCreate(instanceState);
    556 
    557         Intent intent = getIntent();
    558         mFullScreenInNormalMode = intent.getBooleanExtra(
    559                 MediaStore.EXTRA_FULL_SCREEN, true);
    560         mShowActionIcons = intent.getBooleanExtra(
    561                 MediaStore.EXTRA_SHOW_ACTION_ICONS, true);
    562 
    563         mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
    564 
    565         setDefaultKeyMode(DEFAULT_KEYS_SHORTCUT);
    566         requestWindowFeature(Window.FEATURE_NO_TITLE);
    567         setContentView(R.layout.viewimage);
    568 
    569         mImageView = (ImageViewTouch) findViewById(R.id.image);
    570         mImageView.setEnableTrackballScroll(true);
    571         mCache = new BitmapCache(3);
    572         mImageView.setRecycler(mCache);
    573 
    574         makeGetter();
    575 
    576         mAnimationIndex = -1;
    577 
    578         mSlideShowInAnimation = new Animation[] {
    579             makeInAnimation(R.anim.transition_in),
    580             makeInAnimation(R.anim.slide_in),
    581             makeInAnimation(R.anim.slide_in_vertical),
    582         };
    583 
    584         mSlideShowOutAnimation = new Animation[] {
    585             makeOutAnimation(R.anim.transition_out),
    586             makeOutAnimation(R.anim.slide_out),
    587             makeOutAnimation(R.anim.slide_out_vertical),
    588         };
    589 
    590         mSlideShowImageViews[0] =
    591                 (ImageViewTouchBase) findViewById(R.id.image1_slideShow);
    592         mSlideShowImageViews[1] =
    593                 (ImageViewTouchBase) findViewById(R.id.image2_slideShow);
    594         for (ImageViewTouchBase v : mSlideShowImageViews) {
    595             v.setVisibility(View.INVISIBLE);
    596             v.setRecycler(mCache);
    597         }
    598 
    599         mActionIconPanel = findViewById(R.id.action_icon_panel);
    600 
    601         mParam = getIntent().getParcelableExtra(KEY_IMAGE_LIST);
    602 
    603         boolean slideshow;
    604         if (instanceState != null) {
    605             mSavedUri = instanceState.getParcelable(STATE_URI);
    606             slideshow = instanceState.getBoolean(STATE_SLIDESHOW, false);
    607             mShowControls = instanceState.getBoolean(STATE_SHOW_CONTROLS, true);
    608         } else {
    609             mSavedUri = getIntent().getData();
    610             slideshow = intent.getBooleanExtra(EXTRA_SLIDESHOW, false);
    611         }
    612 
    613         // We only show action icons for URIs that we know we can share and
    614         // delete. Although we get read permission (for the images) from
    615         // applications like MMS, we cannot pass the permission to other
    616         // activities due to the current framework design.
    617         if (!MenuHelper.isWhiteListUri(mSavedUri)) {
    618             mShowActionIcons = false;
    619         }
    620 
    621         if (mShowActionIcons) {
    622             int[] pickIds = {R.id.attach, R.id.cancel};
    623             int[] normalIds = {R.id.setas, R.id.play, R.id.share, R.id.discard};
    624             int[] connectIds = isPickIntent() ? pickIds : normalIds;
    625             for (int id : connectIds) {
    626                 View view = mActionIconPanel.findViewById(id);
    627                 view.setVisibility(View.VISIBLE);
    628                 view.setOnClickListener(this);
    629             }
    630         }
    631 
    632         // Don't show the "delete" icon for SingleImageList.
    633         if (ImageManager.isSingleImageMode(mSavedUri.toString())) {
    634             mActionIconPanel.findViewById(R.id.discard)
    635                     .setVisibility(View.GONE);
    636         }
    637 
    638         if (slideshow) {
    639             setMode(MODE_SLIDESHOW);
    640         } else {
    641             if (mFullScreenInNormalMode) {
    642                 getWindow().addFlags(
    643                         WindowManager.LayoutParams.FLAG_FULLSCREEN);
    644             }
    645             if (mShowActionIcons) {
    646                 mActionIconPanel.setVisibility(View.VISIBLE);
    647             }
    648         }
    649 
    650         setupOnScreenControls(findViewById(R.id.rootLayout), mImageView);
    651     }
    652 
    653     private void updateActionIcons() {
    654         if (isPickIntent()) return;
    655 
    656         IImage image = mAllImages.getImageAt(mCurrentPosition);
    657         View panel = mActionIconPanel;
    658         if (image instanceof VideoObject) {
    659             panel.findViewById(R.id.setas).setVisibility(View.GONE);
    660             panel.findViewById(R.id.play).setVisibility(View.VISIBLE);
    661         } else {
    662             panel.findViewById(R.id.setas).setVisibility(View.VISIBLE);
    663             panel.findViewById(R.id.play).setVisibility(View.GONE);
    664         }
    665     }
    666 
    667     private Animation makeInAnimation(int id) {
    668         Animation inAnimation = AnimationUtils.loadAnimation(this, id);
    669         return inAnimation;
    670     }
    671 
    672     private Animation makeOutAnimation(int id) {
    673         Animation outAnimation = AnimationUtils.loadAnimation(this, id);
    674         return outAnimation;
    675     }
    676 
    677     private static int getPreferencesInteger(
    678             SharedPreferences prefs, String key, int defaultValue) {
    679         String value = prefs.getString(key, null);
    680         try {
    681             return value == null ? defaultValue : Integer.parseInt(value);
    682         } catch (NumberFormatException ex) {
    683             Log.e(TAG, "couldn't parse preference: " + value, ex);
    684             return defaultValue;
    685         }
    686     }
    687 
    688     void setMode(int mode) {
    689         if (mMode == mode) {
    690             return;
    691         }
    692         View slideshowPanel = findViewById(R.id.slideShowContainer);
    693         View normalPanel = findViewById(R.id.abs);
    694 
    695         Window win = getWindow();
    696         mMode = mode;
    697         if (mode == MODE_SLIDESHOW) {
    698             slideshowPanel.setVisibility(View.VISIBLE);
    699             normalPanel.setVisibility(View.GONE);
    700 
    701             win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN
    702                     | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    703 
    704             mImageView.clear();
    705             mActionIconPanel.setVisibility(View.GONE);
    706 
    707             slideshowPanel.getRootView().requestLayout();
    708 
    709             // The preferences we want to read:
    710             //   mUseShuffleOrder
    711             //   mSlideShowLoop
    712             //   mAnimationIndex
    713             //   mSlideShowInterval
    714 
    715             mUseShuffleOrder = mPrefs.getBoolean(PREF_SHUFFLE_SLIDESHOW, false);
    716             mSlideShowLoop = mPrefs.getBoolean(PREF_SLIDESHOW_REPEAT, false);
    717             mAnimationIndex = getPreferencesInteger(
    718                     mPrefs, "pref_gallery_slideshow_transition_key", 0);
    719             mSlideShowInterval = getPreferencesInteger(
    720                     mPrefs, "pref_gallery_slideshow_interval_key", 3) * 1000;
    721         } else {
    722             slideshowPanel.setVisibility(View.GONE);
    723             normalPanel.setVisibility(View.VISIBLE);
    724 
    725             win.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    726             if (mFullScreenInNormalMode) {
    727                 win.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    728             } else {
    729                 win.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
    730             }
    731 
    732             if (mGetter != null) {
    733                 mGetter.cancelCurrent();
    734             }
    735 
    736             if (mShowActionIcons) {
    737                 Animation animation = new AlphaAnimation(0F, 1F);
    738                 animation.setDuration(500);
    739                 mActionIconPanel.setAnimation(animation);
    740                 mActionIconPanel.setVisibility(View.VISIBLE);
    741             }
    742 
    743             ImageViewTouchBase dst = mImageView;
    744             for (ImageViewTouchBase ivt : mSlideShowImageViews) {
    745                 ivt.clear();
    746             }
    747 
    748             mShuffleOrder = null;
    749 
    750             // mGetter null is a proxy for being paused
    751             if (mGetter != null) {
    752                 setImage(mCurrentPosition, true);
    753             }
    754         }
    755     }
    756 
    757     private void generateShuffleOrder() {
    758         if (mShuffleOrder == null
    759                 || mShuffleOrder.length != mAllImages.getCount()) {
    760             mShuffleOrder = new int[mAllImages.getCount()];
    761             for (int i = 0, n = mShuffleOrder.length; i < n; i++) {
    762                 mShuffleOrder[i] = i;
    763             }
    764         }
    765 
    766         for (int i = mShuffleOrder.length - 1; i >= 0; i--) {
    767             int r = mRandom.nextInt(i + 1);
    768             if (r != i) {
    769                 int tmp = mShuffleOrder[r];
    770                 mShuffleOrder[r] = mShuffleOrder[i];
    771                 mShuffleOrder[i] = tmp;
    772             }
    773         }
    774     }
    775 
    776     private void loadNextImage(final int requestedPos, final long delay,
    777                                final boolean firstCall) {
    778         if (firstCall && mUseShuffleOrder) {
    779             generateShuffleOrder();
    780         }
    781 
    782         final long targetDisplayTime = System.currentTimeMillis() + delay;
    783 
    784         ImageGetterCallback cb = new ImageGetterCallback() {
    785             public void completed() {
    786             }
    787 
    788             public boolean wantsThumbnail(int pos, int offset) {
    789                 return true;
    790             }
    791 
    792             public boolean wantsFullImage(int pos, int offset) {
    793                 return false;
    794             }
    795 
    796             public int [] loadOrder() {
    797                 return sOrderSlideshow;
    798             }
    799 
    800             public int fullImageSizeToUse(int pos, int offset) {
    801                 return 480; // TODO compute this
    802             }
    803 
    804             public void imageLoaded(final int pos, final int offset,
    805                     final RotateBitmap bitmap, final boolean isThumb) {
    806                 long timeRemaining = Math.max(0,
    807                         targetDisplayTime - System.currentTimeMillis());
    808                 mHandler.postDelayedGetterCallback(new Runnable() {
    809                     public void run() {
    810                         if (mMode == MODE_NORMAL) {
    811                             return;
    812                         }
    813 
    814                         ImageViewTouchBase oldView =
    815                                 mSlideShowImageViews[mSlideShowImageCurrent];
    816 
    817                         if (++mSlideShowImageCurrent
    818                                 == mSlideShowImageViews.length) {
    819                             mSlideShowImageCurrent = 0;
    820                         }
    821 
    822                         ImageViewTouchBase newView =
    823                                 mSlideShowImageViews[mSlideShowImageCurrent];
    824                         newView.setVisibility(View.VISIBLE);
    825                         newView.setImageRotateBitmapResetBase(bitmap, true);
    826                         newView.bringToFront();
    827 
    828                         int animation = 0;
    829 
    830                         if (mAnimationIndex == -1) {
    831                             int n = mRandom.nextInt(
    832                                     mSlideShowInAnimation.length);
    833                             animation = n;
    834                         } else {
    835                             animation = mAnimationIndex;
    836                         }
    837 
    838                         Animation aIn = mSlideShowInAnimation[animation];
    839                         newView.startAnimation(aIn);
    840                         newView.setVisibility(View.VISIBLE);
    841 
    842                         Animation aOut = mSlideShowOutAnimation[animation];
    843                         oldView.setVisibility(View.INVISIBLE);
    844                         oldView.startAnimation(aOut);
    845 
    846                         mCurrentPosition = requestedPos;
    847 
    848                         if (mCurrentPosition == mLastSlideShowImage
    849                                 && !firstCall) {
    850                             if (mSlideShowLoop) {
    851                                 if (mUseShuffleOrder) {
    852                                     generateShuffleOrder();
    853                                 }
    854                             } else {
    855                                 setMode(MODE_NORMAL);
    856                                 return;
    857                             }
    858                         }
    859 
    860                         loadNextImage(
    861                                 (mCurrentPosition + 1) % mAllImages.getCount(),
    862                                 mSlideShowInterval, false);
    863                     }
    864                 }, timeRemaining);
    865             }
    866         };
    867         // Could be null if we're stopping a slide show in the course of pausing
    868         if (mGetter != null) {
    869             int pos = requestedPos;
    870             if (mShuffleOrder != null) {
    871                 pos = mShuffleOrder[pos];
    872             }
    873             mGetter.setPosition(pos, cb, mAllImages, mHandler);
    874         }
    875     }
    876 
    877     private void makeGetter() {
    878         mGetter = new ImageGetter(getContentResolver());
    879     }
    880 
    881     private IImageList buildImageListFromUri(Uri uri) {
    882         String sortOrder = mPrefs.getString(
    883                 "pref_gallery_sort_key", "descending");
    884         int sort = sortOrder.equals("ascending")
    885                 ? ImageManager.SORT_ASCENDING
    886                 : ImageManager.SORT_DESCENDING;
    887         return ImageManager.makeImageList(getContentResolver(), uri, sort);
    888     }
    889 
    890     private boolean init(Uri uri) {
    891         if (uri == null) return false;
    892         mAllImages = (mParam == null)
    893                 ? buildImageListFromUri(uri)
    894                 : ImageManager.makeImageList(getContentResolver(), mParam);
    895         IImage image = mAllImages.getImageForUri(uri);
    896         if (image == null) return false;
    897         mCurrentPosition = mAllImages.getImageIndex(image);
    898         mLastSlideShowImage = mCurrentPosition;
    899         return true;
    900     }
    901 
    902     private Uri getCurrentUri() {
    903         if (mAllImages.getCount() == 0) return null;
    904         IImage image = mAllImages.getImageAt(mCurrentPosition);
    905         if (image == null) return null;
    906         return image.fullSizeImageUri();
    907     }
    908 
    909     @Override
    910     public void onSaveInstanceState(Bundle b) {
    911         super.onSaveInstanceState(b);
    912         b.putParcelable(STATE_URI,
    913                 mAllImages.getImageAt(mCurrentPosition).fullSizeImageUri());
    914         b.putBoolean(STATE_SLIDESHOW, mMode == MODE_SLIDESHOW);
    915     }
    916 
    917     @Override
    918     public void onStart() {
    919         super.onStart();
    920         mPaused = false;
    921 
    922         if (!init(mSavedUri)) {
    923             Log.w(TAG, "init failed: " + mSavedUri);
    924             finish();
    925             return;
    926         }
    927 
    928         // normally this will never be zero but if one "backs" into this
    929         // activity after removing the sdcard it could be zero.  in that
    930         // case just "finish" since there's nothing useful that can happen.
    931         int count = mAllImages.getCount();
    932         if (count == 0) {
    933             finish();
    934             return;
    935         } else if (count <= mCurrentPosition) {
    936             mCurrentPosition = count - 1;
    937         }
    938 
    939         if (mGetter == null) {
    940             makeGetter();
    941         }
    942 
    943         if (mMode == MODE_SLIDESHOW) {
    944             loadNextImage(mCurrentPosition, 0, true);
    945         } else {  // MODE_NORMAL
    946             setImage(mCurrentPosition, mShowControls);
    947             mShowControls = false;
    948         }
    949     }
    950 
    951     @Override
    952     public void onStop() {
    953         super.onStop();
    954         mPaused = true;
    955 
    956         // mGetter could be null if we call finish() and leave early in
    957         // onStart().
    958         if (mGetter != null) {
    959             mGetter.cancelCurrent();
    960             mGetter.stop();
    961             mGetter = null;
    962         }
    963         setMode(MODE_NORMAL);
    964 
    965         // removing all callback in the message queue
    966         mHandler.removeAllGetterCallbacks();
    967 
    968         if (mAllImages != null) {
    969             mSavedUri = getCurrentUri();
    970             mAllImages.close();
    971             mAllImages = null;
    972         }
    973 
    974         hideOnScreenControls();
    975         mImageView.clear();
    976         mCache.clear();
    977 
    978         for (ImageViewTouchBase iv : mSlideShowImageViews) {
    979             iv.clear();
    980         }
    981     }
    982 
    983     private void startShareMediaActivity(IImage image) {
    984         boolean isVideo = image instanceof VideoObject;
    985         Intent intent = new Intent();
    986         intent.setAction(Intent.ACTION_SEND);
    987         intent.setType(image.getMimeType());
    988         intent.putExtra(Intent.EXTRA_STREAM, image.fullSizeImageUri());
    989         try {
    990             startActivity(Intent.createChooser(intent, getText(
    991                     isVideo ? R.string.sendVideo : R.string.sendImage)));
    992         } catch (android.content.ActivityNotFoundException ex) {
    993             Toast.makeText(this, isVideo
    994                     ? R.string.no_way_to_share_image
    995                     : R.string.no_way_to_share_video,
    996                     Toast.LENGTH_SHORT).show();
    997         }
    998     }
    999 
   1000     private void startPlayVideoActivity() {
   1001         IImage image = mAllImages.getImageAt(mCurrentPosition);
   1002         Intent intent = new Intent(
   1003                 Intent.ACTION_VIEW, image.fullSizeImageUri());
   1004         try {
   1005             startActivity(intent);
   1006         } catch (android.content.ActivityNotFoundException ex) {
   1007             Log.e(TAG, "Couldn't view video " + image.fullSizeImageUri(), ex);
   1008         }
   1009     }
   1010 
   1011     public void onClick(View v) {
   1012         switch (v.getId()) {
   1013             case R.id.discard:
   1014                 MenuHelper.deletePhoto(this, mDeletePhotoRunnable);
   1015                 break;
   1016             case R.id.play:
   1017                 startPlayVideoActivity();
   1018                 break;
   1019             case R.id.share: {
   1020                 IImage image = mAllImages.getImageAt(mCurrentPosition);
   1021                 if (!MenuHelper.isWhiteListUri(image.fullSizeImageUri())) {
   1022                     return;
   1023                 }
   1024                 startShareMediaActivity(image);
   1025                 break;
   1026             }
   1027             case R.id.setas: {
   1028                 IImage image = mAllImages.getImageAt(mCurrentPosition);
   1029                 Intent intent = Util.createSetAsIntent(image);
   1030                 try {
   1031                     startActivity(Intent.createChooser(
   1032                             intent, getText(R.string.setImage)));
   1033                 } catch (android.content.ActivityNotFoundException ex) {
   1034                     Toast.makeText(this, R.string.no_way_to_share_video,
   1035                             Toast.LENGTH_SHORT).show();
   1036                 }
   1037                 break;
   1038             }
   1039             case R.id.next_image:
   1040                 moveNextOrPrevious(1);
   1041                 break;
   1042             case R.id.prev_image:
   1043                 moveNextOrPrevious(-1);
   1044                 break;
   1045         }
   1046     }
   1047 
   1048     private void moveNextOrPrevious(int delta) {
   1049         int nextImagePos = mCurrentPosition + delta;
   1050         if ((0 <= nextImagePos) && (nextImagePos < mAllImages.getCount())) {
   1051             setImage(nextImagePos, true);
   1052             showOnScreenControls();
   1053         }
   1054     }
   1055 
   1056     @Override
   1057     protected void onActivityResult(int requestCode, int resultCode,
   1058             Intent data) {
   1059         switch (requestCode) {
   1060             case MenuHelper.RESULT_COMMON_MENU_CROP:
   1061                 if (resultCode == RESULT_OK) {
   1062                     // The CropImage activity passes back the Uri of the
   1063                     // cropped image as the Action rather than the Data.
   1064                     mSavedUri = Uri.parse(data.getAction());
   1065 
   1066                     // if onStart() runs before, then set the returned
   1067                     // image as currentImage.
   1068                     if (mAllImages != null) {
   1069                         IImage image = mAllImages.getImageForUri(mSavedUri);
   1070                         // image could be null if SD card is removed.
   1071                         if (image == null) {
   1072                             finish();
   1073                         } else {
   1074                             mCurrentPosition = mAllImages.getImageIndex(image);
   1075                             setImage(mCurrentPosition, false);
   1076                         }
   1077                     }
   1078                 }
   1079                 break;
   1080         }
   1081     }
   1082 }
   1083 
   1084 class ImageViewTouch extends ImageViewTouchBase {
   1085     private final ViewImage mViewImage;
   1086     private boolean mEnableTrackballScroll;
   1087 
   1088     public ImageViewTouch(Context context) {
   1089         super(context);
   1090         mViewImage = (ViewImage) context;
   1091     }
   1092 
   1093     public ImageViewTouch(Context context, AttributeSet attrs) {
   1094         super(context, attrs);
   1095         mViewImage = (ViewImage) context;
   1096     }
   1097 
   1098     public void setEnableTrackballScroll(boolean enable) {
   1099         mEnableTrackballScroll = enable;
   1100     }
   1101 
   1102     protected void postTranslateCenter(float dx, float dy) {
   1103         super.postTranslate(dx, dy);
   1104         center(true, true);
   1105     }
   1106 
   1107     private static final float PAN_RATE = 20;
   1108 
   1109     // This is the time we allow the dpad to change the image position again.
   1110     private long mNextChangePositionTime;
   1111 
   1112     @Override
   1113     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1114         if (mViewImage.mPaused) return false;
   1115 
   1116         // Don't respond to arrow keys if trackball scrolling is not enabled
   1117         if (!mEnableTrackballScroll) {
   1118             if ((keyCode >= KeyEvent.KEYCODE_DPAD_UP)
   1119                     && (keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT)) {
   1120                 return super.onKeyDown(keyCode, event);
   1121             }
   1122         }
   1123 
   1124         int current = mViewImage.mCurrentPosition;
   1125 
   1126         int nextImagePos = -2; // default no next image
   1127         try {
   1128             switch (keyCode) {
   1129                 case KeyEvent.KEYCODE_DPAD_CENTER: {
   1130                     if (mViewImage.isPickIntent()) {
   1131                         IImage img = mViewImage.mAllImages
   1132                                 .getImageAt(mViewImage.mCurrentPosition);
   1133                         mViewImage.setResult(ViewImage.RESULT_OK,
   1134                                  new Intent().setData(img.fullSizeImageUri()));
   1135                         mViewImage.finish();
   1136                     }
   1137                     break;
   1138                 }
   1139                 case KeyEvent.KEYCODE_DPAD_LEFT: {
   1140                     if (getScale() <= 1F && event.getEventTime()
   1141                             >= mNextChangePositionTime) {
   1142                         nextImagePos = current - 1;
   1143                         mNextChangePositionTime = event.getEventTime() + 500;
   1144                     } else {
   1145                         panBy(PAN_RATE, 0);
   1146                         center(true, false);
   1147                     }
   1148                     return true;
   1149                 }
   1150                 case KeyEvent.KEYCODE_DPAD_RIGHT: {
   1151                     if (getScale() <= 1F && event.getEventTime()
   1152                             >= mNextChangePositionTime) {
   1153                         nextImagePos = current + 1;
   1154                         mNextChangePositionTime = event.getEventTime() + 500;
   1155                     } else {
   1156                         panBy(-PAN_RATE, 0);
   1157                         center(true, false);
   1158                     }
   1159                     return true;
   1160                 }
   1161                 case KeyEvent.KEYCODE_DPAD_UP: {
   1162                     panBy(0, PAN_RATE);
   1163                     center(false, true);
   1164                     return true;
   1165                 }
   1166                 case KeyEvent.KEYCODE_DPAD_DOWN: {
   1167                     panBy(0, -PAN_RATE);
   1168                     center(false, true);
   1169                     return true;
   1170                 }
   1171                 case KeyEvent.KEYCODE_DEL:
   1172                     MenuHelper.deletePhoto(
   1173                             mViewImage, mViewImage.mDeletePhotoRunnable);
   1174                     break;
   1175             }
   1176         } finally {
   1177             if (nextImagePos >= 0
   1178                     && nextImagePos < mViewImage.mAllImages.getCount()) {
   1179                 synchronized (mViewImage) {
   1180                     mViewImage.setMode(ViewImage.MODE_NORMAL);
   1181                     mViewImage.setImage(nextImagePos, true);
   1182                 }
   1183            } else if (nextImagePos != -2) {
   1184                center(true, true);
   1185            }
   1186         }
   1187 
   1188         return super.onKeyDown(keyCode, event);
   1189     }
   1190 }
   1191 
   1192 // This is a cache for Bitmap displayed in ViewImage (normal mode, thumb only).
   1193 class BitmapCache implements ImageViewTouchBase.Recycler {
   1194     public static class Entry {
   1195         int mPos;
   1196         Bitmap mBitmap;
   1197         public Entry() {
   1198             clear();
   1199         }
   1200         public void clear() {
   1201             mPos = -1;
   1202             mBitmap = null;
   1203         }
   1204     }
   1205 
   1206     private final Entry[] mCache;
   1207 
   1208     public BitmapCache(int size) {
   1209         mCache = new Entry[size];
   1210         for (int i = 0; i < mCache.length; i++) {
   1211             mCache[i] = new Entry();
   1212         }
   1213     }
   1214 
   1215     // Given the position, find the associated entry. Returns null if there is
   1216     // no such entry.
   1217     private Entry findEntry(int pos) {
   1218         for (Entry e : mCache) {
   1219             if (pos == e.mPos) {
   1220                 return e;
   1221             }
   1222         }
   1223         return null;
   1224     }
   1225 
   1226     // Returns the thumb bitmap if we have it, otherwise return null.
   1227     public synchronized Bitmap getBitmap(int pos) {
   1228         Entry e = findEntry(pos);
   1229         if (e != null) {
   1230             return e.mBitmap;
   1231         }
   1232         return null;
   1233     }
   1234 
   1235     public synchronized void put(int pos, Bitmap bitmap) {
   1236         // First see if we already have this entry.
   1237         if (findEntry(pos) != null) {
   1238             return;
   1239         }
   1240 
   1241         // Find the best entry we should replace.
   1242         // See if there is any empty entry.
   1243         // Otherwise assuming sequential access, kick out the entry with the
   1244         // greatest distance.
   1245         Entry best = null;
   1246         int maxDist = -1;
   1247         for (Entry e : mCache) {
   1248             if (e.mPos == -1) {
   1249                 best = e;
   1250                 break;
   1251             } else {
   1252                 int dist = Math.abs(pos - e.mPos);
   1253                 if (dist > maxDist) {
   1254                     maxDist = dist;
   1255                     best = e;
   1256                 }
   1257             }
   1258         }
   1259 
   1260         // Recycle the image being kicked out.
   1261         // This only works because our current usage is sequential, so we
   1262         // do not happen to recycle the image being displayed.
   1263         if (best.mBitmap != null) {
   1264             best.mBitmap.recycle();
   1265         }
   1266 
   1267         best.mPos = pos;
   1268         best.mBitmap = bitmap;
   1269     }
   1270 
   1271     // Recycle all bitmaps in the cache and clear the cache.
   1272     public synchronized void clear() {
   1273         for (Entry e : mCache) {
   1274             if (e.mBitmap != null) {
   1275                 e.mBitmap.recycle();
   1276             }
   1277             e.clear();
   1278         }
   1279     }
   1280 
   1281     // Returns whether the bitmap is in the cache.
   1282     public synchronized boolean hasBitmap(int pos) {
   1283         Entry e = findEntry(pos);
   1284         return (e != null);
   1285     }
   1286 
   1287     // Recycle the bitmap if it's not in the cache.
   1288     // The input must be non-null.
   1289     public synchronized void recycle(Bitmap b) {
   1290         for (Entry e : mCache) {
   1291             if (e.mPos != -1) {
   1292                 if (e.mBitmap == b) {
   1293                     return;
   1294                 }
   1295             }
   1296         }
   1297         b.recycle();
   1298     }
   1299 }
   1300