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