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