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