• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
getBackgroundColorId()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
onBackPressed()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 
onUpPressed()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 
onDown(int index)237     private void onDown(int index) {
238         mAlbumView.setPressedIndex(index);
239     }
240 
onUp(boolean followedByLongPress)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 
onSingleTapUp(int slotIndex)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 
pickPhoto(int slotIndex)267     private void pickPhoto(int slotIndex) {
268         pickPhoto(slotIndex, false);
269     }
270 
pickPhoto(int slotIndex, boolean startInFilmstrip)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 
onGetContent(final MediaItem item)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 
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(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
onResume()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
onPause()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
onDestroy()463     protected void onDestroy() {
464         super.onDestroy();
465         if (mAlbumDataAdapter != null) {
466             mAlbumDataAdapter.setLoadingListener(null);
467         }
468     }
469 
initializeViews()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 
initializeData(Bundle data)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 
showDetails()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 
hideDetails()536     private void hideDetails() {
537         mShowDetails = false;
538         mDetailsHelper.hide();
539         mAlbumView.setHighlightItemPath(null);
540         mSlotView.invalidate();
541     }
542 
543     @Override
onCreateActionBar(Menu menu)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 
prepareAnimationBackToFilmstrip(int slotIndex)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 
switchToFilmstrip()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
onItemSelected(MenuItem item)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
onStateResult(int request, int result, Intent data)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
onSelectionModeChange(int mode)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
onSelectionChange(Path path, boolean selected)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
onSyncDone(final MediaSet mediaSet, final int resultCode)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.
showSyncErrorIfNecessary(boolean loadingFailed)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 
setLoadingBit(int loadTaskBit)726     private void setLoadingBit(int loadTaskBit) {
727         mLoadingBits |= loadTaskBit;
728     }
729 
clearLoadingBit(int loadTaskBit)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
onLoadingStarted()744         public void onLoadingStarted() {
745             setLoadingBit(BIT_LOADING_RELOAD);
746             mLoadingFailed = false;
747         }
748 
749         @Override
onLoadingFinished(boolean loadingFailed)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
size()761         public int size() {
762             return mAlbumDataAdapter.size();
763         }
764 
765         @Override
setIndex()766         public int setIndex() {
767             Path id = mSelectionManager.getSelected(false).get(0);
768             mIndex = mAlbumDataAdapter.findItem(id);
769             return mIndex;
770         }
771 
772         @Override
getDetails()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
onAlbumModeSelected(int mode)786     public void onAlbumModeSelected(int mode) {
787         if (mode == GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED) {
788             switchToFilmstrip();
789         }
790     }
791 }
792