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