Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2010 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.gallery3d.app;
     18 
     19 import android.app.Activity;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.graphics.Rect;
     23 import android.net.Uri;
     24 import android.os.Bundle;
     25 import android.os.Handler;
     26 import android.os.Message;
     27 import android.os.Vibrator;
     28 import android.provider.MediaStore;
     29 import android.view.Menu;
     30 import android.view.MenuInflater;
     31 import android.view.MenuItem;
     32 import android.widget.Toast;
     33 
     34 import com.android.gallery3d.R;
     35 import com.android.gallery3d.common.Utils;
     36 import com.android.gallery3d.data.DataManager;
     37 import com.android.gallery3d.data.MediaDetails;
     38 import com.android.gallery3d.data.MediaItem;
     39 import com.android.gallery3d.data.MediaObject;
     40 import com.android.gallery3d.data.MediaSet;
     41 import com.android.gallery3d.data.MtpDevice;
     42 import com.android.gallery3d.data.Path;
     43 import com.android.gallery3d.ui.ActionModeHandler;
     44 import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
     45 import com.android.gallery3d.ui.AlbumSlotRenderer;
     46 import com.android.gallery3d.ui.DetailsHelper;
     47 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
     48 import com.android.gallery3d.ui.FadeTexture;
     49 import com.android.gallery3d.ui.GLCanvas;
     50 import com.android.gallery3d.ui.GLRoot;
     51 import com.android.gallery3d.ui.GLView;
     52 import com.android.gallery3d.ui.PhotoFallbackEffect;
     53 import com.android.gallery3d.ui.RelativePosition;
     54 import com.android.gallery3d.ui.SelectionManager;
     55 import com.android.gallery3d.ui.SlotView;
     56 import com.android.gallery3d.ui.SynchronizedHandler;
     57 import com.android.gallery3d.util.Future;
     58 import com.android.gallery3d.util.GalleryUtils;
     59 import com.android.gallery3d.util.MediaSetUtils;
     60 
     61 public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,
     62         SelectionManager.SelectionListener, MediaSet.SyncListener, GalleryActionBar.OnAlbumModeSelectedListener {
     63     @SuppressWarnings("unused")
     64     private static final String TAG = "AlbumPage";
     65 
     66     public static final String KEY_MEDIA_PATH = "media-path";
     67     public static final String KEY_PARENT_MEDIA_PATH = "parent-media-path";
     68     public static final String KEY_SET_CENTER = "set-center";
     69     public static final String KEY_AUTO_SELECT_ALL = "auto-select-all";
     70     public static final String KEY_SHOW_CLUSTER_MENU = "cluster-menu";
     71     public static final String KEY_EMPTY_ALBUM = "empty-album";
     72     public static final String KEY_RESUME_ANIMATION = "resume_animation";
     73 
     74     private static final int REQUEST_SLIDESHOW = 1;
     75     public static final int REQUEST_PHOTO = 2;
     76     private static final int REQUEST_DO_ANIMATION = 3;
     77 
     78     private static final int BIT_LOADING_RELOAD = 1;
     79     private static final int BIT_LOADING_SYNC = 2;
     80 
     81     private static final float USER_DISTANCE_METER = 0.3f;
     82 
     83     private boolean mIsActive = false;
     84     private AlbumSlotRenderer mAlbumView;
     85     private Path mMediaSetPath;
     86     private String mParentMediaSetString;
     87     private SlotView mSlotView;
     88 
     89     private AlbumDataLoader mAlbumDataAdapter;
     90 
     91     protected SelectionManager mSelectionManager;
     92     private Vibrator mVibrator;
     93 
     94     private boolean mGetContent;
     95     private boolean mShowClusterMenu;
     96 
     97     private ActionModeHandler mActionModeHandler;
     98     private int mFocusIndex = 0;
     99     private DetailsHelper mDetailsHelper;
    100     private MyDetailsSource mDetailsSource;
    101     private MediaSet mMediaSet;
    102     private boolean mShowDetails;
    103     private float mUserDistance; // in pixel
    104     private Future<Integer> mSyncTask = null;
    105     private boolean mLaunchedFromPhotoPage;
    106     private boolean mInCameraApp;
    107     private boolean mInCameraAndWantQuitOnPause;
    108 
    109     private int mLoadingBits = 0;
    110     private boolean mInitialSynced = false;
    111     private int mSyncResult;
    112     private boolean mLoadingFailed;
    113     private RelativePosition mOpenCenter = new RelativePosition();
    114 
    115     private Handler mHandler;
    116     private static final int MSG_PICK_PHOTO = 0;
    117 
    118     private PhotoFallbackEffect mResumeEffect;
    119     private PhotoFallbackEffect.PositionProvider mPositionProvider =
    120             new PhotoFallbackEffect.PositionProvider() {
    121         @Override
    122         public Rect getPosition(int index) {
    123             Rect rect = mSlotView.getSlotRect(index);
    124             Rect bounds = mSlotView.bounds();
    125             rect.offset(bounds.left - mSlotView.getScrollX(),
    126                     bounds.top - mSlotView.getScrollY());
    127             return rect;
    128         }
    129 
    130         @Override
    131         public int getItemIndex(Path path) {
    132             int start = mSlotView.getVisibleStart();
    133             int end = mSlotView.getVisibleEnd();
    134             for (int i = start; i < end; ++i) {
    135                 MediaItem item = mAlbumDataAdapter.get(i);
    136                 if (item != null && item.getPath() == path) return i;
    137             }
    138             return -1;
    139         }
    140     };
    141 
    142     @Override
    143     protected int getBackgroundColorId() {
    144         return R.color.album_background;
    145     }
    146 
    147     private final GLView mRootPane = new GLView() {
    148         private final float mMatrix[] = new float[16];
    149 
    150         @Override
    151         protected void onLayout(
    152                 boolean changed, int left, int top, int right, int bottom) {
    153 
    154             int slotViewTop = mActivity.getGalleryActionBar().getHeight();
    155             int slotViewBottom = bottom - top;
    156             int slotViewRight = right - left;
    157 
    158             if (mShowDetails) {
    159                 mDetailsHelper.layout(left, slotViewTop, right, bottom);
    160             } else {
    161                 mAlbumView.setHighlightItemPath(null);
    162             }
    163 
    164             // Set the mSlotView as a reference point to the open animation
    165             mOpenCenter.setReferencePosition(0, slotViewTop);
    166             mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
    167             GalleryUtils.setViewPointMatrix(mMatrix,
    168                     (right - left) / 2, (bottom - top) / 2, -mUserDistance);
    169         }
    170 
    171         @Override
    172         protected void render(GLCanvas canvas) {
    173             canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
    174             canvas.multiplyMatrix(mMatrix, 0);
    175             super.render(canvas);
    176 
    177             if (mResumeEffect != null) {
    178                 boolean more = mResumeEffect.draw(canvas);
    179                 if (!more) {
    180                     mResumeEffect = null;
    181                     mAlbumView.setSlotFilter(null);
    182                 }
    183                 // We want to render one more time even when no more effect
    184                 // required. So that the animated thumbnails could be draw
    185                 // with declarations in super.render().
    186                 invalidate();
    187             }
    188             canvas.restore();
    189         }
    190     };
    191 
    192     // This are the transitions we want:
    193     //
    194     // +--------+           +------------+    +-------+    +----------+
    195     // | Camera |---------->| Fullscreen |--->| Album |--->| AlbumSet |
    196     // |  View  | thumbnail |   Photo    | up | Page  | up |   Page   |
    197     // +--------+           +------------+    +-------+    +----------+
    198     //     ^                      |               |            ^  |
    199     //     |                      |               |            |  |         close
    200     //     +----------back--------+               +----back----+  +--back->  app
    201     //
    202     @Override
    203     protected void onBackPressed() {
    204         if (mShowDetails) {
    205             hideDetails();
    206         } else if (mSelectionManager.inSelectionMode()) {
    207             mSelectionManager.leaveSelectionMode();
    208         } else {
    209             if(mLaunchedFromPhotoPage) {
    210                 mActivity.getTransitionStore().putIfNotPresent(
    211                         PhotoPage.KEY_ALBUMPAGE_TRANSITION,
    212                         PhotoPage.MSG_ALBUMPAGE_RESUMED);
    213             }
    214             // TODO: fix this regression
    215             // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
    216             if (mInCameraApp) {
    217                 super.onBackPressed();
    218             } else {
    219                 onUpPressed();
    220             }
    221         }
    222     }
    223 
    224     private void onUpPressed() {
    225         if (mInCameraApp) {
    226             GalleryUtils.startGalleryActivity(mActivity);
    227         } else if (mActivity.getStateManager().getStateCount() > 1) {
    228             super.onBackPressed();
    229         } else if (mParentMediaSetString != null) {
    230             Bundle data = new Bundle(getData());
    231             data.putString(AlbumSetPage.KEY_MEDIA_PATH, mParentMediaSetString);
    232             mActivity.getStateManager().switchState(
    233                     this, AlbumSetPage.class, data);
    234         }
    235     }
    236 
    237     private void onDown(int index) {
    238         mAlbumView.setPressedIndex(index);
    239     }
    240 
    241     private void onUp(boolean followedByLongPress) {
    242         if (followedByLongPress) {
    243             // Avoid showing press-up animations for long-press.
    244             mAlbumView.setPressedIndex(-1);
    245         } else {
    246             mAlbumView.setPressedUp();
    247         }
    248     }
    249 
    250     private void onSingleTapUp(int slotIndex) {
    251         if (!mIsActive) return;
    252 
    253         if (mSelectionManager.inSelectionMode()) {
    254             MediaItem item = mAlbumDataAdapter.get(slotIndex);
    255             if (item == null) return; // Item not ready yet, ignore the click
    256             mSelectionManager.toggle(item.getPath());
    257             mSlotView.invalidate();
    258         } else {
    259             // Render transition in pressed state
    260             mAlbumView.setPressedIndex(slotIndex);
    261             mAlbumView.setPressedUp();
    262             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PICK_PHOTO, slotIndex, 0),
    263                     FadeTexture.DURATION);
    264         }
    265     }
    266 
    267     private void pickPhoto(int slotIndex) {
    268         pickPhoto(slotIndex, false);
    269     }
    270 
    271     private void pickPhoto(int slotIndex, boolean startInFilmstrip) {
    272         if (!mIsActive) return;
    273 
    274         if (!startInFilmstrip) {
    275             // Launch photos in lights out mode
    276             mActivity.getGLRoot().setLightsOutMode(true);
    277         }
    278 
    279         MediaItem item = mAlbumDataAdapter.get(slotIndex);
    280         if (item == null) return; // Item not ready yet, ignore the click
    281         if (mGetContent) {
    282             onGetContent(item);
    283         } else if (mLaunchedFromPhotoPage) {
    284             TransitionStore transitions = mActivity.getTransitionStore();
    285             transitions.put(
    286                     PhotoPage.KEY_ALBUMPAGE_TRANSITION,
    287                     PhotoPage.MSG_ALBUMPAGE_PICKED);
    288             transitions.put(PhotoPage.KEY_INDEX_HINT, slotIndex);
    289             onBackPressed();
    290         } else {
    291             // Get into the PhotoPage.
    292             // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
    293             Bundle data = new Bundle();
    294             data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
    295             data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT,
    296                     mSlotView.getSlotRect(slotIndex, mRootPane));
    297             data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
    298                     mMediaSetPath.toString());
    299             data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
    300                     item.getPath().toString());
    301             data.putInt(PhotoPage.KEY_ALBUMPAGE_TRANSITION,
    302                     PhotoPage.MSG_ALBUMPAGE_STARTED);
    303             data.putBoolean(PhotoPage.KEY_START_IN_FILMSTRIP,
    304                     startInFilmstrip);
    305             data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, mMediaSet.isCameraRoll());
    306             if (startInFilmstrip) {
    307                 mActivity.getStateManager().switchState(this, PhotoPage.class, data);
    308             } else {
    309                 mActivity.getStateManager().startStateForResult(
    310                             PhotoPage.class, REQUEST_PHOTO, data);
    311             }
    312         }
    313     }
    314 
    315     private void onGetContent(final MediaItem item) {
    316         DataManager dm = mActivity.getDataManager();
    317         Activity activity = mActivity;
    318         if (mData.getString(Gallery.EXTRA_CROP) != null) {
    319             // TODO: Handle MtpImagew
    320             Uri uri = dm.getContentUri(item.getPath());
    321             Intent intent = new Intent(CropImage.ACTION_CROP, uri)
    322                     .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
    323                     .putExtras(getData());
    324             if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
    325                 intent.putExtra(CropImage.KEY_RETURN_DATA, true);
    326             }
    327             activity.startActivity(intent);
    328             activity.finish();
    329         } else {
    330             Intent intent = new Intent(null, item.getContentUri())
    331                 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    332             activity.setResult(Activity.RESULT_OK, intent);
    333             activity.finish();
    334         }
    335     }
    336 
    337     public void onLongTap(int slotIndex) {
    338         if (mGetContent) return;
    339         MediaItem item = mAlbumDataAdapter.get(slotIndex);
    340         if (item == null) return;
    341         mSelectionManager.setAutoLeaveSelectionMode(true);
    342         mSelectionManager.toggle(item.getPath());
    343         mSlotView.invalidate();
    344     }
    345 
    346     @Override
    347     public void doCluster(int clusterType) {
    348         String basePath = mMediaSet.getPath().toString();
    349         String newPath = FilterUtils.newClusterPath(basePath, clusterType);
    350         Bundle data = new Bundle(getData());
    351         data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
    352         if (mShowClusterMenu) {
    353             Context context = mActivity.getAndroidContext();
    354             data.putString(AlbumSetPage.KEY_SET_TITLE, mMediaSet.getName());
    355             data.putString(AlbumSetPage.KEY_SET_SUBTITLE,
    356                     GalleryActionBar.getClusterByTypeString(context, clusterType));
    357         }
    358 
    359         // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
    360         mActivity.getStateManager().startStateForResult(
    361                 AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
    362     }
    363 
    364     @Override
    365     protected void onCreate(Bundle data, Bundle restoreState) {
    366         super.onCreate(data, restoreState);
    367         mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
    368         initializeViews();
    369         initializeData(data);
    370         mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false);
    371         mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false);
    372         mDetailsSource = new MyDetailsSource();
    373         Context context = mActivity.getAndroidContext();
    374         mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
    375 
    376         // Enable auto-select-all for mtp album
    377         if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
    378             mSelectionManager.selectAll();
    379         }
    380 
    381         mLaunchedFromPhotoPage =
    382                 mActivity.getStateManager().hasStateClass(PhotoPage.class);
    383         mInCameraApp = data.getBoolean(PhotoPage.KEY_APP_BRIDGE, false);
    384 
    385         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
    386             @Override
    387             public void handleMessage(Message message) {
    388                 switch (message.what) {
    389                     case MSG_PICK_PHOTO: {
    390                         pickPhoto(message.arg1);
    391                         break;
    392                     }
    393                     default:
    394                         throw new AssertionError(message.what);
    395                 }
    396             }
    397         };
    398     }
    399 
    400     @Override
    401     protected void onResume() {
    402         super.onResume();
    403         mIsActive = true;
    404 
    405         mResumeEffect = mActivity.getTransitionStore().get(KEY_RESUME_ANIMATION);
    406         if (mResumeEffect != null) {
    407             mAlbumView.setSlotFilter(mResumeEffect);
    408             mResumeEffect.setPositionProvider(mPositionProvider);
    409             mResumeEffect.start();
    410         }
    411 
    412         setContentPane(mRootPane);
    413 
    414         boolean enableHomeButton = (mActivity.getStateManager().getStateCount() > 1) |
    415                 mParentMediaSetString != null;
    416         GalleryActionBar actionBar = mActivity.getGalleryActionBar();
    417         actionBar.setDisplayOptions(enableHomeButton, false);
    418         if (!mGetContent) {
    419             actionBar.enableAlbumModeMenu(GalleryActionBar.ALBUM_GRID_MODE_SELECTED, this);
    420         }
    421 
    422         // Set the reload bit here to prevent it exit this page in clearLoadingBit().
    423         setLoadingBit(BIT_LOADING_RELOAD);
    424         mLoadingFailed = false;
    425         mAlbumDataAdapter.resume();
    426 
    427         mAlbumView.resume();
    428         mAlbumView.setPressedIndex(-1);
    429         mActionModeHandler.resume();
    430         if (!mInitialSynced) {
    431             setLoadingBit(BIT_LOADING_SYNC);
    432             mSyncTask = mMediaSet.requestSync(this);
    433         }
    434         mInCameraAndWantQuitOnPause = mInCameraApp;
    435     }
    436 
    437     @Override
    438     protected void onPause() {
    439         super.onPause();
    440         mIsActive = false;
    441 
    442         if (mSelectionManager.inSelectionMode()) {
    443             mSelectionManager.leaveSelectionMode();
    444         }
    445         mAlbumView.setSlotFilter(null);
    446 
    447         mAlbumDataAdapter.pause();
    448         mAlbumView.pause();
    449         DetailsHelper.pause();
    450         if (!mGetContent) {
    451             mActivity.getGalleryActionBar().disableAlbumModeMenu(true);
    452         }
    453 
    454         if (mSyncTask != null) {
    455             mSyncTask.cancel();
    456             mSyncTask = null;
    457             clearLoadingBit(BIT_LOADING_SYNC);
    458         }
    459         mActionModeHandler.pause();
    460     }
    461 
    462     @Override
    463     protected void onDestroy() {
    464         super.onDestroy();
    465         if (mAlbumDataAdapter != null) {
    466             mAlbumDataAdapter.setLoadingListener(null);
    467         }
    468     }
    469 
    470     private void initializeViews() {
    471         mSelectionManager = new SelectionManager(mActivity, false);
    472         mSelectionManager.setSelectionListener(this);
    473         Config.AlbumPage config = Config.AlbumPage.get(mActivity);
    474         mSlotView = new SlotView(mActivity, config.slotViewSpec);
    475         mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView,
    476                 mSelectionManager, config.placeholderColor);
    477         mSlotView.setSlotRenderer(mAlbumView);
    478         mRootPane.addComponent(mSlotView);
    479         mSlotView.setListener(new SlotView.SimpleListener() {
    480             @Override
    481             public void onDown(int index) {
    482                 AlbumPage.this.onDown(index);
    483             }
    484 
    485             @Override
    486             public void onUp(boolean followedByLongPress) {
    487                 AlbumPage.this.onUp(followedByLongPress);
    488             }
    489 
    490             @Override
    491             public void onSingleTapUp(int slotIndex) {
    492                 AlbumPage.this.onSingleTapUp(slotIndex);
    493             }
    494 
    495             @Override
    496             public void onLongTap(int slotIndex) {
    497                 AlbumPage.this.onLongTap(slotIndex);
    498             }
    499         });
    500         mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
    501         mActionModeHandler.setActionModeListener(new ActionModeListener() {
    502             @Override
    503             public boolean onActionItemClicked(MenuItem item) {
    504                 return onItemSelected(item);
    505             }
    506         });
    507     }
    508 
    509     private void initializeData(Bundle data) {
    510         mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH));
    511         mParentMediaSetString = data.getString(KEY_PARENT_MEDIA_PATH);
    512         mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath);
    513         if (mMediaSet == null) {
    514             Utils.fail("MediaSet is null. Path = %s", mMediaSetPath);
    515         }
    516         mSelectionManager.setSourceMediaSet(mMediaSet);
    517         mAlbumDataAdapter = new AlbumDataLoader(mActivity, mMediaSet);
    518         mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
    519         mAlbumView.setModel(mAlbumDataAdapter);
    520     }
    521 
    522     private void showDetails() {
    523         mShowDetails = true;
    524         if (mDetailsHelper == null) {
    525             mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
    526             mDetailsHelper.setCloseListener(new CloseListener() {
    527                 @Override
    528                 public void onClose() {
    529                     hideDetails();
    530                 }
    531             });
    532         }
    533         mDetailsHelper.show();
    534     }
    535 
    536     private void hideDetails() {
    537         mShowDetails = false;
    538         mDetailsHelper.hide();
    539         mAlbumView.setHighlightItemPath(null);
    540         mSlotView.invalidate();
    541     }
    542 
    543     @Override
    544     protected boolean onCreateActionBar(Menu menu) {
    545         GalleryActionBar actionBar = mActivity.getGalleryActionBar();
    546         MenuInflater inflator = getSupportMenuInflater();
    547         if (mGetContent) {
    548             inflator.inflate(R.menu.pickup, menu);
    549             int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS,
    550                     DataManager.INCLUDE_IMAGE);
    551             actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
    552         } else {
    553             inflator.inflate(R.menu.album, menu);
    554             actionBar.setTitle(mMediaSet.getName());
    555 
    556             menu.findItem(R.id.action_slideshow)
    557                     .setVisible(!(mMediaSet instanceof MtpDevice));
    558 
    559             FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
    560 
    561             menu.findItem(R.id.action_group_by).setVisible(mShowClusterMenu);
    562             menu.findItem(R.id.action_camera).setVisible(
    563                     MediaSetUtils.isCameraSource(mMediaSetPath)
    564                     && GalleryUtils.isCameraAvailable(mActivity));
    565 
    566         }
    567         actionBar.setSubtitle(null);
    568         return true;
    569     }
    570 
    571     private void prepareAnimationBackToFilmstrip(int slotIndex) {
    572         if (mAlbumDataAdapter == null || !mAlbumDataAdapter.isActive(slotIndex)) return;
    573         MediaItem item = mAlbumDataAdapter.get(slotIndex);
    574         if (item == null) return;
    575         TransitionStore transitions = mActivity.getTransitionStore();
    576         transitions.put(PhotoPage.KEY_INDEX_HINT, slotIndex);
    577         transitions.put(PhotoPage.KEY_OPEN_ANIMATION_RECT,
    578                 mSlotView.getSlotRect(slotIndex, mRootPane));
    579     }
    580 
    581     private void switchToFilmstrip() {
    582         if (mAlbumDataAdapter.size() < 1) return;
    583         int targetPhoto = mSlotView.getVisibleStart();
    584         prepareAnimationBackToFilmstrip(targetPhoto);
    585         if(mLaunchedFromPhotoPage) {
    586             onBackPressed();
    587         } else {
    588             pickPhoto(targetPhoto, true);
    589         }
    590     }
    591 
    592     @Override
    593     protected boolean onItemSelected(MenuItem item) {
    594         switch (item.getItemId()) {
    595             case android.R.id.home: {
    596                 onUpPressed();
    597                 return true;
    598             }
    599             case R.id.action_cancel:
    600                 mActivity.getStateManager().finishState(this);
    601                 return true;
    602             case R.id.action_select:
    603                 mSelectionManager.setAutoLeaveSelectionMode(false);
    604                 mSelectionManager.enterSelectionMode();
    605                 return true;
    606             case R.id.action_group_by: {
    607                 mActivity.getGalleryActionBar().showClusterDialog(this);
    608                 return true;
    609             }
    610             case R.id.action_slideshow: {
    611                 mInCameraAndWantQuitOnPause = false;
    612                 Bundle data = new Bundle();
    613                 data.putString(SlideshowPage.KEY_SET_PATH,
    614                         mMediaSetPath.toString());
    615                 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
    616                 mActivity.getStateManager().startStateForResult(
    617                         SlideshowPage.class, REQUEST_SLIDESHOW, data);
    618                 return true;
    619             }
    620             case R.id.action_details: {
    621                 if (mShowDetails) {
    622                     hideDetails();
    623                 } else {
    624                     showDetails();
    625                 }
    626                 return true;
    627             }
    628             case R.id.action_camera: {
    629                 GalleryUtils.startCameraActivity(mActivity);
    630                 return true;
    631             }
    632             default:
    633                 return false;
    634         }
    635     }
    636 
    637     @Override
    638     protected void onStateResult(int request, int result, Intent data) {
    639         switch (request) {
    640             case REQUEST_SLIDESHOW: {
    641                 // data could be null, if there is no images in the album
    642                 if (data == null) return;
    643                 mFocusIndex = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
    644                 mSlotView.setCenterIndex(mFocusIndex);
    645                 break;
    646             }
    647             case REQUEST_PHOTO: {
    648                 if (data == null) return;
    649                 mFocusIndex = data.getIntExtra(PhotoPage.KEY_RETURN_INDEX_HINT, 0);
    650                 mSlotView.makeSlotVisible(mFocusIndex);
    651                 break;
    652             }
    653             case REQUEST_DO_ANIMATION: {
    654                 mSlotView.startRisingAnimation();
    655                 break;
    656             }
    657         }
    658     }
    659 
    660     @Override
    661     public void onSelectionModeChange(int mode) {
    662         switch (mode) {
    663             case SelectionManager.ENTER_SELECTION_MODE: {
    664                 mActionModeHandler.startActionMode();
    665                 if (mHapticsEnabled) mVibrator.vibrate(100);
    666                 break;
    667             }
    668             case SelectionManager.LEAVE_SELECTION_MODE: {
    669                 mActionModeHandler.finishActionMode();
    670                 mRootPane.invalidate();
    671                 break;
    672             }
    673             case SelectionManager.SELECT_ALL_MODE: {
    674                 mActionModeHandler.updateSupportedOperation();
    675                 mRootPane.invalidate();
    676                 break;
    677             }
    678         }
    679     }
    680 
    681     @Override
    682     public void onSelectionChange(Path path, boolean selected) {
    683         int count = mSelectionManager.getSelectedCount();
    684         String format = mActivity.getResources().getQuantityString(
    685                 R.plurals.number_of_items_selected, count);
    686         mActionModeHandler.setTitle(String.format(format, count));
    687         mActionModeHandler.updateSupportedOperation(path, selected);
    688     }
    689 
    690     @Override
    691     public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
    692         Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
    693                 + resultCode);
    694         ((Activity) mActivity).runOnUiThread(new Runnable() {
    695             @Override
    696             public void run() {
    697                 GLRoot root = mActivity.getGLRoot();
    698                 root.lockRenderThread();
    699                 mSyncResult = resultCode;
    700                 try {
    701                     if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
    702                         mInitialSynced = true;
    703                     }
    704                     clearLoadingBit(BIT_LOADING_SYNC);
    705                     showSyncErrorIfNecessary(mLoadingFailed);
    706                 } finally {
    707                     root.unlockRenderThread();
    708                 }
    709             }
    710         });
    711     }
    712 
    713     // Show sync error toast when all the following conditions are met:
    714     // (1) both loading and sync are done,
    715     // (2) sync result is error,
    716     // (3) the page is still active, and
    717     // (4) no photo is shown or loading fails.
    718     private void showSyncErrorIfNecessary(boolean loadingFailed) {
    719         if ((mLoadingBits == 0) && (mSyncResult == MediaSet.SYNC_RESULT_ERROR) && mIsActive
    720                 && (loadingFailed || (mAlbumDataAdapter.size() == 0))) {
    721             Toast.makeText(mActivity, R.string.sync_album_error,
    722                     Toast.LENGTH_LONG).show();
    723         }
    724     }
    725 
    726     private void setLoadingBit(int loadTaskBit) {
    727         mLoadingBits |= loadTaskBit;
    728     }
    729 
    730     private void clearLoadingBit(int loadTaskBit) {
    731         mLoadingBits &= ~loadTaskBit;
    732         if (mLoadingBits == 0 && mIsActive) {
    733             if (mAlbumDataAdapter.size() == 0) {
    734                 Intent result = new Intent();
    735                 result.putExtra(KEY_EMPTY_ALBUM, true);
    736                 setStateResult(Activity.RESULT_OK, result);
    737                 mActivity.getStateManager().finishState(this);
    738             }
    739         }
    740     }
    741 
    742     private class MyLoadingListener implements LoadingListener {
    743         @Override
    744         public void onLoadingStarted() {
    745             setLoadingBit(BIT_LOADING_RELOAD);
    746             mLoadingFailed = false;
    747         }
    748 
    749         @Override
    750         public void onLoadingFinished(boolean loadingFailed) {
    751             clearLoadingBit(BIT_LOADING_RELOAD);
    752             mLoadingFailed = loadingFailed;
    753             showSyncErrorIfNecessary(loadingFailed);
    754         }
    755     }
    756 
    757     private class MyDetailsSource implements DetailsHelper.DetailsSource {
    758         private int mIndex;
    759 
    760         @Override
    761         public int size() {
    762             return mAlbumDataAdapter.size();
    763         }
    764 
    765         @Override
    766         public int setIndex() {
    767             Path id = mSelectionManager.getSelected(false).get(0);
    768             mIndex = mAlbumDataAdapter.findItem(id);
    769             return mIndex;
    770         }
    771 
    772         @Override
    773         public MediaDetails getDetails() {
    774             // this relies on setIndex() being called beforehand
    775             MediaObject item = mAlbumDataAdapter.get(mIndex);
    776             if (item != null) {
    777                 mAlbumView.setHighlightItemPath(item.getPath());
    778                 return item.getDetails();
    779             } else {
    780                 return null;
    781             }
    782         }
    783     }
    784 
    785     @Override
    786     public void onAlbumModeSelected(int mode) {
    787         if (mode == GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED) {
    788             switchToFilmstrip();
    789         }
    790     }
    791 }
    792