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