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