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