PhotoPage.java revision ee899c09c88e5b054397ac44ad20e5055cd24f39
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.annotation.TargetApi;
20import android.app.Activity;
21import android.content.ActivityNotFoundException;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageManager;
25import android.graphics.Rect;
26import android.net.Uri;
27import android.nfc.NfcAdapter;
28import android.nfc.NfcAdapter.CreateBeamUrisCallback;
29import android.nfc.NfcEvent;
30import android.os.Bundle;
31import android.os.Handler;
32import android.os.Message;
33import android.os.SystemClock;
34import android.view.animation.AccelerateInterpolator;
35import android.widget.RelativeLayout;
36import android.widget.Toast;
37
38import com.actionbarsherlock.app.ActionBar.OnMenuVisibilityListener;
39import com.actionbarsherlock.view.Menu;
40import com.actionbarsherlock.view.MenuItem;
41import com.android.gallery3d.R;
42import com.android.gallery3d.anim.FloatAnimation;
43import com.android.gallery3d.common.ApiHelper;
44import com.android.gallery3d.common.Utils;
45import com.android.gallery3d.data.ComboAlbum;
46import com.android.gallery3d.data.DataManager;
47import com.android.gallery3d.data.FilterDeleteSet;
48import com.android.gallery3d.data.MediaDetails;
49import com.android.gallery3d.data.MediaItem;
50import com.android.gallery3d.data.MediaObject;
51import com.android.gallery3d.data.MediaSet;
52import com.android.gallery3d.data.MtpSource;
53import com.android.gallery3d.data.Path;
54import com.android.gallery3d.data.SecureAlbum;
55import com.android.gallery3d.data.SecureSource;
56import com.android.gallery3d.data.SnailAlbum;
57import com.android.gallery3d.data.SnailItem;
58import com.android.gallery3d.data.SnailSource;
59import com.android.gallery3d.picasasource.PicasaSource;
60import com.android.gallery3d.ui.AnimationTime;
61import com.android.gallery3d.ui.BitmapScreenNail;
62import com.android.gallery3d.ui.DetailsHelper;
63import com.android.gallery3d.ui.DetailsHelper.CloseListener;
64import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
65import com.android.gallery3d.ui.GLCanvas;
66import com.android.gallery3d.ui.GLRoot;
67import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
68import com.android.gallery3d.ui.GLView;
69import com.android.gallery3d.ui.ImportCompleteListener;
70import com.android.gallery3d.ui.MenuExecutor;
71import com.android.gallery3d.ui.PhotoFallbackEffect;
72import com.android.gallery3d.ui.PhotoView;
73import com.android.gallery3d.ui.PreparePageFadeoutTexture;
74import com.android.gallery3d.ui.RawTexture;
75import com.android.gallery3d.ui.SelectionManager;
76import com.android.gallery3d.ui.SynchronizedHandler;
77import com.android.gallery3d.util.GalleryUtils;
78import com.android.gallery3d.util.LightCycleHelper;
79
80public class PhotoPage extends ActivityState implements
81        PhotoView.Listener, OrientationManager.Listener, AppBridge.Server,
82        PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener {
83    private static final String TAG = "PhotoPage";
84
85    private static final int MSG_HIDE_BARS = 1;
86    private static final int MSG_LOCK_ORIENTATION = 2;
87    private static final int MSG_UNLOCK_ORIENTATION = 3;
88    private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
89    private static final int MSG_UPDATE_ACTION_BAR = 5;
90    private static final int MSG_UNFREEZE_GLROOT = 6;
91    private static final int MSG_WANT_BARS = 7;
92    private static final int MSG_REFRESH_BOTTOM_CONTROLS = 8;
93    private static final int MSG_ON_CAMERA_CENTER = 9;
94
95    private static final int HIDE_BARS_TIMEOUT = 3500;
96    private static final int UNFREEZE_GLROOT_TIMEOUT = 250;
97
98    private static final int REQUEST_SLIDESHOW = 1;
99    private static final int REQUEST_CROP = 2;
100    private static final int REQUEST_CROP_PICASA = 3;
101    private static final int REQUEST_EDIT = 4;
102    private static final int REQUEST_PLAY_VIDEO = 5;
103    private static final int REQUEST_TRIM = 6;
104
105    public static final String KEY_MEDIA_SET_PATH = "media-set-path";
106    public static final String KEY_MEDIA_ITEM_PATH = "media-item-path";
107    public static final String KEY_INDEX_HINT = "index-hint";
108    public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect";
109    public static final String KEY_APP_BRIDGE = "app-bridge";
110    public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up";
111    public static final String KEY_START_IN_FILMSTRIP = "start-in-filmstrip";
112    public static final String KEY_RETURN_INDEX_HINT = "return-index-hint";
113    public static final String KEY_SHOW_WHEN_LOCKED = "show_when_locked";
114
115    public static final String KEY_ALBUMPAGE_TRANSITION = "albumpage-transition";
116    public static final int MSG_ALBUMPAGE_NONE = 0;
117    public static final int MSG_ALBUMPAGE_STARTED = 1;
118    public static final int MSG_ALBUMPAGE_RESUMED = 2;
119    public static final int MSG_ALBUMPAGE_PICKED = 4;
120
121    public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit";
122
123    private GalleryApp mApplication;
124    private SelectionManager mSelectionManager;
125
126    private PhotoView mPhotoView;
127    private PhotoPage.Model mModel;
128    private DetailsHelper mDetailsHelper;
129    private boolean mShowDetails;
130
131    // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
132    // E.g., viewing a photo in gmail attachment
133    private FilterDeleteSet mMediaSet;
134
135    // The mediaset used by camera launched from secure lock screen.
136    private SecureAlbum mSecureAlbum;
137
138    private int mCurrentIndex = 0;
139    private Handler mHandler;
140    private boolean mShowBars = true;
141    private volatile boolean mActionBarAllowed = true;
142    private GalleryActionBar mActionBar;
143    private boolean mIsMenuVisible;
144    private boolean mHaveImageEditor;
145    private PhotoPageBottomControls mBottomControls;
146    private MediaItem mCurrentPhoto = null;
147    private MenuExecutor mMenuExecutor;
148    private boolean mIsActive;
149    private String mSetPathString;
150    // This is the original mSetPathString before adding the camera preview item.
151    private String mOriginalSetPathString;
152    private AppBridge mAppBridge;
153    private SnailItem mScreenNailItem;
154    private SnailAlbum mScreenNailSet;
155    private OrientationManager mOrientationManager;
156    private boolean mHasActivityResult;
157    private boolean mTreatBackAsUp;
158    private boolean mStartInFilmstrip;
159    private boolean mStartedFromAlbumPage;
160
161    private long mCameraSwitchCutoff = 0;
162    private boolean mSkipUpdateCurrentPhoto = false;
163    private static final long CAMERA_SWITCH_CUTOFF_THRESHOLD_MS = 300;
164
165    private RawTexture mFadeOutTexture;
166    private Rect mOpenAnimationRect;
167    public static final int ANIM_TIME_OPENING = 300;
168
169    // The item that is deleted (but it can still be undeleted before commiting)
170    private Path mDeletePath;
171    private boolean mDeleteIsFocus;  // whether the deleted item was in focus
172
173    private Uri[] mNfcPushUris = new Uri[1];
174
175    private final MyMenuVisibilityListener mMenuVisibilityListener =
176            new MyMenuVisibilityListener();
177
178    public static interface Model extends PhotoView.Model {
179        public void resume();
180        public void pause();
181        public boolean isEmpty();
182        public void setCurrentPhoto(Path path, int indexHint);
183    }
184
185    private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
186        @Override
187        public void onMenuVisibilityChanged(boolean isVisible) {
188            mIsMenuVisible = isVisible;
189            refreshHidingMessage();
190        }
191    }
192
193    private static class BackgroundFadeOut extends FloatAnimation {
194        public BackgroundFadeOut() {
195            super(1f, 0f, ANIM_TIME_OPENING);
196            setInterpolator(new AccelerateInterpolator(2f));
197        }
198    }
199
200    private final FloatAnimation mBackgroundFade = new BackgroundFadeOut();
201
202    @Override
203    protected int getBackgroundColorId() {
204        return R.color.photo_background;
205    }
206
207    private final GLView mRootPane = new GLView() {
208        @Override
209        protected void renderBackground(GLCanvas view) {
210            if (mFadeOutTexture != null) {
211                if (mBackgroundFade.calculate(AnimationTime.get())) invalidate();
212                if (!mBackgroundFade.isActive()) {
213                    mFadeOutTexture = null;
214                    mOpenAnimationRect = null;
215                    BitmapScreenNail.enableDrawPlaceholder();
216                } else {
217                    float fadeAlpha = mBackgroundFade.get();
218                    if (fadeAlpha < 1f) {
219                        view.clearBuffer(getBackgroundColor());
220                        view.setAlpha(fadeAlpha);
221                    }
222                    mFadeOutTexture.draw(view, 0, 0);
223                    view.setAlpha(1f - fadeAlpha);
224                    return;
225                }
226            }
227            view.clearBuffer(getBackgroundColor());
228        }
229
230        @Override
231        protected void onLayout(
232                boolean changed, int left, int top, int right, int bottom) {
233            mPhotoView.layout(0, 0, right - left, bottom - top);
234            if (mShowDetails) {
235                mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom);
236            }
237        }
238    };
239
240    @Override
241    public void onCreate(Bundle data, Bundle restoreState) {
242        super.onCreate(data, restoreState);
243        mActionBar = mActivity.getGalleryActionBar();
244        mSelectionManager = new SelectionManager(mActivity, false);
245        mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
246
247        mPhotoView = new PhotoView(mActivity);
248        mPhotoView.setListener(this);
249        mRootPane.addComponent(mPhotoView);
250        mApplication = (GalleryApp) ((Activity) mActivity).getApplication();
251        mOrientationManager = mActivity.getOrientationManager();
252        mOrientationManager.addListener(this);
253        mActivity.getGLRoot().setOrientationSource(mOrientationManager);
254
255        mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
256            @Override
257            public void handleMessage(Message message) {
258                switch (message.what) {
259                    case MSG_HIDE_BARS: {
260                        hideBars();
261                        break;
262                    }
263                    case MSG_REFRESH_BOTTOM_CONTROLS: {
264                        if (mBottomControls != null) mBottomControls.refresh();
265                        break;
266                    }
267                    case MSG_LOCK_ORIENTATION: {
268                        mOrientationManager.lockOrientation();
269                        break;
270                    }
271                    case MSG_UNLOCK_ORIENTATION: {
272                        mOrientationManager.unlockOrientation();
273                        break;
274                    }
275                    case MSG_ON_FULL_SCREEN_CHANGED: {
276                        mAppBridge.onFullScreenChanged(message.arg1 == 1);
277                        break;
278                    }
279                    case MSG_UPDATE_ACTION_BAR: {
280                        updateBars();
281                        break;
282                    }
283                    case MSG_WANT_BARS: {
284                        wantBars();
285                        break;
286                    }
287                    case MSG_UNFREEZE_GLROOT: {
288                        mActivity.getGLRoot().unfreeze();
289                        break;
290                    }
291                    case MSG_ON_CAMERA_CENTER: {
292                        mSkipUpdateCurrentPhoto = false;
293                        boolean updateNeeded = false;
294                        if (!mPhotoView.getFilmMode()) {
295                            lockOrientation();
296                            updateNeeded = true;
297                        } else if (SystemClock.uptimeMillis() < mCameraSwitchCutoff &&
298                                mMediaSet.getMediaItemCount() > 1) {
299                            mPhotoView.switchToImage(1);
300                        } else {
301                            mPhotoView.setFilmMode(false);
302                            updateNeeded = true;
303                        }
304
305                        if (updateNeeded) {
306                            updateBars();
307                            updateCurrentPhoto(mModel.getMediaItem(0));
308                        }
309                        break;
310                    }
311                    default: throw new AssertionError(message.what);
312                }
313            }
314        };
315
316        mSetPathString = data.getString(KEY_MEDIA_SET_PATH);
317        mOriginalSetPathString = mSetPathString;
318        setupNfcBeamPush();
319        String itemPathString = data.getString(KEY_MEDIA_ITEM_PATH);
320        Path itemPath = itemPathString != null ?
321                Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)) :
322                    null;
323        mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false);
324        mStartInFilmstrip =
325            data.getBoolean(KEY_START_IN_FILMSTRIP, false);
326        mStartedFromAlbumPage =
327                data.getInt(KEY_ALBUMPAGE_TRANSITION,
328                        MSG_ALBUMPAGE_NONE) == MSG_ALBUMPAGE_STARTED;
329        if (mSetPathString != null) {
330            mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE);
331            if (mAppBridge != null) {
332                mShowBars = false;
333
334                mAppBridge.setServer(this);
335                mOrientationManager.lockOrientation();
336
337                // Get the ScreenNail from AppBridge and register it.
338                int id = SnailSource.newId();
339                Path screenNailSetPath = SnailSource.getSetPath(id);
340                Path screenNailItemPath = SnailSource.getItemPath(id);
341                mScreenNailSet = (SnailAlbum) mActivity.getDataManager()
342                        .getMediaObject(screenNailSetPath);
343                mScreenNailItem = (SnailItem) mActivity.getDataManager()
344                        .getMediaObject(screenNailItemPath);
345                mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
346
347                // Check if the path is a secure album.
348                if (SecureSource.isSecurePath(mSetPathString)) {
349                    mSecureAlbum = (SecureAlbum) mActivity.getDataManager()
350                            .getMediaSet(mSetPathString);
351                }
352                if (data.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) {
353                    // Set the flag to be on top of the lock screen.
354                    mFlags |= FLAG_SHOW_WHEN_LOCKED;
355                }
356
357                // Don't display "empty album" action item for capture intents
358                if(!mSetPathString.equals("/local/all/0")) {
359                    mSetPathString = "/filter/empty/{"+mSetPathString+"}";
360                }
361
362                // Combine the original MediaSet with the one for ScreenNail
363                // from AppBridge.
364                mSetPathString = "/combo/item/{" + screenNailSetPath +
365                        "," + mSetPathString + "}";
366
367                // Start from the screen nail.
368                itemPath = screenNailItemPath;
369            }
370
371            MediaSet originalSet = mActivity.getDataManager()
372                    .getMediaSet(mSetPathString);
373            if (originalSet instanceof ComboAlbum) {
374                // Use the name of the camera album rather than the default
375                // ComboAlbum behavior
376                ((ComboAlbum) originalSet).useNameOfChild(1);
377            }
378            mSelectionManager.setSourceMediaSet(originalSet);
379            mSetPathString = "/filter/delete/{" + mSetPathString + "}";
380            mMediaSet = (FilterDeleteSet) mActivity.getDataManager()
381                    .getMediaSet(mSetPathString);
382            mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
383            if (mMediaSet == null) {
384                Log.w(TAG, "failed to restore " + mSetPathString);
385            }
386            if (itemPath == null) {
387                int mediaItemCount = mMediaSet.getMediaItemCount();
388                if (mediaItemCount > 0) {
389                    if (mCurrentIndex >= mediaItemCount) mCurrentIndex = 0;
390                    itemPath = mMediaSet.getMediaItem(mCurrentIndex, 1)
391                        .get(0).getPath();
392                } else {
393                    // Bail out, PhotoPage can't load on an empty album
394                    return;
395                }
396            }
397            PhotoDataAdapter pda = new PhotoDataAdapter(
398                    mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex,
399                    mAppBridge == null ? -1 : 0,
400                    mAppBridge == null ? false : mAppBridge.isPanorama(),
401                    mAppBridge == null ? false : mAppBridge.isStaticCamera());
402            mModel = pda;
403            mPhotoView.setModel(mModel);
404
405            pda.setDataListener(new PhotoDataAdapter.DataListener() {
406
407                @Override
408                public void onPhotoChanged(int index, Path item) {
409                    int oldIndex = mCurrentIndex;
410                    mCurrentIndex = index;
411
412                    if (mAppBridge != null) {
413                        mPhotoView.setWantCameraCenterCallbacks(true);
414                        if (mCurrentIndex > 0) {
415                            mHandler.removeMessages(MSG_ON_CAMERA_CENTER);
416                            mSkipUpdateCurrentPhoto = false;
417                        }
418
419                        if (oldIndex == 0 && mCurrentIndex > 0
420                                && !mPhotoView.getFilmMode()) {
421                            mPhotoView.setFilmMode(true);
422                        } else if (oldIndex == 2 && mCurrentIndex == 1) {
423                            mCameraSwitchCutoff = SystemClock.uptimeMillis() +
424                                    CAMERA_SWITCH_CUTOFF_THRESHOLD_MS;
425                            mPhotoView.stopScrolling();
426                        } else if (oldIndex == 1 && mCurrentIndex == 0) {
427                            mSkipUpdateCurrentPhoto = true;
428                        }
429                    }
430                    if (!mSkipUpdateCurrentPhoto) {
431                        if (item != null) {
432                            MediaItem photo = mModel.getMediaItem(0);
433                            if (photo != null) updateCurrentPhoto(photo);
434                        }
435                        updateBars();
436                    }
437                    // Reset the timeout for the bars after a swipe
438                    refreshHidingMessage();
439                }
440
441                @Override
442                public void onLoadingFinished() {
443                    if (!mModel.isEmpty()) {
444                        MediaItem photo = mModel.getMediaItem(0);
445                        if (photo != null) updateCurrentPhoto(photo);
446                    } else if (mIsActive) {
447                        // We only want to finish the PhotoPage if there is no
448                        // deletion that the user can undo.
449                        if (mMediaSet.getNumberOfDeletions() == 0) {
450                            mActivity.getStateManager().finishState(
451                                    PhotoPage.this);
452                        }
453                    }
454                }
455
456                @Override
457                public void onLoadingStarted() {
458                }
459            });
460        } else {
461            // Get default media set by the URI
462            MediaItem mediaItem = (MediaItem)
463                    mActivity.getDataManager().getMediaObject(itemPath);
464            mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
465            mPhotoView.setModel(mModel);
466            updateCurrentPhoto(mediaItem);
467        }
468
469        mPhotoView.setFilmMode(mStartInFilmstrip && mMediaSet.getMediaItemCount() > 1);
470        if (mSecureAlbum == null) {
471            RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity)
472                        .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root);
473            if (galleryRoot != null) {
474                mBottomControls = new PhotoPageBottomControls(this, mActivity, galleryRoot);
475            }
476        }
477    }
478
479    public void onCameraCenter() {
480        mPhotoView.setWantCameraCenterCallbacks(false);
481        mHandler.sendEmptyMessage(MSG_ON_CAMERA_CENTER);
482    }
483
484    public boolean canDisplayBottomControls() {
485        return mShowBars && !mPhotoView.getFilmMode();
486    }
487
488    public boolean canDisplayBottomControl(int control) {
489        if (mCurrentPhoto == null) return false;
490        switch(control) {
491            case R.id.photopage_bottom_control_edit:
492                return mCurrentPhoto.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE;
493            case R.id.photopage_bottom_control_panorama:
494                return (mCurrentPhoto.getSupportedOperations()
495                        & MediaItem.SUPPORT_PANORAMA) != 0;
496            default:
497                return false;
498        }
499    }
500
501    public void onBottomControlClicked(int control) {
502        switch(control) {
503            case R.id.photopage_bottom_control_edit:
504                launchPhotoEditor();
505                return;
506            case R.id.photopage_bottom_control_panorama:
507                LightCycleHelper.viewPanorama(mActivity, mCurrentPhoto.getContentUri());
508                return;
509            default:
510                return;
511        }
512    }
513
514    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
515    private void setupNfcBeamPush() {
516        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) return;
517
518        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mActivity);
519        if (adapter != null) {
520            adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
521                @Override
522                public Uri[] createBeamUris(NfcEvent event) {
523                    return mNfcPushUris;
524                }
525            }, mActivity);
526        }
527    }
528
529    private void setNfcBeamPushUri(Uri uri) {
530        mNfcPushUris[0] = uri;
531    }
532
533    private Intent createShareIntent(Path path) {
534        DataManager manager = mActivity.getDataManager();
535        int type = manager.getMediaType(path);
536        Intent intent = new Intent(Intent.ACTION_SEND);
537        intent.setType(MenuExecutor.getMimeType(type));
538        Uri uri = manager.getContentUri(path);
539        intent.putExtra(Intent.EXTRA_STREAM, uri);
540        return intent;
541    }
542
543    private Intent createSharePanoramaIntent(Path path) {
544        DataManager manager = mActivity.getDataManager();
545        int supported = manager.getSupportedOperations(path);
546        if ((supported & MediaObject.SUPPORT_PANORAMA) == 0) {
547            return null;
548        }
549        Intent intent = new Intent(Intent.ACTION_SEND);
550        intent.setType(GalleryUtils.MIME_TYPE_PANORAMA);
551        Uri uri = manager.getContentUri(path);
552        intent.putExtra(Intent.EXTRA_STREAM, uri);
553        return intent;
554    }
555
556    private void launchPhotoEditor() {
557        MediaItem current = mModel.getMediaItem(0);
558        if (current == null) return;
559
560        Intent intent = new Intent(ACTION_NEXTGEN_EDIT);
561        intent.setData(mActivity.getDataManager().getContentUri(current.getPath())).setFlags(
562                Intent.FLAG_GRANT_READ_URI_PERMISSION);
563        if (mActivity.getPackageManager()
564                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
565            intent.setAction(Intent.ACTION_EDIT);
566        }
567        ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
568                REQUEST_EDIT);
569    }
570
571    private void updateShareURI(Path path) {
572        DataManager manager = mActivity.getDataManager();
573        mActionBar.setShareIntents(
574                createSharePanoramaIntent(path),
575                createShareIntent(path));
576        Uri uri = manager.getContentUri(path);
577        setNfcBeamPushUri(uri);
578    }
579
580    private void updateCurrentPhoto(MediaItem photo) {
581        if (mCurrentPhoto == photo) return;
582        mCurrentPhoto = photo;
583        if (mCurrentPhoto == null) return;
584
585        // If by swiping or deletion the user ends up on an action item
586        // and zoomed in, zoom out so that the context of the action is
587        // more clear
588        if ((photo.getSupportedOperations() & MediaObject.SUPPORT_ACTION) != 0
589                && !mPhotoView.getFilmMode()) {
590            mPhotoView.setFilmMode(true);
591        }
592
593        updateMenuOperations();
594        if (mBottomControls != null) mBottomControls.refresh();
595        if (mShowDetails) {
596            mDetailsHelper.reloadDetails();
597        }
598        if ((mSecureAlbum == null)
599                && (photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
600            updateShareURI(photo.getPath());
601        }
602    }
603
604    private void updateMenuOperations() {
605        Menu menu = mActionBar.getMenu();
606
607        // it could be null if onCreateActionBar has not been called yet
608        if (menu == null) return;
609
610        MenuItem item = menu.findItem(R.id.action_slideshow);
611        if (item != null) {
612            item.setVisible((mSecureAlbum == null) && canDoSlideShow());
613        }
614        if (mCurrentPhoto == null) return;
615
616        int supportedOperations = mCurrentPhoto.getSupportedOperations();
617        if (mSecureAlbum != null) {
618            supportedOperations &= MediaObject.SUPPORT_DELETE;
619        } else if (!mHaveImageEditor) {
620            supportedOperations &= ~MediaObject.SUPPORT_EDIT;
621        }
622        MenuExecutor.updateMenuOperation(menu, supportedOperations);
623    }
624
625    private boolean canDoSlideShow() {
626        if (mMediaSet == null || mCurrentPhoto == null) {
627            return false;
628        }
629        if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) {
630            return false;
631        }
632        if (MtpSource.isMtpPath(mOriginalSetPathString)) {
633            return false;
634        }
635        return true;
636    }
637
638    //////////////////////////////////////////////////////////////////////////
639    //  Action Bar show/hide management
640    //////////////////////////////////////////////////////////////////////////
641
642    private void showBars() {
643        if (mShowBars) return;
644        mShowBars = true;
645        mOrientationManager.unlockOrientation();
646        mActionBar.show();
647        mActivity.getGLRoot().setLightsOutMode(false);
648        refreshHidingMessage();
649        if (mBottomControls != null) mBottomControls.refresh();
650    }
651
652    private void hideBars() {
653        if (!mShowBars) return;
654        mShowBars = false;
655        mActionBar.hide();
656        mActivity.getGLRoot().setLightsOutMode(true);
657        mHandler.removeMessages(MSG_HIDE_BARS);
658        if (mBottomControls != null) mBottomControls.refresh();
659    }
660
661    private void refreshHidingMessage() {
662        mHandler.removeMessages(MSG_HIDE_BARS);
663        if (!mIsMenuVisible && !mPhotoView.getFilmMode()) {
664            mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
665        }
666    }
667
668    private boolean canShowBars() {
669        // No bars if we are showing camera preview.
670        if (mAppBridge != null && mCurrentIndex == 0
671                && !mPhotoView.getFilmMode()) return false;
672
673        // No bars if it's not allowed.
674        if (!mActionBarAllowed) return false;
675
676        return true;
677    }
678
679    private void wantBars() {
680        if (canShowBars()) showBars();
681    }
682
683    private void toggleBars() {
684        if (mShowBars) {
685            hideBars();
686        } else {
687            if (canShowBars()) showBars();
688        }
689    }
690
691    private void updateBars() {
692        if (!canShowBars()) {
693            hideBars();
694        }
695    }
696
697    @Override
698    public void onOrientationCompensationChanged() {
699        mActivity.getGLRoot().requestLayoutContentPane();
700    }
701
702    @Override
703    protected void onBackPressed() {
704        if (mShowDetails) {
705            hideDetails();
706        } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) {
707            // We are leaving this page. Set the result now.
708            setResult();
709            if (mStartInFilmstrip && !mPhotoView.getFilmMode()) {
710                mPhotoView.setFilmMode(true);
711            } else if (mTreatBackAsUp) {
712                onUpPressed();
713            } else {
714                super.onBackPressed();
715            }
716        }
717    }
718
719    private void onUpPressed() {
720        if ((mStartInFilmstrip || mAppBridge != null)
721                && !mPhotoView.getFilmMode()) {
722            mPhotoView.setFilmMode(true);
723            return;
724        }
725
726        if (mActivity.getStateManager().getStateCount() > 1) {
727            setResult();
728            super.onBackPressed();
729            return;
730        }
731
732        if (mOriginalSetPathString == null) return;
733
734        if (mAppBridge == null) {
735            // We're in view mode so set up the stacks on our own.
736            Bundle data = new Bundle(getData());
737            data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
738            data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
739                    mActivity.getDataManager().getTopSetPath(
740                            DataManager.INCLUDE_ALL));
741            mActivity.getStateManager().switchState(this, AlbumPage.class, data);
742        } else {
743            GalleryUtils.startGalleryActivity(mActivity);
744        }
745    }
746
747    private void setResult() {
748        Intent result = null;
749        result = new Intent();
750        result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex);
751        setStateResult(Activity.RESULT_OK, result);
752    }
753
754    //////////////////////////////////////////////////////////////////////////
755    //  AppBridge.Server interface
756    //////////////////////////////////////////////////////////////////////////
757
758    @Override
759    public void setCameraRelativeFrame(Rect frame) {
760        mPhotoView.setCameraRelativeFrame(frame);
761    }
762
763    @Override
764    public boolean switchWithCaptureAnimation(int offset) {
765        return mPhotoView.switchWithCaptureAnimation(offset);
766    }
767
768    @Override
769    public void setSwipingEnabled(boolean enabled) {
770        mPhotoView.setSwipingEnabled(enabled);
771    }
772
773    @Override
774    public void notifyScreenNailChanged() {
775        mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
776        mScreenNailSet.notifyChange();
777    }
778
779    @Override
780    public void addSecureAlbumItem(boolean isVideo, int id) {
781        mSecureAlbum.addMediaItem(isVideo, id);
782    }
783
784    @Override
785    protected boolean onCreateActionBar(Menu menu) {
786        mActionBar.createActionBarMenu(R.menu.photo, menu);
787        mHaveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
788        updateMenuOperations();
789        mActionBar.setTitle(mMediaSet != null ? mMediaSet.getName() : "");
790        return true;
791    }
792
793    private MenuExecutor.ProgressListener mConfirmDialogListener =
794            new MenuExecutor.ProgressListener() {
795        @Override
796        public void onProgressUpdate(int index) {}
797
798        @Override
799        public void onProgressComplete(int result) {}
800
801        @Override
802        public void onConfirmDialogShown() {
803            mHandler.removeMessages(MSG_HIDE_BARS);
804        }
805
806        @Override
807        public void onConfirmDialogDismissed(boolean confirmed) {
808            refreshHidingMessage();
809        }
810
811        @Override
812        public void onProgressStart() {}
813    };
814
815    private void switchToGrid() {
816        if (mStartedFromAlbumPage) {
817            onUpPressed();
818        } else {
819            if (mOriginalSetPathString == null) return;
820            preparePhotoFallbackView();
821            Bundle data = new Bundle(getData());
822            data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
823            data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
824                    mActivity.getDataManager().getTopSetPath(
825                            DataManager.INCLUDE_ALL));
826
827            // We only show cluster menu in the first AlbumPage in stack
828            // TODO: Enable this when running from the camera app
829            boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
830            data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum
831                    && mAppBridge == null);
832
833            data.putBoolean(PhotoPage.KEY_APP_BRIDGE, mAppBridge != null);
834
835            // Account for live preview being first item
836            mActivity.getTransitionStore().put(KEY_RETURN_INDEX_HINT,
837                    mAppBridge != null ? mCurrentIndex - 1 : mCurrentIndex);
838
839            mActivity.getStateManager().startState(AlbumPage.class, data);
840        }
841    }
842
843    @Override
844    protected boolean onItemSelected(MenuItem item) {
845        if (mModel == null) return true;
846        refreshHidingMessage();
847        MediaItem current = mModel.getMediaItem(0);
848
849        if (current == null) {
850            // item is not ready, ignore
851            return true;
852        }
853
854        int currentIndex = mModel.getCurrentIndex();
855        Path path = current.getPath();
856
857        DataManager manager = mActivity.getDataManager();
858        int action = item.getItemId();
859        String confirmMsg = null;
860        switch (action) {
861            case android.R.id.home: {
862                onUpPressed();
863                return true;
864            }
865            case R.id.action_slideshow: {
866                Bundle data = new Bundle();
867                data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
868                data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString());
869                data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
870                data.putBoolean(SlideshowPage.KEY_REPEAT, true);
871                mActivity.getStateManager().startStateForResult(
872                        SlideshowPage.class, REQUEST_SLIDESHOW, data);
873                return true;
874            }
875            case R.id.action_crop: {
876                Activity activity = mActivity;
877                Intent intent = new Intent(CropImage.CROP_ACTION);
878                intent.setClass(activity, CropImage.class);
879                intent.setData(manager.getContentUri(path));
880                activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
881                        ? REQUEST_CROP_PICASA
882                        : REQUEST_CROP);
883                return true;
884            }
885            case R.id.action_trim: {
886                Intent intent = new Intent(mActivity, TrimVideo.class);
887                intent.setData(manager.getContentUri(path));
888                // We need the file path to wrap this into a RandomAccessFile.
889                intent.putExtra(KEY_MEDIA_ITEM_PATH, current.getFilePath());
890                mActivity.startActivityForResult(intent, REQUEST_TRIM);
891                return true;
892            }
893            case R.id.action_edit: {
894                launchPhotoEditor();
895                return true;
896            }
897            case R.id.action_details: {
898                if (mShowDetails) {
899                    hideDetails();
900                } else {
901                    showDetails();
902                }
903                return true;
904            }
905            case R.id.action_delete:
906                confirmMsg = mActivity.getResources().getQuantityString(
907                        R.plurals.delete_selection, 1);
908            case R.id.action_setas:
909            case R.id.action_rotate_ccw:
910            case R.id.action_rotate_cw:
911            case R.id.action_show_on_map:
912                mSelectionManager.deSelectAll();
913                mSelectionManager.toggle(path);
914                mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
915                return true;
916            case R.id.action_import:
917                mSelectionManager.deSelectAll();
918                mSelectionManager.toggle(path);
919                mMenuExecutor.onMenuClicked(item, confirmMsg,
920                        new ImportCompleteListener(mActivity));
921                return true;
922            case R.id.action_share:
923                Activity activity = mActivity;
924                Intent intent = createShareIntent(mCurrentPhoto.getPath());
925                activity.startActivity(Intent.createChooser(intent,
926                        activity.getString(R.string.share)));
927                return true;
928            default :
929                return false;
930        }
931    }
932
933    private void hideDetails() {
934        mShowDetails = false;
935        mDetailsHelper.hide();
936    }
937
938    private void showDetails() {
939        mShowDetails = true;
940        if (mDetailsHelper == null) {
941            mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
942            mDetailsHelper.setCloseListener(new CloseListener() {
943                @Override
944                public void onClose() {
945                    hideDetails();
946                }
947            });
948        }
949        mDetailsHelper.show();
950    }
951
952    ////////////////////////////////////////////////////////////////////////////
953    //  Callbacks from PhotoView
954    ////////////////////////////////////////////////////////////////////////////
955    @Override
956    public void onSingleTapUp(int x, int y) {
957        if (mAppBridge != null) {
958            if (mAppBridge.onSingleTapUp(x, y)) return;
959        }
960
961        MediaItem item = mModel.getMediaItem(0);
962        if (item == null || item == mScreenNailItem) {
963            // item is not ready or it is camera preview, ignore
964            return;
965        }
966
967        int supported = item.getSupportedOperations();
968        boolean playVideo = (mSecureAlbum == null) &&
969                ((supported & MediaItem.SUPPORT_PLAY) != 0);
970        boolean unlock = ((supported & MediaItem.SUPPORT_UNLOCK) != 0);
971        boolean goBack = ((supported & MediaItem.SUPPORT_BACK) != 0);
972
973        if (playVideo) {
974            // determine if the point is at center (1/6) of the photo view.
975            // (The position of the "play" icon is at center (1/6) of the photo)
976            int w = mPhotoView.getWidth();
977            int h = mPhotoView.getHeight();
978            playVideo = (Math.abs(x - w / 2) * 12 <= w)
979                && (Math.abs(y - h / 2) * 12 <= h);
980        }
981
982        if (playVideo) {
983            playVideo(mActivity, item.getPlayUri(), item.getName());
984        } else if (goBack) {
985            onBackPressed();
986        } else if (unlock) {
987            mActivity.getStateManager().finishState(this);
988        } else {
989            toggleBars();
990        }
991    }
992
993    @Override
994    public void lockOrientation() {
995        mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION);
996    }
997
998    @Override
999    public void unlockOrientation() {
1000        mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION);
1001    }
1002
1003    @Override
1004    public void onActionBarAllowed(boolean allowed) {
1005        mActionBarAllowed = allowed;
1006        mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
1007    }
1008
1009    @Override
1010    public void onActionBarWanted() {
1011        mHandler.sendEmptyMessage(MSG_WANT_BARS);
1012    }
1013
1014    @Override
1015    public void onFullScreenChanged(boolean full) {
1016        Message m = mHandler.obtainMessage(
1017                MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
1018        m.sendToTarget();
1019    }
1020
1021    // How we do delete/undo:
1022    //
1023    // When the user choose to delete a media item, we just tell the
1024    // FilterDeleteSet to hide that item. If the user choose to undo it, we
1025    // again tell FilterDeleteSet not to hide it. If the user choose to commit
1026    // the deletion, we then actually delete the media item.
1027    @Override
1028    public void onDeleteImage(Path path, int offset) {
1029        onCommitDeleteImage();  // commit the previous deletion
1030        mDeletePath = path;
1031        mDeleteIsFocus = (offset == 0);
1032        mMediaSet.addDeletion(path, mCurrentIndex + offset);
1033    }
1034
1035    @Override
1036    public void onUndoDeleteImage() {
1037        if (mDeletePath == null) return;
1038        // If the deletion was done on the focused item, we want the model to
1039        // focus on it when it is undeleted.
1040        if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath);
1041        mMediaSet.removeDeletion(mDeletePath);
1042        mDeletePath = null;
1043    }
1044
1045    @Override
1046    public void onCommitDeleteImage() {
1047        if (mDeletePath == null) return;
1048        mSelectionManager.deSelectAll();
1049        mSelectionManager.toggle(mDeletePath);
1050        mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false);
1051        mDeletePath = null;
1052    }
1053
1054    public static void playVideo(Activity activity, Uri uri, String title) {
1055        try {
1056            Intent intent = new Intent(Intent.ACTION_VIEW)
1057                    .setDataAndType(uri, "video/*")
1058                    .putExtra(Intent.EXTRA_TITLE, title)
1059                    .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true);
1060            activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO);
1061        } catch (ActivityNotFoundException e) {
1062            Toast.makeText(activity, activity.getString(R.string.video_err),
1063                    Toast.LENGTH_SHORT).show();
1064        }
1065    }
1066
1067    private void setCurrentPhotoByIntent(Intent intent) {
1068        if (intent == null) return;
1069        Path path = mApplication.getDataManager()
1070                .findPathByUri(intent.getData(), intent.getType());
1071        if (path != null) {
1072            mModel.setCurrentPhoto(path, mCurrentIndex);
1073        }
1074    }
1075
1076    @Override
1077    protected void onStateResult(int requestCode, int resultCode, Intent data) {
1078        mHasActivityResult = true;
1079        switch (requestCode) {
1080            case REQUEST_EDIT:
1081                setCurrentPhotoByIntent(data);
1082                break;
1083            case REQUEST_CROP:
1084                if (resultCode == Activity.RESULT_OK) {
1085                    setCurrentPhotoByIntent(data);
1086                }
1087                break;
1088            case REQUEST_CROP_PICASA: {
1089                if (resultCode == Activity.RESULT_OK) {
1090                    Context context = mActivity.getAndroidContext();
1091                    String message = context.getString(R.string.crop_saved,
1092                            context.getString(R.string.folder_download));
1093                    Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
1094                }
1095                break;
1096            }
1097            case REQUEST_SLIDESHOW: {
1098                if (data == null) break;
1099                String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
1100                int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
1101                if (path != null) {
1102                    mModel.setCurrentPhoto(Path.fromString(path), index);
1103                }
1104            }
1105        }
1106    }
1107
1108    @Override
1109    protected void clearStateResult() {
1110        mHasActivityResult = false;
1111    }
1112
1113    private class PreparePhotoFallback implements OnGLIdleListener {
1114        private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect();
1115        private boolean mResultReady = false;
1116
1117        public synchronized PhotoFallbackEffect get() {
1118            while (!mResultReady) {
1119                Utils.waitWithoutInterrupt(this);
1120            }
1121            return mPhotoFallback;
1122        }
1123
1124        @Override
1125        public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
1126            mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas);
1127            synchronized (this) {
1128                mResultReady = true;
1129                notifyAll();
1130            }
1131            return false;
1132        }
1133    }
1134
1135    private void preparePhotoFallbackView() {
1136        GLRoot root = mActivity.getGLRoot();
1137        PreparePhotoFallback task = new PreparePhotoFallback();
1138        root.unlockRenderThread();
1139        PhotoFallbackEffect anim;
1140        try {
1141            root.addOnGLIdleListener(task);
1142            anim = task.get();
1143        } finally {
1144            root.lockRenderThread();
1145        }
1146        mActivity.getTransitionStore().put(
1147                AlbumPage.KEY_RESUME_ANIMATION, anim);
1148    }
1149
1150    @Override
1151    public void onPause() {
1152        super.onPause();
1153        mIsActive = false;
1154
1155        mActivity.getGLRoot().unfreeze();
1156        mHandler.removeMessages(MSG_UNFREEZE_GLROOT);
1157
1158        DetailsHelper.pause();
1159        if (mModel != null) {
1160            if (isFinishing()) preparePhotoFallbackView();
1161            mModel.pause();
1162        }
1163        mPhotoView.pause();
1164        mHandler.removeMessages(MSG_HIDE_BARS);
1165        mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
1166        mActionBar.disableAlbumModeMenu(true);
1167
1168        onCommitDeleteImage();
1169        mMenuExecutor.pause();
1170        if (mMediaSet != null) mMediaSet.clearDeletion();
1171    }
1172
1173    @Override
1174    public void onCurrentImageUpdated() {
1175        mActivity.getGLRoot().unfreeze();
1176    }
1177
1178    public void onFilmModeChanged(boolean enabled) {
1179        mHandler.sendEmptyMessage(MSG_REFRESH_BOTTOM_CONTROLS);
1180        if (enabled) {
1181            mHandler.removeMessages(MSG_HIDE_BARS);
1182        } else {
1183            refreshHidingMessage();
1184        }
1185    }
1186
1187    private void transitionFromAlbumPageIfNeeded() {
1188        TransitionStore transitions = mActivity.getTransitionStore();
1189
1190        int albumPageTransition = transitions.get(
1191                KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE);
1192
1193        if (albumPageTransition == MSG_ALBUMPAGE_NONE && mAppBridge != null) {
1194            // Generally, resuming the PhotoPage when in Camera should
1195            // reset to the capture mode to allow quick photo taking
1196            mCurrentIndex = 0;
1197            mPhotoView.resetToFirstPicture();
1198        } else {
1199            int resumeIndex = transitions.get(KEY_INDEX_HINT, -1);
1200            if (resumeIndex >= 0) {
1201                if (mAppBridge != null) {
1202                    // Account for live preview being the first item
1203                    resumeIndex++;
1204                }
1205                if (resumeIndex < mMediaSet.getMediaItemCount()) {
1206                    mCurrentIndex = resumeIndex;
1207                    mModel.moveTo(mCurrentIndex);
1208                }
1209            }
1210        }
1211
1212        if (albumPageTransition == MSG_ALBUMPAGE_RESUMED) {
1213            mPhotoView.setFilmMode(mStartInFilmstrip || mAppBridge != null);
1214        } else if (albumPageTransition == MSG_ALBUMPAGE_PICKED) {
1215            mPhotoView.setFilmMode(false);
1216        }
1217
1218        mFadeOutTexture = transitions.get(PreparePageFadeoutTexture.KEY_FADE_TEXTURE);
1219        if (mFadeOutTexture != null) {
1220            mBackgroundFade.start();
1221            BitmapScreenNail.disableDrawPlaceholder();
1222            mOpenAnimationRect =
1223                    albumPageTransition == MSG_ALBUMPAGE_NONE ?
1224                    (Rect) mData.getParcelable(KEY_OPEN_ANIMATION_RECT) :
1225                    (Rect) transitions.get(KEY_OPEN_ANIMATION_RECT);
1226            mPhotoView.setOpenAnimationRect(mOpenAnimationRect);
1227            mBackgroundFade.start();
1228        }
1229    }
1230
1231    @Override
1232    protected void onResume() {
1233        super.onResume();
1234
1235        if (mModel == null) {
1236            mActivity.getStateManager().finishState(this);
1237            return;
1238        }
1239        transitionFromAlbumPageIfNeeded();
1240
1241        mActivity.getGLRoot().freeze();
1242        mIsActive = true;
1243        setContentPane(mRootPane);
1244
1245        mModel.resume();
1246        mPhotoView.resume();
1247        mActionBar.setDisplayOptions(
1248                ((mSecureAlbum == null) && (mSetPathString != null)), false);
1249        mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
1250        mActionBar.enableAlbumModeMenu(GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
1251        if (!mShowBars) {
1252            mActionBar.hide();
1253            mActivity.getGLRoot().setLightsOutMode(true);
1254        }
1255        boolean haveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
1256        if (haveImageEditor != mHaveImageEditor) {
1257            mHaveImageEditor = haveImageEditor;
1258            updateMenuOperations();
1259        }
1260
1261        mHasActivityResult = false;
1262        mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
1263    }
1264
1265    @Override
1266    protected void onDestroy() {
1267        if (mAppBridge != null) {
1268            mAppBridge.setServer(null);
1269            mScreenNailItem.setScreenNail(null);
1270            mAppBridge.detachScreenNail();
1271            mAppBridge = null;
1272            mScreenNailSet = null;
1273            mScreenNailItem = null;
1274        }
1275        mOrientationManager.removeListener(this);
1276        mActivity.getGLRoot().setOrientationSource(null);
1277        if (mBottomControls != null) mBottomControls.cleanup();
1278
1279        // Remove all pending messages.
1280        mHandler.removeCallbacksAndMessages(null);
1281        super.onDestroy();
1282    }
1283
1284    private class MyDetailsSource implements DetailsSource {
1285
1286        @Override
1287        public MediaDetails getDetails() {
1288            return mModel.getMediaItem(0).getDetails();
1289        }
1290
1291        @Override
1292        public int size() {
1293            return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
1294        }
1295
1296        @Override
1297        public int setIndex() {
1298            return mModel.getCurrentIndex();
1299        }
1300    }
1301
1302    @Override
1303    public void onAlbumModeSelected(int mode) {
1304        if (mode == GalleryActionBar.ALBUM_GRID_MODE_SELECTED) {
1305            switchToGrid();
1306        }
1307    }
1308}
1309