2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.gallery3d.app;
19 import android.annotation.TargetApi;
20 import android.app.ActionBar.OnMenuVisibilityListener;
21 import android.app.Activity;
22 import android.content.ActivityNotFoundException;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.content.res.Configuration;
27 import android.graphics.Rect;
28 import android.net.Uri;
29 import android.nfc.NfcAdapter;
30 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
31 import android.nfc.NfcEvent;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Message;
35 import android.os.SystemClock;
36 import android.view.Menu;
37 import android.view.MenuItem;
38 import android.widget.RelativeLayout;
39 import android.widget.Toast;
41 import com.android.camera.CameraActivity;
42 import com.android.camera.ProxyLauncher;
43 import com.android.gallery3d.R;
44 import com.android.gallery3d.common.ApiHelper;
45 import com.android.gallery3d.data.ComboAlbum;
46 import com.android.gallery3d.data.DataManager;
47 import com.android.gallery3d.data.FilterDeleteSet;
48 import com.android.gallery3d.data.FilterSource;
49 import com.android.gallery3d.data.LocalImage;
50 import com.android.gallery3d.data.MediaDetails;
51 import com.android.gallery3d.data.MediaItem;
52 import com.android.gallery3d.data.MediaObject;
53 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
54 import com.android.gallery3d.data.MediaSet;
55 import com.android.gallery3d.data.Path;
56 import com.android.gallery3d.data.SecureAlbum;
57 import com.android.gallery3d.data.SecureSource;
58 import com.android.gallery3d.data.SnailAlbum;
59 import com.android.gallery3d.data.SnailItem;
60 import com.android.gallery3d.data.SnailSource;
61 import com.android.gallery3d.filtershow.FilterShowActivity;
62 import com.android.gallery3d.picasasource.PicasaSource;
63 import com.android.gallery3d.ui.DetailsHelper;
64 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
65 import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
66 import com.android.gallery3d.ui.GLView;
67 import com.android.gallery3d.ui.MenuExecutor;
68 import com.android.gallery3d.ui.PhotoView;
69 import com.android.gallery3d.ui.SelectionManager;
70 import com.android.gallery3d.ui.SynchronizedHandler;
71 import com.android.gallery3d.util.GalleryUtils;
73 public abstract class PhotoPage extends ActivityState implements
74 PhotoView.Listener, AppBridge.Server,
75 PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener {
76 private static final String TAG = "PhotoPage";
78 private static final int MSG_HIDE_BARS = 1;
79 private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
80 private static final int MSG_UPDATE_ACTION_BAR = 5;
81 private static final int MSG_UNFREEZE_GLROOT = 6;
82 private static final int MSG_WANT_BARS = 7;
83 private static final int MSG_REFRESH_BOTTOM_CONTROLS = 8;
84 private static final int MSG_ON_CAMERA_CENTER = 9;
85 private static final int MSG_ON_PICTURE_CENTER = 10;
86 private static final int MSG_REFRESH_IMAGE = 11;
87 private static final int MSG_UPDATE_PHOTO_UI = 12;
88 private static final int MSG_UPDATE_PROGRESS = 13;
89 private static final int MSG_UPDATE_DEFERRED = 14;
90 private static final int MSG_UPDATE_SHARE_URI = 15;
91 private static final int MSG_UPDATE_PANORAMA_UI = 16;
93 private static final int HIDE_BARS_TIMEOUT = 3500;
94 private static final int UNFREEZE_GLROOT_TIMEOUT = 250;
96 private static final int REQUEST_SLIDESHOW = 1;
97 private static final int REQUEST_CROP = 2;
98 private static final int REQUEST_CROP_PICASA = 3;
99 private static final int REQUEST_EDIT = 4;
100 private static final int REQUEST_PLAY_VIDEO = 5;
101 private static final int REQUEST_TRIM = 6;
103 public static final String KEY_MEDIA_SET_PATH = "media-set-path";
104 public static final String KEY_MEDIA_ITEM_PATH = "media-item-path";
105 public static final String KEY_INDEX_HINT = "index-hint";
106 public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect";
107 public static final String KEY_APP_BRIDGE = "app-bridge";
108 public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up";
109 public static final String KEY_START_IN_FILMSTRIP = "start-in-filmstrip";
110 public static final String KEY_RETURN_INDEX_HINT = "return-index-hint";
111 public static final String KEY_SHOW_WHEN_LOCKED = "show_when_locked";
112 public static final String KEY_IN_CAMERA_ROLL = "in_camera_roll";
114 public static final String KEY_ALBUMPAGE_TRANSITION = "albumpage-transition";
115 public static final int MSG_ALBUMPAGE_NONE = 0;
116 public static final int MSG_ALBUMPAGE_STARTED = 1;
117 public static final int MSG_ALBUMPAGE_RESUMED = 2;
118 public static final int MSG_ALBUMPAGE_PICKED = 4;
120 public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit";
122 private GalleryApp mApplication;
123 private SelectionManager mSelectionManager;
125 private PhotoView mPhotoView;
126 private PhotoPage.Model mModel;
127 private DetailsHelper mDetailsHelper;
128 private boolean mShowDetails;
130 // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
131 // E.g., viewing a photo in gmail attachment
132 private FilterDeleteSet mMediaSet;
134 // The mediaset used by camera launched from secure lock screen.
135 private SecureAlbum mSecureAlbum;
137 private int mCurrentIndex = 0;
138 private Handler mHandler;
139 private boolean mShowBars = true;
140 private volatile boolean mActionBarAllowed = true;
141 private GalleryActionBar mActionBar;
142 private boolean mIsMenuVisible;
143 private boolean mHaveImageEditor;
144 private PhotoPageBottomControls mBottomControls;
145 private PhotoPageProgressBar mProgressBar;
146 private MediaItem mCurrentPhoto = null;
147 private MenuExecutor mMenuExecutor;
148 private boolean mIsActive;
149 private boolean mShowSpinner;
150 private String mSetPathString;
151 // This is the original mSetPathString before adding the camera preview item.
152 private String mOriginalSetPathString;
153 private AppBridge mAppBridge;
154 private SnailItem mScreenNailItem;
155 private SnailAlbum mScreenNailSet;
156 private OrientationManager mOrientationManager;
157 private boolean mTreatBackAsUp;
158 private boolean mStartInFilmstrip;
159 private boolean mHasCameraScreennailOrPlaceholder = false;
160 private boolean mRecenterCameraOnResume = true;
162 // These are only valid after the panorama callback
163 private boolean mIsPanorama;
164 private boolean mIsPanorama360;
166 private long mCameraSwitchCutoff = 0;
167 private boolean mSkipUpdateCurrentPhoto = false;
168 private static final long CAMERA_SWITCH_CUTOFF_THRESHOLD_MS = 300;
170 private static final long DEFERRED_UPDATE_MS = 250;
171 private boolean mDeferredUpdateWaiting = false;
172 private long mDeferUpdateUntil = Long.MAX_VALUE;
174 // The item that is deleted (but it can still be undeleted before commiting)
175 private Path mDeletePath;
176 private boolean mDeleteIsFocus; // whether the deleted item was in focus
178 private Uri[] mNfcPushUris = new Uri[1];
180 private final MyMenuVisibilityListener mMenuVisibilityListener =
181 new MyMenuVisibilityListener();
182 private UpdateProgressListener mProgressListener;
184 private final PanoramaSupportCallback mUpdatePanoramaMenuItemsCallback = new PanoramaSupportCallback() {
186 public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
187 boolean isPanorama360) {
188 if (mediaObject == mCurrentPhoto) {
189 mHandler.obtainMessage(MSG_UPDATE_PANORAMA_UI, isPanorama360 ? 1 : 0, 0,
190 mediaObject).sendToTarget();
195 private final PanoramaSupportCallback mRefreshBottomControlsCallback = new PanoramaSupportCallback() {
197 public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
198 boolean isPanorama360) {
199 if (mediaObject == mCurrentPhoto) {
200 mHandler.obtainMessage(MSG_REFRESH_BOTTOM_CONTROLS, isPanorama ? 1 : 0, isPanorama360 ? 1 : 0,
201 mediaObject).sendToTarget();
206 private final PanoramaSupportCallback mUpdateShareURICallback = new PanoramaSupportCallback() {
208 public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
209 boolean isPanorama360) {
210 if (mediaObject == mCurrentPhoto) {
211 mHandler.obtainMessage(MSG_UPDATE_SHARE_URI, isPanorama360 ? 1 : 0, 0, mediaObject)
217 public static interface Model extends PhotoView.Model {
218 public void resume();
220 public boolean isEmpty();
221 public void setCurrentPhoto(Path path, int indexHint);
224 private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
226 public void onMenuVisibilityChanged(boolean isVisible) {
227 mIsMenuVisible = isVisible;
228 refreshHidingMessage();
232 private class UpdateProgressListener implements StitchingChangeListener {
235 public void onStitchingResult(Uri uri) {
236 sendUpdate(uri, MSG_REFRESH_IMAGE);
240 public void onStitchingQueued(Uri uri) {
241 sendUpdate(uri, MSG_UPDATE_PROGRESS);
245 public void onStitchingProgress(Uri uri, final int progress) {
246 sendUpdate(uri, MSG_UPDATE_PROGRESS);
249 private void sendUpdate(Uri uri, int message) {
250 MediaObject currentPhoto = mCurrentPhoto;
251 boolean isCurrentPhoto = currentPhoto instanceof LocalImage
252 && currentPhoto.getContentUri().equals(uri);
253 if (isCurrentPhoto) {
254 mHandler.sendEmptyMessage(message);
260 protected int getBackgroundColorId() {
261 return R.color.photo_background;
264 private final GLView mRootPane = new GLView() {
266 protected void onLayout(
267 boolean changed, int left, int top, int right, int bottom) {
268 mPhotoView.layout(0, 0, right - left, bottom - top);
270 mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom);
276 public void onCreate(Bundle data, Bundle restoreState) {
277 super.onCreate(data, restoreState);
278 mActionBar = mActivity.getGalleryActionBar();
279 mSelectionManager = new SelectionManager(mActivity, false);
280 mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
282 mPhotoView = new PhotoView(mActivity);
283 mPhotoView.setListener(this);
284 mRootPane.addComponent(mPhotoView);
285 mApplication = (GalleryApp) ((Activity) mActivity).getApplication();
286 mOrientationManager = mActivity.getOrientationManager();
287 mActivity.getGLRoot().setOrientationSource(mOrientationManager);
289 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
291 public void handleMessage(Message message) {
292 switch (message.what) {
293 case MSG_HIDE_BARS: {
297 case MSG_REFRESH_BOTTOM_CONTROLS: {
298 if (mCurrentPhoto == message.obj && mBottomControls != null) {
299 mIsPanorama = message.arg1 == 1;
300 mIsPanorama360 = message.arg2 == 1;
301 mBottomControls.refresh();
305 case MSG_ON_FULL_SCREEN_CHANGED: {
306 if (mAppBridge != null) {
307 mAppBridge.onFullScreenChanged(message.arg1 == 1);
311 case MSG_UPDATE_ACTION_BAR: {
315 case MSG_WANT_BARS: {
319 case MSG_UNFREEZE_GLROOT: {
320 mActivity.getGLRoot().unfreeze();
323 case MSG_UPDATE_DEFERRED: {
324 long nextUpdate = mDeferUpdateUntil - SystemClock.uptimeMillis();
325 if (nextUpdate <= 0) {
326 mDeferredUpdateWaiting = false;
327 updateUIForCurrentPhoto();
329 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_DEFERRED, nextUpdate);
333 case MSG_ON_CAMERA_CENTER: {
334 mSkipUpdateCurrentPhoto = false;
335 boolean stayedOnCamera = false;
336 if (!mPhotoView.getFilmMode()) {
337 stayedOnCamera = true;
338 } else if (SystemClock.uptimeMillis() < mCameraSwitchCutoff &&
339 mMediaSet.getMediaItemCount() > 1) {
340 mPhotoView.switchToImage(1);
342 if (mAppBridge != null) mPhotoView.setFilmMode(false);
343 stayedOnCamera = true;
346 if (stayedOnCamera) {
347 if (mAppBridge == null && mMediaSet.getTotalMediaItemCount() > 1) {
349 /* We got here by swiping from photo 1 to the
350 placeholder, so make it be the thing that
351 is in focus when the user presses back from
353 mPhotoView.switchToImage(1);
356 updateCurrentPhoto(mModel.getMediaItem(0));
361 case MSG_ON_PICTURE_CENTER: {
362 if (!mPhotoView.getFilmMode() && mCurrentPhoto != null
363 && (mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_ACTION) != 0) {
364 mPhotoView.setFilmMode(true);
368 case MSG_REFRESH_IMAGE: {
369 final MediaItem photo = mCurrentPhoto;
370 mCurrentPhoto = null;
371 updateCurrentPhoto(photo);
374 case MSG_UPDATE_PHOTO_UI: {
375 updateUIForCurrentPhoto();
378 case MSG_UPDATE_PROGRESS: {
382 case MSG_UPDATE_SHARE_URI: {
383 if (mCurrentPhoto == message.obj) {
384 boolean isPanorama360 = message.arg1 != 0;
385 Uri contentUri = mCurrentPhoto.getContentUri();
386 Intent panoramaIntent = null;
388 panoramaIntent = createSharePanoramaIntent(contentUri);
390 Intent shareIntent = createShareIntent(mCurrentPhoto);
392 mActionBar.setShareIntents(panoramaIntent, shareIntent);
393 setNfcBeamPushUri(contentUri);
397 case MSG_UPDATE_PANORAMA_UI: {
398 if (mCurrentPhoto == message.obj) {
399 boolean isPanorama360 = message.arg1 != 0;
400 updatePanoramaUI(isPanorama360);
404 default: throw new AssertionError(message.what);
409 mSetPathString = data.getString(KEY_MEDIA_SET_PATH);
410 mOriginalSetPathString = mSetPathString;
412 String itemPathString = data.getString(KEY_MEDIA_ITEM_PATH);
413 Path itemPath = itemPathString != null ?
414 Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)) :
416 mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false);
417 mStartInFilmstrip = data.getBoolean(KEY_START_IN_FILMSTRIP, false);
418 boolean inCameraRoll = data.getBoolean(KEY_IN_CAMERA_ROLL, false);
419 mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
420 if (mSetPathString != null) {
422 mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE);
423 if (mAppBridge != null) {
425 mHasCameraScreennailOrPlaceholder = true;
426 mAppBridge.setServer(this);
428 // Get the ScreenNail from AppBridge and register it.
429 int id = SnailSource.newId();
430 Path screenNailSetPath = SnailSource.getSetPath(id);
431 Path screenNailItemPath = SnailSource.getItemPath(id);
432 mScreenNailSet = (SnailAlbum) mActivity.getDataManager()
433 .getMediaObject(screenNailSetPath);
434 mScreenNailItem = (SnailItem) mActivity.getDataManager()
435 .getMediaObject(screenNailItemPath);
436 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
438 if (data.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) {
439 // Set the flag to be on top of the lock screen.
440 mFlags |= FLAG_SHOW_WHEN_LOCKED;
443 // Don't display "empty album" action item for capture intents.
444 if (!mSetPathString.equals("/local/all/0")) {
445 // Check if the path is a secure album.
446 if (SecureSource.isSecurePath(mSetPathString)) {
447 mSecureAlbum = (SecureAlbum) mActivity.getDataManager()
448 .getMediaSet(mSetPathString);
449 mShowSpinner = false;
451 mSetPathString = "/filter/empty/{"+mSetPathString+"}";
454 // Combine the original MediaSet with the one for ScreenNail
456 mSetPathString = "/combo/item/{" + screenNailSetPath +
457 "," + mSetPathString + "}";
459 // Start from the screen nail.
460 itemPath = screenNailItemPath;
461 } else if (inCameraRoll && GalleryUtils.isCameraAvailable(mActivity)) {
462 mSetPathString = "/combo/item/{" + FilterSource.FILTER_CAMERA_SHORTCUT +
463 "," + mSetPathString + "}";
465 mHasCameraScreennailOrPlaceholder = true;
468 MediaSet originalSet = mActivity.getDataManager()
469 .getMediaSet(mSetPathString);
470 if (mHasCameraScreennailOrPlaceholder && originalSet instanceof ComboAlbum) {
471 // Use the name of the camera album rather than the default
472 // ComboAlbum behavior
473 ((ComboAlbum) originalSet).useNameOfChild(1);
475 mSelectionManager.setSourceMediaSet(originalSet);
476 mSetPathString = "/filter/delete/{" + mSetPathString + "}";
477 mMediaSet = (FilterDeleteSet) mActivity.getDataManager()
478 .getMediaSet(mSetPathString);
479 if (mMediaSet == null) {
480 Log.w(TAG, "failed to restore " + mSetPathString);
482 if (itemPath == null) {
483 int mediaItemCount = mMediaSet.getMediaItemCount();
484 if (mediaItemCount > 0) {
485 if (mCurrentIndex >= mediaItemCount) mCurrentIndex = 0;
486 itemPath = mMediaSet.getMediaItem(mCurrentIndex, 1)
489 // Bail out, PhotoPage can't load on an empty album
493 PhotoDataAdapter pda = new PhotoDataAdapter(
494 mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex,
495 mAppBridge == null ? -1 : 0,
496 mAppBridge == null ? false : mAppBridge.isPanorama(),
497 mAppBridge == null ? false : mAppBridge.isStaticCamera());
499 mPhotoView.setModel(mModel);
501 pda.setDataListener(new PhotoDataAdapter.DataListener() {
504 public void onPhotoChanged(int index, Path item) {
505 int oldIndex = mCurrentIndex;
506 mCurrentIndex = index;
508 if (mHasCameraScreennailOrPlaceholder) {
509 if (mCurrentIndex > 0) {
510 mSkipUpdateCurrentPhoto = false;
513 if (oldIndex == 0 && mCurrentIndex > 0
514 && !mPhotoView.getFilmMode()) {
515 mPhotoView.setFilmMode(true);
516 } else if (oldIndex == 2 && mCurrentIndex == 1) {
517 mCameraSwitchCutoff = SystemClock.uptimeMillis() +
518 CAMERA_SWITCH_CUTOFF_THRESHOLD_MS;
519 mPhotoView.stopScrolling();
520 } else if (oldIndex >= 1 && mCurrentIndex == 0) {
521 mPhotoView.setWantPictureCenterCallbacks(true);
522 mSkipUpdateCurrentPhoto = true;
525 if (!mSkipUpdateCurrentPhoto) {
527 MediaItem photo = mModel.getMediaItem(0);
528 if (photo != null) updateCurrentPhoto(photo);
532 // Reset the timeout for the bars after a swipe
533 refreshHidingMessage();
537 public void onLoadingFinished(boolean loadingFailed) {
538 if (!mModel.isEmpty()) {
539 MediaItem photo = mModel.getMediaItem(0);
540 if (photo != null) updateCurrentPhoto(photo);
541 } else if (mIsActive) {
542 // We only want to finish the PhotoPage if there is no
543 // deletion that the user can undo.
544 if (mMediaSet.getNumberOfDeletions() == 0) {
545 mActivity.getStateManager().finishState(
552 public void onLoadingStarted() {
556 // Get default media set by the URI
557 MediaItem mediaItem = (MediaItem)
558 mActivity.getDataManager().getMediaObject(itemPath);
559 mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
560 mPhotoView.setModel(mModel);
561 updateCurrentPhoto(mediaItem);
562 mShowSpinner = false;
565 mPhotoView.setFilmMode(mStartInFilmstrip && mMediaSet.getMediaItemCount() > 1);
566 RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity)
567 .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root);
568 if (galleryRoot != null) {
569 if (mSecureAlbum == null) {
570 mBottomControls = new PhotoPageBottomControls(this, mActivity, galleryRoot);
572 StitchingProgressManager progressManager = mApplication.getStitchingProgressManager();
573 if (progressManager != null) {
574 mProgressBar = new PhotoPageProgressBar(mActivity, galleryRoot);
575 mProgressListener = new UpdateProgressListener();
576 progressManager.addChangeListener(mProgressListener);
577 if (mSecureAlbum != null) {
578 progressManager.addChangeListener(mSecureAlbum);
585 public void onPictureCenter(boolean isCamera) {
586 isCamera = isCamera || (mHasCameraScreennailOrPlaceholder && mAppBridge == null);
587 mPhotoView.setWantPictureCenterCallbacks(false);
588 mHandler.removeMessages(MSG_ON_CAMERA_CENTER);
589 mHandler.removeMessages(MSG_ON_PICTURE_CENTER);
590 mHandler.sendEmptyMessage(isCamera ? MSG_ON_CAMERA_CENTER : MSG_ON_PICTURE_CENTER);
594 public boolean canDisplayBottomControls() {
595 return mIsActive && !mPhotoView.canUndo();
599 public boolean canDisplayBottomControl(int control) {
600 if (mCurrentPhoto == null) {
604 case R.id.photopage_bottom_control_edit:
605 return mHaveImageEditor && mShowBars
606 && !mPhotoView.getFilmMode()
607 && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_EDIT) != 0
608 && mCurrentPhoto.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE;
609 case R.id.photopage_bottom_control_panorama:
611 case R.id.photopage_bottom_control_tiny_planet:
612 return mHaveImageEditor && mShowBars
613 && mIsPanorama360 && !mPhotoView.getFilmMode();
620 public void onBottomControlClicked(int control) {
622 case R.id.photopage_bottom_control_edit:
625 case R.id.photopage_bottom_control_panorama:
626 mActivity.getPanoramaViewHelper()
627 .showPanorama(mCurrentPhoto.getContentUri());
629 case R.id.photopage_bottom_control_tiny_planet:
637 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
638 private void setupNfcBeamPush() {
639 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) return;
641 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mActivity);
642 if (adapter != null) {
643 adapter.setBeamPushUris(null, mActivity);
644 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
646 public Uri[] createBeamUris(NfcEvent event) {
653 private void setNfcBeamPushUri(Uri uri) {
654 mNfcPushUris[0] = uri;
657 private static Intent createShareIntent(MediaObject mediaObject) {
658 int type = mediaObject.getMediaType();
659 return new Intent(Intent.ACTION_SEND)
660 .setType(MenuExecutor.getMimeType(type))
661 .putExtra(Intent.EXTRA_STREAM, mediaObject.getContentUri())
662 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
665 private static Intent createSharePanoramaIntent(Uri contentUri) {
666 return new Intent(Intent.ACTION_SEND)
667 .setType(GalleryUtils.MIME_TYPE_PANORAMA360)
668 .putExtra(Intent.EXTRA_STREAM, contentUri)
669 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
672 private void overrideTransitionToEditor() {
673 ((Activity) mActivity).overridePendingTransition(android.R.anim.slide_in_left,
674 android.R.anim.fade_out);
677 private void launchTinyPlanet() {
678 // Deep link into tiny planet
679 MediaItem current = mModel.getMediaItem(0);
680 Intent intent = new Intent(FilterShowActivity.TINY_PLANET_ACTION);
681 intent.setClass(mActivity, FilterShowActivity.class);
682 intent.setDataAndType(current.getContentUri(), current.getMimeType())
683 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
684 intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN,
685 mActivity.isFullscreen());
686 mActivity.startActivityForResult(intent, REQUEST_EDIT);
687 overrideTransitionToEditor();
690 private void launchCamera() {
691 Intent intent = new Intent(mActivity, CameraActivity.class)
692 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
693 mRecenterCameraOnResume = false;
694 mActivity.startActivity(intent);
697 private void launchPhotoEditor() {
698 MediaItem current = mModel.getMediaItem(0);
699 if (current == null || (current.getSupportedOperations()
700 & MediaObject.SUPPORT_EDIT) == 0) {
704 Intent intent = new Intent(ACTION_NEXTGEN_EDIT);
706 intent.setDataAndType(current.getContentUri(), current.getMimeType())
707 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
708 if (mActivity.getPackageManager()
709 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
710 intent.setAction(Intent.ACTION_EDIT);
712 intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN,
713 mActivity.isFullscreen());
714 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
716 overrideTransitionToEditor();
719 private void requestDeferredUpdate() {
720 mDeferUpdateUntil = SystemClock.uptimeMillis() + DEFERRED_UPDATE_MS;
721 if (!mDeferredUpdateWaiting) {
722 mDeferredUpdateWaiting = true;
723 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_DEFERRED, DEFERRED_UPDATE_MS);
727 private void updateUIForCurrentPhoto() {
728 if (mCurrentPhoto == null) return;
730 // If by swiping or deletion the user ends up on an action item
731 // and zoomed in, zoom out so that the context of the action is
733 if ((mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_ACTION) != 0
734 && !mPhotoView.getFilmMode()) {
735 mPhotoView.setWantPictureCenterCallbacks(true);
738 updateMenuOperations();
739 refreshBottomControlsWhenReady();
741 mDetailsHelper.reloadDetails();
743 if ((mSecureAlbum == null)
744 && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
745 mCurrentPhoto.getPanoramaSupport(mUpdateShareURICallback);
750 private void updateCurrentPhoto(MediaItem photo) {
751 if (mCurrentPhoto == photo) return;
752 mCurrentPhoto = photo;
753 if (mPhotoView.getFilmMode()) {
754 requestDeferredUpdate();
756 updateUIForCurrentPhoto();
760 private void updateProgressBar() {
761 if (mProgressBar != null) {
762 mProgressBar.hideProgress();
763 StitchingProgressManager progressManager = mApplication.getStitchingProgressManager();
764 if (progressManager != null && mCurrentPhoto instanceof LocalImage) {
765 Integer progress = progressManager.getProgress(mCurrentPhoto.getContentUri());
766 if (progress != null) {
767 mProgressBar.setProgress(progress);
773 private void updateMenuOperations() {
774 Menu menu = mActionBar.getMenu();
776 // it could be null if onCreateActionBar has not been called yet
777 if (menu == null) return;
779 MenuItem item = menu.findItem(R.id.action_slideshow);
781 item.setVisible((mSecureAlbum == null) && canDoSlideShow());
783 if (mCurrentPhoto == null) return;
785 int supportedOperations = mCurrentPhoto.getSupportedOperations();
786 if (mSecureAlbum != null) {
787 supportedOperations &= MediaObject.SUPPORT_DELETE;
788 } else if (!mHaveImageEditor) {
789 supportedOperations &= ~MediaObject.SUPPORT_EDIT;
791 MenuExecutor.updateMenuOperation(menu, supportedOperations);
792 mCurrentPhoto.getPanoramaSupport(mUpdatePanoramaMenuItemsCallback);
795 private boolean canDoSlideShow() {
796 if (mMediaSet == null || mCurrentPhoto == null) {
799 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) {
805 //////////////////////////////////////////////////////////////////////////
806 // Action Bar show/hide management
807 //////////////////////////////////////////////////////////////////////////
809 private void showBars() {
810 if (mShowBars) return;
812 mOrientationManager.unlockOrientation();
814 mActivity.getGLRoot().setLightsOutMode(false);
815 refreshHidingMessage();
816 refreshBottomControlsWhenReady();
819 private void hideBars() {
820 if (!mShowBars) return;
823 mActivity.getGLRoot().setLightsOutMode(true);
824 mHandler.removeMessages(MSG_HIDE_BARS);
825 refreshBottomControlsWhenReady();
828 private void refreshHidingMessage() {
829 mHandler.removeMessages(MSG_HIDE_BARS);
830 if (!mIsMenuVisible && !mPhotoView.getFilmMode()) {
831 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
835 private boolean canShowBars() {
836 // No bars if we are showing camera preview.
837 if (mAppBridge != null && mCurrentIndex == 0
838 && !mPhotoView.getFilmMode()) return false;
840 // No bars if it's not allowed.
841 if (!mActionBarAllowed) return false;
843 Configuration config = mActivity.getResources().getConfiguration();
844 if (config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH) {
851 private void wantBars() {
852 if (canShowBars()) showBars();
855 private void toggleBars() {
859 if (canShowBars()) showBars();
863 private void updateBars() {
864 if (!canShowBars()) {
870 protected void onBackPressed() {
873 } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) {
874 // We are leaving this page. Set the result now.
876 if (mStartInFilmstrip && !mPhotoView.getFilmMode()) {
877 mPhotoView.setFilmMode(true);
878 } else if (mTreatBackAsUp) {
881 super.onBackPressed();
886 private void onUpPressed() {
887 if ((mStartInFilmstrip || mAppBridge != null)
888 && !mPhotoView.getFilmMode()) {
889 mPhotoView.setFilmMode(true);
893 if (mActivity.getStateManager().getStateCount() > 1) {
895 super.onBackPressed();
899 if (mOriginalSetPathString == null) return;
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);
910 GalleryUtils.startGalleryActivity(mActivity);
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);
921 //////////////////////////////////////////////////////////////////////////
922 // AppBridge.Server interface
923 //////////////////////////////////////////////////////////////////////////
926 public void setCameraRelativeFrame(Rect frame) {
927 mPhotoView.setCameraRelativeFrame(frame);
931 public boolean switchWithCaptureAnimation(int offset) {
932 return mPhotoView.switchWithCaptureAnimation(offset);
936 public void setSwipingEnabled(boolean enabled) {
937 mPhotoView.setSwipingEnabled(enabled);
941 public void notifyScreenNailChanged() {
942 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
943 mScreenNailSet.notifyChange();
947 public void addSecureAlbumItem(boolean isVideo, int id) {
948 mSecureAlbum.addMediaItem(isVideo, id);
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() : "");
960 private MenuExecutor.ProgressListener mConfirmDialogListener =
961 new MenuExecutor.ProgressListener() {
963 public void onProgressUpdate(int index) {}
966 public void onProgressComplete(int result) {}
969 public void onConfirmDialogShown() {
970 mHandler.removeMessages(MSG_HIDE_BARS);
974 public void onConfirmDialogDismissed(boolean confirmed) {
975 refreshHidingMessage();
979 public void onProgressStart() {}
982 private void switchToGrid() {
983 if (mActivity.getStateManager().hasStateClass(AlbumPage.class)) {
986 if (mOriginalSetPathString == null) return;
987 if (mProgressBar != null) {
988 updateCurrentPhoto(null);
989 mProgressBar.hideProgress();
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));
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);
1003 data.putBoolean(PhotoPage.KEY_APP_BRIDGE, mAppBridge != null);
1005 // Account for live preview being first item
1006 mActivity.getTransitionStore().put(KEY_RETURN_INDEX_HINT,
1007 mAppBridge != null ? mCurrentIndex - 1 : mCurrentIndex);
1009 if (mHasCameraScreennailOrPlaceholder && mAppBridge != null) {
1010 mActivity.getStateManager().startState(AlbumPage.class, data);
1012 mActivity.getStateManager().switchState(this, AlbumPage.class, data);
1018 protected boolean onItemSelected(MenuItem item) {
1019 if (mModel == null) return true;
1020 refreshHidingMessage();
1021 MediaItem current = mModel.getMediaItem(0);
1023 // This is a shield for monkey when it clicks the action bar
1024 // menu when transitioning from filmstrip to camera
1025 if (current instanceof SnailItem) return true;
1026 // TODO: We should check the current photo against the MediaItem
1027 // that the menu was initially created for. We need to fix this
1028 // after PhotoPage being refactored.
1029 if (current == null) {
1030 // item is not ready, ignore
1033 int currentIndex = mModel.getCurrentIndex();
1034 Path path = current.getPath();
1036 DataManager manager = mActivity.getDataManager();
1037 int action = item.getItemId();
1038 String confirmMsg = null;
1040 case android.R.id.home: {
1044 case R.id.action_slideshow: {
1045 Bundle data = new Bundle();
1046 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
1047 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString());
1048 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
1049 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
1050 mActivity.getStateManager().startStateForResult(
1051 SlideshowPage.class, REQUEST_SLIDESHOW, data);
1054 case R.id.action_crop: {
1055 Activity activity = mActivity;
1056 Intent intent = new Intent(FilterShowActivity.CROP_ACTION);
1057 intent.setClass(activity, FilterShowActivity.class);
1058 intent.setDataAndType(manager.getContentUri(path), current.getMimeType())
1059 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1060 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
1061 ? REQUEST_CROP_PICASA
1065 case R.id.action_trim: {
1066 Intent intent = new Intent(mActivity, TrimVideo.class);
1067 intent.setData(manager.getContentUri(path));
1068 // We need the file path to wrap this into a RandomAccessFile.
1069 intent.putExtra(KEY_MEDIA_ITEM_PATH, current.getFilePath());
1070 mActivity.startActivityForResult(intent, REQUEST_TRIM);
1073 case R.id.action_mute: {
1074 MuteVideo muteVideo = new MuteVideo(current,
1075 manager.getContentUri(path), mActivity);
1076 muteVideo.muteInBackground();
1079 case R.id.action_edit: {
1080 launchPhotoEditor();
1083 case R.id.action_details: {
1091 case R.id.action_delete:
1092 confirmMsg = mActivity.getResources().getQuantityString(
1093 R.plurals.delete_selection, 1);
1094 case R.id.action_setas:
1095 case R.id.action_rotate_ccw:
1096 case R.id.action_rotate_cw:
1097 case R.id.action_show_on_map:
1098 mSelectionManager.deSelectAll();
1099 mSelectionManager.toggle(path);
1100 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
1107 private void hideDetails() {
1108 mShowDetails = false;
1109 mDetailsHelper.hide();
1112 private void showDetails() {
1113 mShowDetails = true;
1114 if (mDetailsHelper == null) {
1115 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
1116 mDetailsHelper.setCloseListener(new CloseListener() {
1118 public void onClose() {
1123 mDetailsHelper.show();
1126 ////////////////////////////////////////////////////////////////////////////
1127 // Callbacks from PhotoView
1128 ////////////////////////////////////////////////////////////////////////////
1130 public void onSingleTapUp(int x, int y) {
1131 if (mAppBridge != null) {
1132 if (mAppBridge.onSingleTapUp(x, y)) return;
1135 MediaItem item = mModel.getMediaItem(0);
1136 if (item == null || item == mScreenNailItem) {
1137 // item is not ready or it is camera preview, ignore
1141 int supported = item.getSupportedOperations();
1142 boolean playVideo = ((supported & MediaItem.SUPPORT_PLAY) != 0);
1143 boolean unlock = ((supported & MediaItem.SUPPORT_UNLOCK) != 0);
1144 boolean goBack = ((supported & MediaItem.SUPPORT_BACK) != 0);
1145 boolean launchCamera = ((supported & MediaItem.SUPPORT_CAMERA_SHORTCUT) != 0);
1148 // determine if the point is at center (1/6) of the photo view.
1149 // (The position of the "play" icon is at center (1/6) of the photo)
1150 int w = mPhotoView.getWidth();
1151 int h = mPhotoView.getHeight();
1152 playVideo = (Math.abs(x - w / 2) * 12 <= w)
1153 && (Math.abs(y - h / 2) * 12 <= h);
1157 if (mSecureAlbum == null) {
1158 playVideo(mActivity, item.getPlayUri(), item.getName());
1160 mActivity.getStateManager().finishState(this);
1162 } else if (goBack) {
1164 } else if (unlock) {
1165 Intent intent = new Intent(mActivity, Gallery.class);
1166 intent.putExtra(Gallery.KEY_DISMISS_KEYGUARD, true);
1167 mActivity.startActivity(intent);
1168 } else if (launchCamera) {
1176 public void onActionBarAllowed(boolean allowed) {
1177 mActionBarAllowed = allowed;
1178 mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
1182 public void onActionBarWanted() {
1183 mHandler.sendEmptyMessage(MSG_WANT_BARS);
1187 public void onFullScreenChanged(boolean full) {
1188 Message m = mHandler.obtainMessage(
1189 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
1193 // How we do delete/undo:
1195 // When the user choose to delete a media item, we just tell the
1196 // FilterDeleteSet to hide that item. If the user choose to undo it, we
1197 // again tell FilterDeleteSet not to hide it. If the user choose to commit
1198 // the deletion, we then actually delete the media item.
1200 public void onDeleteImage(Path path, int offset) {
1201 onCommitDeleteImage(); // commit the previous deletion
1203 mDeleteIsFocus = (offset == 0);
1204 mMediaSet.addDeletion(path, mCurrentIndex + offset);
1208 public void onUndoDeleteImage() {
1209 if (mDeletePath == null) return;
1210 // If the deletion was done on the focused item, we want the model to
1211 // focus on it when it is undeleted.
1212 if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath);
1213 mMediaSet.removeDeletion(mDeletePath);
1218 public void onCommitDeleteImage() {
1219 if (mDeletePath == null) return;
1220 mSelectionManager.deSelectAll();
1221 mSelectionManager.toggle(mDeletePath);
1222 mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false);
1226 public void playVideo(Activity activity, Uri uri, String title) {
1228 Intent intent = new Intent(Intent.ACTION_VIEW)
1229 .setDataAndType(uri, "video/*")
1230 .putExtra(Intent.EXTRA_TITLE, title)
1231 .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true);
1232 activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO);
1233 } catch (ActivityNotFoundException e) {
1234 Toast.makeText(activity, activity.getString(R.string.video_err),
1235 Toast.LENGTH_SHORT).show();
1239 private void setCurrentPhotoByIntent(Intent intent) {
1240 if (intent == null) return;
1241 Path path = mApplication.getDataManager()
1242 .findPathByUri(intent.getData(), intent.getType());
1244 Path albumPath = mApplication.getDataManager().getDefaultSetOf(path);
1245 if (!albumPath.equalsIgnoreCase(mOriginalSetPathString)) {
1246 // If the edited image is stored in a different album, we need
1247 // to start a new activity state to show the new image
1248 Bundle data = new Bundle(getData());
1249 data.putString(KEY_MEDIA_SET_PATH, albumPath.toString());
1250 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path.toString());
1251 mActivity.getStateManager().startState(SinglePhotoPage.class, data);
1254 mModel.setCurrentPhoto(path, mCurrentIndex);
1259 protected void onStateResult(int requestCode, int resultCode, Intent data) {
1260 if (resultCode == Activity.RESULT_CANCELED) {
1261 // This is a reset, not a canceled
1264 if (resultCode == ProxyLauncher.RESULT_USER_CANCELED) {
1265 // Unmap reset vs. canceled
1266 resultCode = Activity.RESULT_CANCELED;
1268 mRecenterCameraOnResume = false;
1269 switch (requestCode) {
1271 setCurrentPhotoByIntent(data);
1274 if (resultCode == Activity.RESULT_OK) {
1275 setCurrentPhotoByIntent(data);
1278 case REQUEST_CROP_PICASA: {
1279 if (resultCode == Activity.RESULT_OK) {
1280 Context context = mActivity.getAndroidContext();
1281 String message = context.getString(R.string.crop_saved,
1282 context.getString(R.string.folder_edited_online_photos));
1283 Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
1287 case REQUEST_SLIDESHOW: {
1288 if (data == null) break;
1289 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
1290 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
1292 mModel.setCurrentPhoto(Path.fromString(path), index);
1299 public void onPause() {
1303 mActivity.getGLRoot().unfreeze();
1304 mHandler.removeMessages(MSG_UNFREEZE_GLROOT);
1306 DetailsHelper.pause();
1307 // Hide the detail dialog on exit
1308 if (mShowDetails) hideDetails();
1309 if (mModel != null) {
1313 mHandler.removeMessages(MSG_HIDE_BARS);
1314 mHandler.removeMessages(MSG_REFRESH_BOTTOM_CONTROLS);
1315 refreshBottomControlsWhenReady();
1316 mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
1318 mActionBar.disableAlbumModeMenu(true);
1320 onCommitDeleteImage();
1321 mMenuExecutor.pause();
1322 if (mMediaSet != null) mMediaSet.clearDeletion();
1326 public void onCurrentImageUpdated() {
1327 mActivity.getGLRoot().unfreeze();
1331 public void onFilmModeChanged(boolean enabled) {
1332 refreshBottomControlsWhenReady();
1335 mActionBar.enableAlbumModeMenu(
1336 GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
1338 mActionBar.disableAlbumModeMenu(true);
1342 mHandler.removeMessages(MSG_HIDE_BARS);
1344 refreshHidingMessage();
1348 private void transitionFromAlbumPageIfNeeded() {
1349 TransitionStore transitions = mActivity.getTransitionStore();
1351 int albumPageTransition = transitions.get(
1352 KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE);
1354 if (albumPageTransition == MSG_ALBUMPAGE_NONE && mAppBridge != null
1355 && mRecenterCameraOnResume) {
1356 // Generally, resuming the PhotoPage when in Camera should
1357 // reset to the capture mode to allow quick photo taking
1359 mPhotoView.resetToFirstPicture();
1361 int resumeIndex = transitions.get(KEY_INDEX_HINT, -1);
1362 if (resumeIndex >= 0) {
1363 if (mHasCameraScreennailOrPlaceholder) {
1364 // Account for preview/placeholder being the first item
1367 if (resumeIndex < mMediaSet.getMediaItemCount()) {
1368 mCurrentIndex = resumeIndex;
1369 mModel.moveTo(mCurrentIndex);
1374 if (albumPageTransition == MSG_ALBUMPAGE_RESUMED) {
1375 mPhotoView.setFilmMode(mStartInFilmstrip || mAppBridge != null);
1376 } else if (albumPageTransition == MSG_ALBUMPAGE_PICKED) {
1377 mPhotoView.setFilmMode(false);
1382 protected void onResume() {
1385 if (mModel == null) {
1386 mActivity.getStateManager().finishState(this);
1389 transitionFromAlbumPageIfNeeded();
1391 mActivity.getGLRoot().freeze();
1393 setContentPane(mRootPane);
1396 mPhotoView.resume();
1397 mActionBar.setDisplayOptions(
1398 ((mSecureAlbum == null) && (mSetPathString != null)), false);
1399 mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
1400 refreshBottomControlsWhenReady();
1401 if (mShowSpinner && mPhotoView.getFilmMode()) {
1402 mActionBar.enableAlbumModeMenu(
1403 GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
1407 mActivity.getGLRoot().setLightsOutMode(true);
1409 boolean haveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
1410 if (haveImageEditor != mHaveImageEditor) {
1411 mHaveImageEditor = haveImageEditor;
1412 updateMenuOperations();
1415 mRecenterCameraOnResume = true;
1416 mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
1420 protected void onDestroy() {
1421 if (mAppBridge != null) {
1422 mAppBridge.setServer(null);
1423 mScreenNailItem.setScreenNail(null);
1424 mAppBridge.detachScreenNail();
1426 mScreenNailSet = null;
1427 mScreenNailItem = null;
1429 mActivity.getGLRoot().setOrientationSource(null);
1430 if (mBottomControls != null) mBottomControls.cleanup();
1432 // Remove all pending messages.
1433 mHandler.removeCallbacksAndMessages(null);
1437 private class MyDetailsSource implements DetailsSource {
1440 public MediaDetails getDetails() {
1441 return mModel.getMediaItem(0).getDetails();
1446 return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
1450 public int setIndex() {
1451 return mModel.getCurrentIndex();
1456 public void onAlbumModeSelected(int mode) {
1457 if (mode == GalleryActionBar.ALBUM_GRID_MODE_SELECTED) {
1463 public void refreshBottomControlsWhenReady() {
1464 if (mBottomControls == null) {
1467 MediaObject currentPhoto = mCurrentPhoto;
1468 if (currentPhoto == null) {
1469 mHandler.obtainMessage(MSG_REFRESH_BOTTOM_CONTROLS, 0, 0, currentPhoto).sendToTarget();
1471 currentPhoto.getPanoramaSupport(mRefreshBottomControlsCallback);
1475 private void updatePanoramaUI(boolean isPanorama360) {
1476 Menu menu = mActionBar.getMenu();
1478 // it could be null if onCreateActionBar has not been called yet
1483 MenuExecutor.updateMenuForPanorama(menu, isPanorama360, isPanorama360);
1485 if (isPanorama360) {
1486 MenuItem item = menu.findItem(R.id.action_share);
1488 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1489 item.setTitle(mActivity.getResources().getString(R.string.share_as_photo));
1491 } else if ((mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_SHARE) != 0) {
1492 MenuItem item = menu.findItem(R.id.action_share);
1494 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
1495 item.setTitle(mActivity.getResources().getString(R.string.share));
1501 public void onUndoBarVisibilityChanged(boolean visible) {
1502 refreshBottomControlsWhenReady();