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
getBackgroundColorId()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
onBackPressed()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 
onUpPressed()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 
onDown(int index)238     private void onDown(int index) {
239         mAlbumView.setPressedIndex(index);
240     }
241 
onUp(boolean followedByLongPress)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 
onSingleTapUp(int slotIndex)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 
pickPhoto(int slotIndex)268     private void pickPhoto(int slotIndex) {
269         pickPhoto(slotIndex, false);
270     }
271 
pickPhoto(int slotIndex, boolean startInFilmstrip)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 
onGetContent(final MediaItem item)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 
onLongTap(int slotIndex)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
doCluster(int clusterType)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
onCreate(Bundle data, Bundle restoreState)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
onResume()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
onPause()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
onDestroy()460     protected void onDestroy() {
461         super.onDestroy();
462         if (mAlbumDataAdapter != null) {
463             mAlbumDataAdapter.setLoadingListener(null);
464         }
465         mActionModeHandler.destroy();
466     }
467 
initializeViews()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 
initializeData(Bundle data)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 
showDetails()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 
hideDetails()534     private void hideDetails() {
535         mShowDetails = false;
536         mDetailsHelper.hide();
537         mAlbumView.setHighlightItemPath(null);
538         mSlotView.invalidate();
539     }
540 
541     @Override
onCreateActionBar(Menu menu)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 
prepareAnimationBackToFilmstrip(int slotIndex)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 
switchToFilmstrip()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
onItemSelected(MenuItem item)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
onStateResult(int request, int result, Intent data)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
onSelectionModeChange(int mode)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
onSelectionChange(Path path, boolean selected)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
onSyncDone(final MediaSet mediaSet, final int resultCode)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.
showSyncErrorIfNecessary(boolean loadingFailed)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 
setLoadingBit(int loadTaskBit)721     private void setLoadingBit(int loadTaskBit) {
722         mLoadingBits |= loadTaskBit;
723     }
724 
clearLoadingBit(int loadTaskBit)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
onLoadingStarted()739         public void onLoadingStarted() {
740             setLoadingBit(BIT_LOADING_RELOAD);
741             mLoadingFailed = false;
742         }
743 
744         @Override
onLoadingFinished(boolean loadingFailed)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
size()756         public int size() {
757             return mAlbumDataAdapter.size();
758         }
759 
760         @Override
setIndex()761         public int setIndex() {
762             Path id = mSelectionManager.getSelected(false).get(0);
763             mIndex = mAlbumDataAdapter.findItem(id);
764             return mIndex;
765         }
766 
767         @Override
getDetails()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
onAlbumModeSelected(int mode)781     public void onAlbumModeSelected(int mode) {
782         if (mode == GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED) {
783             switchToFilmstrip();
784         }
785     }
786 }
787