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