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.ShareActionProvider;
40 import android.widget.Toast;
42 import com.android.gallery3d.R;
43 import com.android.gallery3d.common.ApiHelper;
44 import com.android.gallery3d.data.ComboAlbum;
45 import com.android.gallery3d.data.DataManager;
46 import com.android.gallery3d.data.FilterDeleteSet;
47 import com.android.gallery3d.data.FilterSource;
48 import com.android.gallery3d.data.LocalImage;
49 import com.android.gallery3d.data.MediaDetails;
50 import com.android.gallery3d.data.MediaItem;
51 import com.android.gallery3d.data.MediaObject;
52 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
53 import com.android.gallery3d.data.MediaSet;
54 import com.android.gallery3d.data.Path;
55 import com.android.gallery3d.data.SecureAlbum;
56 import com.android.gallery3d.data.SecureSource;
57 import com.android.gallery3d.data.SnailAlbum;
58 import com.android.gallery3d.data.SnailItem;
59 import com.android.gallery3d.data.SnailSource;
60 import com.android.gallery3d.filtershow.FilterShowActivity;
61 import com.android.gallery3d.filtershow.crop.CropActivity;
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;
72 import com.android.gallery3d.util.UsageStatistics;
74 public abstract class PhotoPage extends ActivityState implements
75 PhotoView.Listener, AppBridge.Server, ShareActionProvider.OnShareTargetSelectedListener,
76 PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener {
77 private static final String TAG = "PhotoPage";
79 private static final int MSG_HIDE_BARS = 1;
80 private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
81 private static final int MSG_UPDATE_ACTION_BAR = 5;
82 private static final int MSG_UNFREEZE_GLROOT = 6;
83 private static final int MSG_WANT_BARS = 7;
84 private static final int MSG_REFRESH_BOTTOM_CONTROLS = 8;
85 private static final int MSG_ON_CAMERA_CENTER = 9;
86 private static final int MSG_ON_PICTURE_CENTER = 10;
87 private static final int MSG_REFRESH_IMAGE = 11;
88 private static final int MSG_UPDATE_PHOTO_UI = 12;
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";
121 public static final String ACTION_SIMPLE_EDIT = "action_simple_edit";
123 private GalleryApp mApplication;
124 private SelectionManager mSelectionManager;
126 private PhotoView mPhotoView;
127 private PhotoPage.Model mModel;
128 private DetailsHelper mDetailsHelper;
129 private boolean mShowDetails;
131 // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
132 // E.g., viewing a photo in gmail attachment
133 private FilterDeleteSet mMediaSet;
135 // The mediaset used by camera launched from secure lock screen.
136 private SecureAlbum mSecureAlbum;
138 private int mCurrentIndex = 0;
139 private Handler mHandler;
140 private boolean mShowBars = true;
141 private volatile boolean mActionBarAllowed = true;
142 private GalleryActionBar mActionBar;
143 private boolean mIsMenuVisible;
144 private boolean mHaveImageEditor;
145 private PhotoPageBottomControls mBottomControls;
146 private MediaItem mCurrentPhoto = null;
147 private MenuExecutor mMenuExecutor;
148 private boolean mIsActive;
149 private 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();
183 private final PanoramaSupportCallback mUpdatePanoramaMenuItemsCallback = new PanoramaSupportCallback() {
185 public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
186 boolean isPanorama360) {
187 if (mediaObject == mCurrentPhoto) {
188 mHandler.obtainMessage(MSG_UPDATE_PANORAMA_UI, isPanorama360 ? 1 : 0, 0,
189 mediaObject).sendToTarget();
194 private final PanoramaSupportCallback mRefreshBottomControlsCallback = new PanoramaSupportCallback() {
196 public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
197 boolean isPanorama360) {
198 if (mediaObject == mCurrentPhoto) {
199 mHandler.obtainMessage(MSG_REFRESH_BOTTOM_CONTROLS, isPanorama ? 1 : 0, isPanorama360 ? 1 : 0,
200 mediaObject).sendToTarget();
205 private final PanoramaSupportCallback mUpdateShareURICallback = new PanoramaSupportCallback() {
207 public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
208 boolean isPanorama360) {
209 if (mediaObject == mCurrentPhoto) {
210 mHandler.obtainMessage(MSG_UPDATE_SHARE_URI, isPanorama360 ? 1 : 0, 0, mediaObject)
216 public static interface Model extends PhotoView.Model {
217 public void resume();
219 public boolean isEmpty();
220 public void setCurrentPhoto(Path path, int indexHint);
223 private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
225 public void onMenuVisibilityChanged(boolean isVisible) {
226 mIsMenuVisible = isVisible;
227 refreshHidingMessage();
232 protected int getBackgroundColorId() {
233 return R.color.photo_background;
236 private final GLView mRootPane = new GLView() {
238 protected void onLayout(
239 boolean changed, int left, int top, int right, int bottom) {
240 mPhotoView.layout(0, 0, right - left, bottom - top);
242 mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom);
248 public void onCreate(Bundle data, Bundle restoreState) {
249 super.onCreate(data, restoreState);
250 mActionBar = mActivity.getGalleryActionBar();
251 mSelectionManager = new SelectionManager(mActivity, false);
252 mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
254 mPhotoView = new PhotoView(mActivity);
255 mPhotoView.setListener(this);
256 mRootPane.addComponent(mPhotoView);
257 mApplication = (GalleryApp) ((Activity) mActivity).getApplication();
258 mOrientationManager = mActivity.getOrientationManager();
259 mActivity.getGLRoot().setOrientationSource(mOrientationManager);
261 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
263 public void handleMessage(Message message) {
264 switch (message.what) {
265 case MSG_HIDE_BARS: {
269 case MSG_REFRESH_BOTTOM_CONTROLS: {
270 if (mCurrentPhoto == message.obj && mBottomControls != null) {
271 mIsPanorama = message.arg1 == 1;
272 mIsPanorama360 = message.arg2 == 1;
273 mBottomControls.refresh();
277 case MSG_ON_FULL_SCREEN_CHANGED: {
278 if (mAppBridge != null) {
279 mAppBridge.onFullScreenChanged(message.arg1 == 1);
283 case MSG_UPDATE_ACTION_BAR: {
287 case MSG_WANT_BARS: {
291 case MSG_UNFREEZE_GLROOT: {
292 mActivity.getGLRoot().unfreeze();
295 case MSG_UPDATE_DEFERRED: {
296 long nextUpdate = mDeferUpdateUntil - SystemClock.uptimeMillis();
297 if (nextUpdate <= 0) {
298 mDeferredUpdateWaiting = false;
299 updateUIForCurrentPhoto();
301 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_DEFERRED, nextUpdate);
305 case MSG_ON_CAMERA_CENTER: {
306 mSkipUpdateCurrentPhoto = false;
307 boolean stayedOnCamera = false;
308 if (!mPhotoView.getFilmMode()) {
309 stayedOnCamera = true;
310 } else if (SystemClock.uptimeMillis() < mCameraSwitchCutoff &&
311 mMediaSet.getMediaItemCount() > 1) {
312 mPhotoView.switchToImage(1);
314 if (mAppBridge != null) mPhotoView.setFilmMode(false);
315 stayedOnCamera = true;
318 if (stayedOnCamera) {
319 if (mAppBridge == null && mMediaSet.getTotalMediaItemCount() > 1) {
321 /* We got here by swiping from photo 1 to the
322 placeholder, so make it be the thing that
323 is in focus when the user presses back from
325 mPhotoView.switchToImage(1);
328 updateCurrentPhoto(mModel.getMediaItem(0));
333 case MSG_ON_PICTURE_CENTER: {
334 if (!mPhotoView.getFilmMode() && mCurrentPhoto != null
335 && (mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_ACTION) != 0) {
336 mPhotoView.setFilmMode(true);
340 case MSG_REFRESH_IMAGE: {
341 final MediaItem photo = mCurrentPhoto;
342 mCurrentPhoto = null;
343 updateCurrentPhoto(photo);
346 case MSG_UPDATE_PHOTO_UI: {
347 updateUIForCurrentPhoto();
350 case MSG_UPDATE_SHARE_URI: {
351 if (mCurrentPhoto == message.obj) {
352 boolean isPanorama360 = message.arg1 != 0;
353 Uri contentUri = mCurrentPhoto.getContentUri();
354 Intent panoramaIntent = null;
356 panoramaIntent = createSharePanoramaIntent(contentUri);
358 Intent shareIntent = createShareIntent(mCurrentPhoto);
360 mActionBar.setShareIntents(panoramaIntent, shareIntent, PhotoPage.this);
361 setNfcBeamPushUri(contentUri);
365 case MSG_UPDATE_PANORAMA_UI: {
366 if (mCurrentPhoto == message.obj) {
367 boolean isPanorama360 = message.arg1 != 0;
368 updatePanoramaUI(isPanorama360);
372 default: throw new AssertionError(message.what);
377 mSetPathString = data.getString(KEY_MEDIA_SET_PATH);
378 mOriginalSetPathString = mSetPathString;
380 String itemPathString = data.getString(KEY_MEDIA_ITEM_PATH);
381 Path itemPath = itemPathString != null ?
382 Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)) :
384 mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false);
385 mStartInFilmstrip = data.getBoolean(KEY_START_IN_FILMSTRIP, false);
386 boolean inCameraRoll = data.getBoolean(KEY_IN_CAMERA_ROLL, false);
387 mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
388 if (mSetPathString != null) {
390 mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE);
391 if (mAppBridge != null) {
393 mHasCameraScreennailOrPlaceholder = true;
394 mAppBridge.setServer(this);
396 // Get the ScreenNail from AppBridge and register it.
397 int id = SnailSource.newId();
398 Path screenNailSetPath = SnailSource.getSetPath(id);
399 Path screenNailItemPath = SnailSource.getItemPath(id);
400 mScreenNailSet = (SnailAlbum) mActivity.getDataManager()
401 .getMediaObject(screenNailSetPath);
402 mScreenNailItem = (SnailItem) mActivity.getDataManager()
403 .getMediaObject(screenNailItemPath);
404 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
406 if (data.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) {
407 // Set the flag to be on top of the lock screen.
408 mFlags |= FLAG_SHOW_WHEN_LOCKED;
411 // Don't display "empty album" action item for capture intents.
412 if (!mSetPathString.equals("/local/all/0")) {
413 // Check if the path is a secure album.
414 if (SecureSource.isSecurePath(mSetPathString)) {
415 mSecureAlbum = (SecureAlbum) mActivity.getDataManager()
416 .getMediaSet(mSetPathString);
417 mShowSpinner = false;
419 mSetPathString = "/filter/empty/{"+mSetPathString+"}";
422 // Combine the original MediaSet with the one for ScreenNail
424 mSetPathString = "/combo/item/{" + screenNailSetPath +
425 "," + mSetPathString + "}";
427 // Start from the screen nail.
428 itemPath = screenNailItemPath;
429 } else if (inCameraRoll && GalleryUtils.isCameraAvailable(mActivity)) {
430 mSetPathString = "/combo/item/{" + FilterSource.FILTER_CAMERA_SHORTCUT +
431 "," + mSetPathString + "}";
433 mHasCameraScreennailOrPlaceholder = true;
436 MediaSet originalSet = mActivity.getDataManager()
437 .getMediaSet(mSetPathString);
438 if (mHasCameraScreennailOrPlaceholder && originalSet instanceof ComboAlbum) {
439 // Use the name of the camera album rather than the default
440 // ComboAlbum behavior
441 ((ComboAlbum) originalSet).useNameOfChild(1);
443 mSelectionManager.setSourceMediaSet(originalSet);
444 mSetPathString = "/filter/delete/{" + mSetPathString + "}";
445 mMediaSet = (FilterDeleteSet) mActivity.getDataManager()
446 .getMediaSet(mSetPathString);
447 if (mMediaSet == null) {
448 Log.w(TAG, "failed to restore " + mSetPathString);
450 if (itemPath == null) {
451 int mediaItemCount = mMediaSet.getMediaItemCount();
452 if (mediaItemCount > 0) {
453 if (mCurrentIndex >= mediaItemCount) mCurrentIndex = 0;
454 itemPath = mMediaSet.getMediaItem(mCurrentIndex, 1)
457 // Bail out, PhotoPage can't load on an empty album
461 PhotoDataAdapter pda = new PhotoDataAdapter(
462 mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex,
463 mAppBridge == null ? -1 : 0,
464 mAppBridge == null ? false : mAppBridge.isPanorama(),
465 mAppBridge == null ? false : mAppBridge.isStaticCamera());
467 mPhotoView.setModel(mModel);
469 pda.setDataListener(new PhotoDataAdapter.DataListener() {
472 public void onPhotoChanged(int index, Path item) {
473 int oldIndex = mCurrentIndex;
474 mCurrentIndex = index;
476 if (mHasCameraScreennailOrPlaceholder) {
477 if (mCurrentIndex > 0) {
478 mSkipUpdateCurrentPhoto = false;
481 if (oldIndex == 0 && mCurrentIndex > 0
482 && !mPhotoView.getFilmMode()) {
483 mPhotoView.setFilmMode(true);
484 if (mAppBridge != null) {
485 UsageStatistics.onEvent("CameraToFilmstrip",
486 UsageStatistics.TRANSITION_SWIPE, null);
488 } else if (oldIndex == 2 && mCurrentIndex == 1) {
489 mCameraSwitchCutoff = SystemClock.uptimeMillis() +
490 CAMERA_SWITCH_CUTOFF_THRESHOLD_MS;
491 mPhotoView.stopScrolling();
492 } else if (oldIndex >= 1 && mCurrentIndex == 0) {
493 mPhotoView.setWantPictureCenterCallbacks(true);
494 mSkipUpdateCurrentPhoto = true;
497 if (!mSkipUpdateCurrentPhoto) {
499 MediaItem photo = mModel.getMediaItem(0);
500 if (photo != null) updateCurrentPhoto(photo);
504 // Reset the timeout for the bars after a swipe
505 refreshHidingMessage();
509 public void onLoadingFinished(boolean loadingFailed) {
510 if (!mModel.isEmpty()) {
511 MediaItem photo = mModel.getMediaItem(0);
512 if (photo != null) updateCurrentPhoto(photo);
513 } else if (mIsActive) {
514 // We only want to finish the PhotoPage if there is no
515 // deletion that the user can undo.
516 if (mMediaSet.getNumberOfDeletions() == 0) {
517 mActivity.getStateManager().finishState(
524 public void onLoadingStarted() {
528 // Get default media set by the URI
529 MediaItem mediaItem = (MediaItem)
530 mActivity.getDataManager().getMediaObject(itemPath);
531 mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
532 mPhotoView.setModel(mModel);
533 updateCurrentPhoto(mediaItem);
534 mShowSpinner = false;
537 mPhotoView.setFilmMode(mStartInFilmstrip && mMediaSet.getMediaItemCount() > 1);
538 RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity)
539 .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root);
540 if (galleryRoot != null) {
541 if (mSecureAlbum == null) {
542 mBottomControls = new PhotoPageBottomControls(this, mActivity, galleryRoot);
548 public void onPictureCenter(boolean isCamera) {
549 isCamera = isCamera || (mHasCameraScreennailOrPlaceholder && mAppBridge == null);
550 mPhotoView.setWantPictureCenterCallbacks(false);
551 mHandler.removeMessages(MSG_ON_CAMERA_CENTER);
552 mHandler.removeMessages(MSG_ON_PICTURE_CENTER);
553 mHandler.sendEmptyMessage(isCamera ? MSG_ON_CAMERA_CENTER : MSG_ON_PICTURE_CENTER);
557 public boolean canDisplayBottomControls() {
558 return mIsActive && !mPhotoView.canUndo();
562 public boolean canDisplayBottomControl(int control) {
563 if (mCurrentPhoto == null) {
567 case R.id.photopage_bottom_control_edit:
568 return mHaveImageEditor && mShowBars
569 && !mPhotoView.getFilmMode()
570 && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_EDIT) != 0
571 && mCurrentPhoto.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE;
572 case R.id.photopage_bottom_control_panorama:
574 case R.id.photopage_bottom_control_tiny_planet:
575 return mHaveImageEditor && mShowBars
576 && mIsPanorama360 && !mPhotoView.getFilmMode();
583 public void onBottomControlClicked(int control) {
585 case R.id.photopage_bottom_control_edit:
588 case R.id.photopage_bottom_control_panorama:
589 mActivity.getPanoramaViewHelper()
590 .showPanorama(mCurrentPhoto.getContentUri());
592 case R.id.photopage_bottom_control_tiny_planet:
600 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
601 private void setupNfcBeamPush() {
602 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) return;
604 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mActivity);
605 if (adapter != null) {
606 adapter.setBeamPushUris(null, mActivity);
607 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
609 public Uri[] createBeamUris(NfcEvent event) {
616 private void setNfcBeamPushUri(Uri uri) {
617 mNfcPushUris[0] = uri;
620 private static Intent createShareIntent(MediaObject mediaObject) {
621 int type = mediaObject.getMediaType();
622 return new Intent(Intent.ACTION_SEND)
623 .setType(MenuExecutor.getMimeType(type))
624 .putExtra(Intent.EXTRA_STREAM, mediaObject.getContentUri())
625 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
628 private static Intent createSharePanoramaIntent(Uri contentUri) {
629 return new Intent(Intent.ACTION_SEND)
630 .setType(GalleryUtils.MIME_TYPE_PANORAMA360)
631 .putExtra(Intent.EXTRA_STREAM, contentUri)
632 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
635 private void overrideTransitionToEditor() {
636 ((Activity) mActivity).overridePendingTransition(android.R.anim.fade_in,
637 android.R.anim.fade_out);
640 private void launchTinyPlanet() {
641 // Deep link into tiny planet
642 MediaItem current = mModel.getMediaItem(0);
643 Intent intent = new Intent(FilterShowActivity.TINY_PLANET_ACTION);
644 intent.setClass(mActivity, FilterShowActivity.class);
645 intent.setDataAndType(current.getContentUri(), current.getMimeType())
646 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
647 intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN,
648 mActivity.isFullscreen());
649 mActivity.startActivityForResult(intent, REQUEST_EDIT);
650 overrideTransitionToEditor();
653 private void launchCamera() {
654 mRecenterCameraOnResume = false;
655 GalleryUtils.startCameraActivity(mActivity);
658 private void launchPhotoEditor() {
659 MediaItem current = mModel.getMediaItem(0);
660 if (current == null || (current.getSupportedOperations()
661 & MediaObject.SUPPORT_EDIT) == 0) {
665 Intent intent = new Intent(ACTION_NEXTGEN_EDIT);
667 intent.setDataAndType(current.getContentUri(), current.getMimeType())
668 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
669 if (mActivity.getPackageManager()
670 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
671 intent.setAction(Intent.ACTION_EDIT);
673 intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN,
674 mActivity.isFullscreen());
675 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
677 overrideTransitionToEditor();
680 private void launchSimpleEditor() {
681 MediaItem current = mModel.getMediaItem(0);
682 if (current == null || (current.getSupportedOperations()
683 & MediaObject.SUPPORT_EDIT) == 0) {
687 Intent intent = new Intent(ACTION_SIMPLE_EDIT);
689 intent.setDataAndType(current.getContentUri(), current.getMimeType())
690 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
691 if (mActivity.getPackageManager()
692 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
693 intent.setAction(Intent.ACTION_EDIT);
695 intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN,
696 mActivity.isFullscreen());
697 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
699 overrideTransitionToEditor();
702 private void requestDeferredUpdate() {
703 mDeferUpdateUntil = SystemClock.uptimeMillis() + DEFERRED_UPDATE_MS;
704 if (!mDeferredUpdateWaiting) {
705 mDeferredUpdateWaiting = true;
706 mHandler.sendEmptyMessageDelayed(MSG_UPDATE_DEFERRED, DEFERRED_UPDATE_MS);
710 private void updateUIForCurrentPhoto() {
711 if (mCurrentPhoto == null) return;
713 // If by swiping or deletion the user ends up on an action item
714 // and zoomed in, zoom out so that the context of the action is
716 if ((mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_ACTION) != 0
717 && !mPhotoView.getFilmMode()) {
718 mPhotoView.setWantPictureCenterCallbacks(true);
721 updateMenuOperations();
722 refreshBottomControlsWhenReady();
724 mDetailsHelper.reloadDetails();
726 if ((mSecureAlbum == null)
727 && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
728 mCurrentPhoto.getPanoramaSupport(mUpdateShareURICallback);
732 private void updateCurrentPhoto(MediaItem photo) {
733 if (mCurrentPhoto == photo) return;
734 mCurrentPhoto = photo;
735 if (mPhotoView.getFilmMode()) {
736 requestDeferredUpdate();
738 updateUIForCurrentPhoto();
742 private void updateMenuOperations() {
743 Menu menu = mActionBar.getMenu();
745 // it could be null if onCreateActionBar has not been called yet
746 if (menu == null) return;
748 MenuItem item = menu.findItem(R.id.action_slideshow);
750 item.setVisible((mSecureAlbum == null) && canDoSlideShow());
752 if (mCurrentPhoto == null) return;
754 int supportedOperations = mCurrentPhoto.getSupportedOperations();
755 if (mSecureAlbum != null) {
756 supportedOperations &= MediaObject.SUPPORT_DELETE;
758 mCurrentPhoto.getPanoramaSupport(mUpdatePanoramaMenuItemsCallback);
759 if (!mHaveImageEditor) {
760 supportedOperations &= ~MediaObject.SUPPORT_EDIT;
763 MenuExecutor.updateMenuOperation(menu, supportedOperations);
766 private boolean canDoSlideShow() {
767 if (mMediaSet == null || mCurrentPhoto == null) {
770 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) {
776 //////////////////////////////////////////////////////////////////////////
777 // Action Bar show/hide management
778 //////////////////////////////////////////////////////////////////////////
780 private void showBars() {
781 if (mShowBars) return;
783 mOrientationManager.unlockOrientation();
785 mActivity.getGLRoot().setLightsOutMode(false);
786 refreshHidingMessage();
787 refreshBottomControlsWhenReady();
790 private void hideBars() {
791 if (!mShowBars) return;
794 mActivity.getGLRoot().setLightsOutMode(true);
795 mHandler.removeMessages(MSG_HIDE_BARS);
796 refreshBottomControlsWhenReady();
799 private void refreshHidingMessage() {
800 mHandler.removeMessages(MSG_HIDE_BARS);
801 if (!mIsMenuVisible && !mPhotoView.getFilmMode()) {
802 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
806 private boolean canShowBars() {
807 // No bars if we are showing camera preview.
808 if (mAppBridge != null && mCurrentIndex == 0
809 && !mPhotoView.getFilmMode()) return false;
811 // No bars if it's not allowed.
812 if (!mActionBarAllowed) return false;
814 Configuration config = mActivity.getResources().getConfiguration();
815 if (config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH) {
822 private void wantBars() {
823 if (canShowBars()) showBars();
826 private void toggleBars() {
830 if (canShowBars()) showBars();
834 private void updateBars() {
835 if (!canShowBars()) {
841 protected void onBackPressed() {
844 } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) {
845 // We are leaving this page. Set the result now.
847 if (mStartInFilmstrip && !mPhotoView.getFilmMode()) {
848 mPhotoView.setFilmMode(true);
849 } else if (mTreatBackAsUp) {
852 super.onBackPressed();
857 private void onUpPressed() {
858 if ((mStartInFilmstrip || mAppBridge != null)
859 && !mPhotoView.getFilmMode()) {
860 mPhotoView.setFilmMode(true);
864 if (mActivity.getStateManager().getStateCount() > 1) {
866 super.onBackPressed();
870 if (mOriginalSetPathString == null) return;
872 if (mAppBridge == null) {
873 // We're in view mode so set up the stacks on our own.
874 Bundle data = new Bundle(getData());
875 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
876 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
877 mActivity.getDataManager().getTopSetPath(
878 DataManager.INCLUDE_ALL));
879 mActivity.getStateManager().switchState(this, AlbumPage.class, data);
881 GalleryUtils.startGalleryActivity(mActivity);
885 private void setResult() {
886 Intent result = null;
887 result = new Intent();
888 result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex);
889 setStateResult(Activity.RESULT_OK, result);
892 //////////////////////////////////////////////////////////////////////////
893 // AppBridge.Server interface
894 //////////////////////////////////////////////////////////////////////////
897 public void setCameraRelativeFrame(Rect frame) {
898 mPhotoView.setCameraRelativeFrame(frame);
902 public boolean switchWithCaptureAnimation(int offset) {
903 return mPhotoView.switchWithCaptureAnimation(offset);
907 public void setSwipingEnabled(boolean enabled) {
908 mPhotoView.setSwipingEnabled(enabled);
912 public void notifyScreenNailChanged() {
913 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
914 mScreenNailSet.notifyChange();
918 public void addSecureAlbumItem(boolean isVideo, int id) {
919 mSecureAlbum.addMediaItem(isVideo, id);
923 protected boolean onCreateActionBar(Menu menu) {
924 mActionBar.createActionBarMenu(R.menu.photo, menu);
925 mHaveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
926 updateMenuOperations();
927 mActionBar.setTitle(mMediaSet != null ? mMediaSet.getName() : "");
931 private MenuExecutor.ProgressListener mConfirmDialogListener =
932 new MenuExecutor.ProgressListener() {
934 public void onProgressUpdate(int index) {}
937 public void onProgressComplete(int result) {}
940 public void onConfirmDialogShown() {
941 mHandler.removeMessages(MSG_HIDE_BARS);
945 public void onConfirmDialogDismissed(boolean confirmed) {
946 refreshHidingMessage();
950 public void onProgressStart() {}
953 private void switchToGrid() {
954 if (mActivity.getStateManager().hasStateClass(AlbumPage.class)) {
957 if (mOriginalSetPathString == null) return;
958 Bundle data = new Bundle(getData());
959 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
960 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
961 mActivity.getDataManager().getTopSetPath(
962 DataManager.INCLUDE_ALL));
964 // We only show cluster menu in the first AlbumPage in stack
965 // TODO: Enable this when running from the camera app
966 boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
967 data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum
968 && mAppBridge == null);
970 data.putBoolean(PhotoPage.KEY_APP_BRIDGE, mAppBridge != null);
972 // Account for live preview being first item
973 mActivity.getTransitionStore().put(KEY_RETURN_INDEX_HINT,
974 mAppBridge != null ? mCurrentIndex - 1 : mCurrentIndex);
976 if (mHasCameraScreennailOrPlaceholder && mAppBridge != null) {
977 mActivity.getStateManager().startState(AlbumPage.class, data);
979 mActivity.getStateManager().switchState(this, AlbumPage.class, data);
985 protected boolean onItemSelected(MenuItem item) {
986 if (mModel == null) return true;
987 refreshHidingMessage();
988 MediaItem current = mModel.getMediaItem(0);
990 // This is a shield for monkey when it clicks the action bar
991 // menu when transitioning from filmstrip to camera
992 if (current instanceof SnailItem) return true;
993 // TODO: We should check the current photo against the MediaItem
994 // that the menu was initially created for. We need to fix this
995 // after PhotoPage being refactored.
996 if (current == null) {
997 // item is not ready, ignore
1000 int currentIndex = mModel.getCurrentIndex();
1001 Path path = current.getPath();
1003 DataManager manager = mActivity.getDataManager();
1004 int action = item.getItemId();
1005 String confirmMsg = null;
1007 case android.R.id.home: {
1011 case R.id.action_slideshow: {
1012 Bundle data = new Bundle();
1013 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
1014 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString());
1015 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
1016 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
1017 mActivity.getStateManager().startStateForResult(
1018 SlideshowPage.class, REQUEST_SLIDESHOW, data);
1021 case R.id.action_crop: {
1022 Activity activity = mActivity;
1023 Intent intent = new Intent(CropActivity.CROP_ACTION);
1024 intent.setClass(activity, CropActivity.class);
1025 intent.setDataAndType(manager.getContentUri(path), current.getMimeType())
1026 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1027 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
1028 ? REQUEST_CROP_PICASA
1032 case R.id.action_trim: {
1033 Intent intent = new Intent(mActivity, TrimVideo.class);
1034 intent.setData(manager.getContentUri(path));
1035 // We need the file path to wrap this into a RandomAccessFile.
1036 intent.putExtra(KEY_MEDIA_ITEM_PATH, current.getFilePath());
1037 mActivity.startActivityForResult(intent, REQUEST_TRIM);
1040 case R.id.action_mute: {
1041 MuteVideo muteVideo = new MuteVideo(current.getFilePath(),
1042 manager.getContentUri(path), mActivity);
1043 muteVideo.muteInBackground();
1046 case R.id.action_edit: {
1047 launchPhotoEditor();
1050 case R.id.action_simple_edit: {
1051 launchSimpleEditor();
1054 case R.id.action_details: {
1063 mActivity.printSelectedImage(manager.getContentUri(path));
1066 case R.id.action_delete:
1067 confirmMsg = mActivity.getResources().getQuantityString(
1068 R.plurals.delete_selection, 1);
1069 case R.id.action_setas:
1070 case R.id.action_rotate_ccw:
1071 case R.id.action_rotate_cw:
1072 case R.id.action_show_on_map:
1073 mSelectionManager.deSelectAll();
1074 mSelectionManager.toggle(path);
1075 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
1082 private void hideDetails() {
1083 mShowDetails = false;
1084 mDetailsHelper.hide();
1087 private void showDetails() {
1088 mShowDetails = true;
1089 if (mDetailsHelper == null) {
1090 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
1091 mDetailsHelper.setCloseListener(new CloseListener() {
1093 public void onClose() {
1098 mDetailsHelper.show();
1101 ////////////////////////////////////////////////////////////////////////////
1102 // Callbacks from PhotoView
1103 ////////////////////////////////////////////////////////////////////////////
1105 public void onSingleTapUp(int x, int y) {
1106 if (mAppBridge != null) {
1107 if (mAppBridge.onSingleTapUp(x, y)) return;
1110 MediaItem item = mModel.getMediaItem(0);
1111 if (item == null || item == mScreenNailItem) {
1112 // item is not ready or it is camera preview, ignore
1116 int supported = item.getSupportedOperations();
1117 boolean playVideo = ((supported & MediaItem.SUPPORT_PLAY) != 0);
1118 boolean unlock = ((supported & MediaItem.SUPPORT_UNLOCK) != 0);
1119 boolean goBack = ((supported & MediaItem.SUPPORT_BACK) != 0);
1120 boolean launchCamera = ((supported & MediaItem.SUPPORT_CAMERA_SHORTCUT) != 0);
1123 // determine if the point is at center (1/6) of the photo view.
1124 // (The position of the "play" icon is at center (1/6) of the photo)
1125 int w = mPhotoView.getWidth();
1126 int h = mPhotoView.getHeight();
1127 playVideo = (Math.abs(x - w / 2) * 12 <= w)
1128 && (Math.abs(y - h / 2) * 12 <= h);
1132 if (mSecureAlbum == null) {
1133 playVideo(mActivity, item.getPlayUri(), item.getName());
1135 mActivity.getStateManager().finishState(this);
1137 } else if (goBack) {
1139 } else if (unlock) {
1140 Intent intent = new Intent(mActivity, Gallery.class);
1141 intent.putExtra(Gallery.KEY_DISMISS_KEYGUARD, true);
1142 mActivity.startActivity(intent);
1143 } else if (launchCamera) {
1151 public void onActionBarAllowed(boolean allowed) {
1152 mActionBarAllowed = allowed;
1153 mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
1157 public void onActionBarWanted() {
1158 mHandler.sendEmptyMessage(MSG_WANT_BARS);
1162 public void onFullScreenChanged(boolean full) {
1163 Message m = mHandler.obtainMessage(
1164 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
1168 // How we do delete/undo:
1170 // When the user choose to delete a media item, we just tell the
1171 // FilterDeleteSet to hide that item. If the user choose to undo it, we
1172 // again tell FilterDeleteSet not to hide it. If the user choose to commit
1173 // the deletion, we then actually delete the media item.
1175 public void onDeleteImage(Path path, int offset) {
1176 onCommitDeleteImage(); // commit the previous deletion
1178 mDeleteIsFocus = (offset == 0);
1179 mMediaSet.addDeletion(path, mCurrentIndex + offset);
1183 public void onUndoDeleteImage() {
1184 if (mDeletePath == null) return;
1185 // If the deletion was done on the focused item, we want the model to
1186 // focus on it when it is undeleted.
1187 if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath);
1188 mMediaSet.removeDeletion(mDeletePath);
1193 public void onCommitDeleteImage() {
1194 if (mDeletePath == null) return;
1195 mMenuExecutor.startSingleItemAction(R.id.action_delete, mDeletePath);
1199 public void playVideo(Activity activity, Uri uri, String title) {
1201 Intent intent = new Intent(Intent.ACTION_VIEW)
1202 .setDataAndType(uri, "video/*")
1203 .putExtra(Intent.EXTRA_TITLE, title)
1204 .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true);
1205 activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO);
1206 } catch (ActivityNotFoundException e) {
1207 Toast.makeText(activity, activity.getString(R.string.video_err),
1208 Toast.LENGTH_SHORT).show();
1212 private void setCurrentPhotoByIntent(Intent intent) {
1213 if (intent == null) return;
1214 Path path = mApplication.getDataManager()
1215 .findPathByUri(intent.getData(), intent.getType());
1217 Path albumPath = mApplication.getDataManager().getDefaultSetOf(path);
1218 if (!albumPath.equalsIgnoreCase(mOriginalSetPathString)) {
1219 // If the edited image is stored in a different album, we need
1220 // to start a new activity state to show the new image
1221 Bundle data = new Bundle(getData());
1222 data.putString(KEY_MEDIA_SET_PATH, albumPath.toString());
1223 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path.toString());
1224 mActivity.getStateManager().startState(SinglePhotoPage.class, data);
1227 mModel.setCurrentPhoto(path, mCurrentIndex);
1232 protected void onStateResult(int requestCode, int resultCode, Intent data) {
1233 if (resultCode == Activity.RESULT_CANCELED) {
1234 // This is a reset, not a canceled
1237 mRecenterCameraOnResume = false;
1238 switch (requestCode) {
1240 setCurrentPhotoByIntent(data);
1243 if (resultCode == Activity.RESULT_OK) {
1244 setCurrentPhotoByIntent(data);
1247 case REQUEST_CROP_PICASA: {
1248 if (resultCode == Activity.RESULT_OK) {
1249 Context context = mActivity.getAndroidContext();
1250 String message = context.getString(R.string.crop_saved,
1251 context.getString(R.string.folder_edited_online_photos));
1252 Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
1256 case REQUEST_SLIDESHOW: {
1257 if (data == null) break;
1258 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
1259 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
1261 mModel.setCurrentPhoto(Path.fromString(path), index);
1268 public void onPause() {
1272 mActivity.getGLRoot().unfreeze();
1273 mHandler.removeMessages(MSG_UNFREEZE_GLROOT);
1275 DetailsHelper.pause();
1276 // Hide the detail dialog on exit
1277 if (mShowDetails) hideDetails();
1278 if (mModel != null) {
1282 mHandler.removeMessages(MSG_HIDE_BARS);
1283 mHandler.removeMessages(MSG_REFRESH_BOTTOM_CONTROLS);
1284 refreshBottomControlsWhenReady();
1285 mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
1287 mActionBar.disableAlbumModeMenu(true);
1289 onCommitDeleteImage();
1290 mMenuExecutor.pause();
1291 if (mMediaSet != null) mMediaSet.clearDeletion();
1295 public void onCurrentImageUpdated() {
1296 mActivity.getGLRoot().unfreeze();
1300 public void onFilmModeChanged(boolean enabled) {
1301 refreshBottomControlsWhenReady();
1304 mActionBar.enableAlbumModeMenu(
1305 GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
1307 mActionBar.disableAlbumModeMenu(true);
1311 mHandler.removeMessages(MSG_HIDE_BARS);
1312 UsageStatistics.onContentViewChanged(
1313 UsageStatistics.COMPONENT_GALLERY, "FilmstripPage");
1315 refreshHidingMessage();
1316 if (mAppBridge == null || mCurrentIndex > 0) {
1317 UsageStatistics.onContentViewChanged(
1318 UsageStatistics.COMPONENT_GALLERY, "SinglePhotoPage");
1320 UsageStatistics.onContentViewChanged(
1321 UsageStatistics.COMPONENT_CAMERA, "Unknown"); // TODO
1326 private void transitionFromAlbumPageIfNeeded() {
1327 TransitionStore transitions = mActivity.getTransitionStore();
1329 int albumPageTransition = transitions.get(
1330 KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE);
1332 if (albumPageTransition == MSG_ALBUMPAGE_NONE && mAppBridge != null
1333 && mRecenterCameraOnResume) {
1334 // Generally, resuming the PhotoPage when in Camera should
1335 // reset to the capture mode to allow quick photo taking
1337 mPhotoView.resetToFirstPicture();
1339 int resumeIndex = transitions.get(KEY_INDEX_HINT, -1);
1340 if (resumeIndex >= 0) {
1341 if (mHasCameraScreennailOrPlaceholder) {
1342 // Account for preview/placeholder being the first item
1345 if (resumeIndex < mMediaSet.getMediaItemCount()) {
1346 mCurrentIndex = resumeIndex;
1347 mModel.moveTo(mCurrentIndex);
1352 if (albumPageTransition == MSG_ALBUMPAGE_RESUMED) {
1353 mPhotoView.setFilmMode(mStartInFilmstrip || mAppBridge != null);
1354 } else if (albumPageTransition == MSG_ALBUMPAGE_PICKED) {
1355 mPhotoView.setFilmMode(false);
1360 protected void onResume() {
1363 if (mModel == null) {
1364 mActivity.getStateManager().finishState(this);
1367 transitionFromAlbumPageIfNeeded();
1369 mActivity.getGLRoot().freeze();
1371 setContentPane(mRootPane);
1374 mPhotoView.resume();
1375 mActionBar.setDisplayOptions(
1376 ((mSecureAlbum == null) && (mSetPathString != null)), false);
1377 mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
1378 refreshBottomControlsWhenReady();
1379 if (mShowSpinner && mPhotoView.getFilmMode()) {
1380 mActionBar.enableAlbumModeMenu(
1381 GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
1385 mActivity.getGLRoot().setLightsOutMode(true);
1387 boolean haveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
1388 if (haveImageEditor != mHaveImageEditor) {
1389 mHaveImageEditor = haveImageEditor;
1390 updateMenuOperations();
1393 mRecenterCameraOnResume = true;
1394 mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
1398 protected void onDestroy() {
1399 if (mAppBridge != null) {
1400 mAppBridge.setServer(null);
1401 mScreenNailItem.setScreenNail(null);
1402 mAppBridge.detachScreenNail();
1404 mScreenNailSet = null;
1405 mScreenNailItem = null;
1407 mActivity.getGLRoot().setOrientationSource(null);
1408 if (mBottomControls != null) mBottomControls.cleanup();
1410 // Remove all pending messages.
1411 mHandler.removeCallbacksAndMessages(null);
1415 private class MyDetailsSource implements DetailsSource {
1418 public MediaDetails getDetails() {
1419 return mModel.getMediaItem(0).getDetails();
1424 return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
1428 public int setIndex() {
1429 return mModel.getCurrentIndex();
1434 public void onAlbumModeSelected(int mode) {
1435 if (mode == GalleryActionBar.ALBUM_GRID_MODE_SELECTED) {
1441 public void refreshBottomControlsWhenReady() {
1442 if (mBottomControls == null) {
1445 MediaObject currentPhoto = mCurrentPhoto;
1446 if (currentPhoto == null) {
1447 mHandler.obtainMessage(MSG_REFRESH_BOTTOM_CONTROLS, 0, 0, currentPhoto).sendToTarget();
1449 currentPhoto.getPanoramaSupport(mRefreshBottomControlsCallback);
1453 private void updatePanoramaUI(boolean isPanorama360) {
1454 Menu menu = mActionBar.getMenu();
1456 // it could be null if onCreateActionBar has not been called yet
1461 MenuExecutor.updateMenuForPanorama(menu, isPanorama360, isPanorama360);
1463 if (isPanorama360) {
1464 MenuItem item = menu.findItem(R.id.action_share);
1466 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1467 item.setTitle(mActivity.getResources().getString(R.string.share_as_photo));
1469 } else if ((mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_SHARE) != 0) {
1470 MenuItem item = menu.findItem(R.id.action_share);
1472 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
1473 item.setTitle(mActivity.getResources().getString(R.string.share));
1479 public void onUndoBarVisibilityChanged(boolean visible) {
1480 refreshBottomControlsWhenReady();
1484 public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
1485 final long timestampMillis = mCurrentPhoto.getDateInMs();
1486 final String mediaType = getMediaTypeString(mCurrentPhoto);
1487 UsageStatistics.onEvent(UsageStatistics.COMPONENT_GALLERY,
1488 UsageStatistics.ACTION_SHARE,
1491 ? System.currentTimeMillis() - timestampMillis
1496 private static String getMediaTypeString(MediaItem item) {
1497 if (item.getMediaType() == MediaObject.MEDIA_TYPE_VIDEO) {
1499 } else if (item.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE) {
1502 return "Unknown:" + item.getMediaType();