PhotoPage.java revision 53fe9f72433f2fdae30e1708c5933390202cbcf5
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.Activity; 21import android.content.ActivityNotFoundException; 22import android.content.ContentResolver; 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.view.animation.AccelerateInterpolator; 35import android.widget.RelativeLayout; 36import android.widget.Toast; 37 38import com.actionbarsherlock.app.ActionBar.OnMenuVisibilityListener; 39import com.actionbarsherlock.view.Menu; 40import com.actionbarsherlock.view.MenuItem; 41import com.android.gallery3d.R; 42import com.android.gallery3d.anim.FloatAnimation; 43import com.android.gallery3d.common.ApiHelper; 44import com.android.gallery3d.common.Utils; 45import com.android.gallery3d.data.DataManager; 46import com.android.gallery3d.data.FilterDeleteSet; 47import com.android.gallery3d.data.MediaDetails; 48import com.android.gallery3d.data.MediaItem; 49import com.android.gallery3d.data.MediaObject; 50import com.android.gallery3d.data.MediaSet; 51import com.android.gallery3d.data.MtpSource; 52import com.android.gallery3d.data.Path; 53import com.android.gallery3d.data.SecureAlbum; 54import com.android.gallery3d.data.SecureSource; 55import com.android.gallery3d.data.SnailAlbum; 56import com.android.gallery3d.data.SnailItem; 57import com.android.gallery3d.data.SnailSource; 58import com.android.gallery3d.picasasource.PicasaSource; 59import com.android.gallery3d.ui.AnimationTime; 60import com.android.gallery3d.ui.BitmapScreenNail; 61import com.android.gallery3d.ui.DetailsHelper; 62import com.android.gallery3d.ui.DetailsHelper.CloseListener; 63import com.android.gallery3d.ui.DetailsHelper.DetailsSource; 64import com.android.gallery3d.ui.GLCanvas; 65import com.android.gallery3d.ui.GLRoot; 66import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; 67import com.android.gallery3d.ui.GLView; 68import com.android.gallery3d.ui.ImportCompleteListener; 69import com.android.gallery3d.ui.MenuExecutor; 70import com.android.gallery3d.ui.PhotoFallbackEffect; 71import com.android.gallery3d.ui.PhotoView; 72import com.android.gallery3d.ui.PreparePageFadeoutTexture; 73import com.android.gallery3d.ui.RawTexture; 74import com.android.gallery3d.ui.SelectionManager; 75import com.android.gallery3d.ui.SynchronizedHandler; 76import com.android.gallery3d.util.GalleryUtils; 77import com.android.gallery3d.util.LightCycleHelper; 78import com.android.gallery3d.util.MediaSetUtils; 79 80public class PhotoPage extends ActivityState implements 81 PhotoView.Listener, OrientationManager.Listener, AppBridge.Server, 82 PhotoPageBottomControls.Delegate { 83 private static final String TAG = "PhotoPage"; 84 85 private static final int MSG_HIDE_BARS = 1; 86 private static final int MSG_LOCK_ORIENTATION = 2; 87 private static final int MSG_UNLOCK_ORIENTATION = 3; 88 private static final int MSG_ON_FULL_SCREEN_CHANGED = 4; 89 private static final int MSG_UPDATE_ACTION_BAR = 5; 90 private static final int MSG_UNFREEZE_GLROOT = 6; 91 private static final int MSG_WANT_BARS = 7; 92 private static final int MSG_REFRESH_GRID_BUTTON = 8; 93 private static final int MSG_REFRESH_BOTTOM_CONTROLS = 9; 94 95 private static final int HIDE_BARS_TIMEOUT = 3500; 96 private static final int UNFREEZE_GLROOT_TIMEOUT = 250; 97 98 private static final int REQUEST_SLIDESHOW = 1; 99 private static final int REQUEST_CROP = 2; 100 private static final int REQUEST_CROP_PICASA = 3; 101 private static final int REQUEST_EDIT = 4; 102 private static final int REQUEST_PLAY_VIDEO = 5; 103 private static final int REQUEST_TRIM = 6; 104 105 public static final String KEY_MEDIA_SET_PATH = "media-set-path"; 106 public static final String KEY_MEDIA_ITEM_PATH = "media-item-path"; 107 public static final String KEY_INDEX_HINT = "index-hint"; 108 public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect"; 109 public static final String KEY_APP_BRIDGE = "app-bridge"; 110 public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up"; 111 public static final String KEY_START_IN_FILMSTRIP = "start-in-filmstrip"; 112 public static final String KEY_RETURN_INDEX_HINT = "return-index-hint"; 113 public static final String KEY_SHOW_WHEN_LOCKED = "show_when_locked"; 114 115 public static final String KEY_ALBUMPAGE_TRANSITION = "albumpage-transition"; 116 public static final int MSG_ALBUMPAGE_NONE = 0; 117 public static final int MSG_ALBUMPAGE_STARTED = 1; 118 public static final int MSG_ALBUMPAGE_RESUMED = 2; 119 public static final int MSG_ALBUMPAGE_PICKED = 4; 120 121 public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit"; 122 123 private GalleryApp mApplication; 124 private SelectionManager mSelectionManager; 125 126 private PhotoView mPhotoView; 127 private PhotoPage.Model mModel; 128 private DetailsHelper mDetailsHelper; 129 private boolean mShowDetails; 130 131 // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied. 132 // E.g., viewing a photo in gmail attachment 133 private FilterDeleteSet mMediaSet; 134 135 // The mediaset used by camera launched from secure lock screen. 136 private SecureAlbum mSecureAlbum; 137 138 private int mCurrentIndex = 0; 139 private Handler mHandler; 140 private boolean mShowBars = true; 141 private volatile boolean mActionBarAllowed = true; 142 private GalleryActionBar mActionBar; 143 private boolean mIsMenuVisible; 144 private boolean mHaveImageEditor; 145 private PhotoPageBottomControls mBottomControls; 146 private MediaItem mCurrentPhoto = null; 147 private MenuExecutor mMenuExecutor; 148 private boolean mIsActive; 149 private String mSetPathString; 150 // This is the original mSetPathString before adding the camera preview item. 151 private String mOriginalSetPathString; 152 private AppBridge mAppBridge; 153 private SnailItem mScreenNailItem; 154 private SnailAlbum mScreenNailSet; 155 private OrientationManager mOrientationManager; 156 private boolean mHasActivityResult; 157 private boolean mTreatBackAsUp; 158 private boolean mStartInFilmstrip; 159 private boolean mStartedFromAlbumPage; 160 161 private RawTexture mFadeOutTexture; 162 private Rect mOpenAnimationRect; 163 public static final int ANIM_TIME_OPENING = 300; 164 165 // The item that is deleted (but it can still be undeleted before commiting) 166 private Path mDeletePath; 167 private boolean mDeleteIsFocus; // whether the deleted item was in focus 168 169 private Uri[] mNfcPushUris = new Uri[1]; 170 171 private final MyMenuVisibilityListener mMenuVisibilityListener = 172 new MyMenuVisibilityListener(); 173 174 public static interface Model extends PhotoView.Model { 175 public void resume(); 176 public void pause(); 177 public boolean isEmpty(); 178 public void setCurrentPhoto(Path path, int indexHint); 179 } 180 181 private class MyMenuVisibilityListener implements OnMenuVisibilityListener { 182 @Override 183 public void onMenuVisibilityChanged(boolean isVisible) { 184 mIsMenuVisible = isVisible; 185 refreshHidingMessage(); 186 } 187 } 188 189 private static class BackgroundFadeOut extends FloatAnimation { 190 public BackgroundFadeOut() { 191 super(1f, 0f, ANIM_TIME_OPENING); 192 setInterpolator(new AccelerateInterpolator(2f)); 193 } 194 } 195 196 private final FloatAnimation mBackgroundFade = new BackgroundFadeOut(); 197 198 @Override 199 protected int getBackgroundColorId() { 200 return R.color.photo_background; 201 } 202 203 private final GLView mRootPane = new GLView() { 204 @Override 205 protected void renderBackground(GLCanvas view) { 206 if (mFadeOutTexture != null) { 207 if (mBackgroundFade.calculate(AnimationTime.get())) invalidate(); 208 if (!mBackgroundFade.isActive()) { 209 mFadeOutTexture = null; 210 mOpenAnimationRect = null; 211 BitmapScreenNail.enableDrawPlaceholder(); 212 } else { 213 float fadeAlpha = mBackgroundFade.get(); 214 if (fadeAlpha < 1f) { 215 view.clearBuffer(getBackgroundColor()); 216 view.setAlpha(fadeAlpha); 217 } 218 mFadeOutTexture.draw(view, 0, 0); 219 view.setAlpha(1f - fadeAlpha); 220 return; 221 } 222 } 223 view.clearBuffer(getBackgroundColor()); 224 } 225 226 @Override 227 protected void onLayout( 228 boolean changed, int left, int top, int right, int bottom) { 229 mPhotoView.layout(0, 0, right - left, bottom - top); 230 if (mShowDetails) { 231 mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom); 232 } 233 } 234 }; 235 236 @Override 237 public void onCreate(Bundle data, Bundle restoreState) { 238 super.onCreate(data, restoreState); 239 mActionBar = mActivity.getGalleryActionBar(); 240 mSelectionManager = new SelectionManager(mActivity, false); 241 mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager); 242 243 mPhotoView = new PhotoView(mActivity); 244 mPhotoView.setListener(this); 245 mRootPane.addComponent(mPhotoView); 246 mApplication = (GalleryApp) ((Activity) mActivity).getApplication(); 247 mOrientationManager = mActivity.getOrientationManager(); 248 mOrientationManager.addListener(this); 249 mActivity.getGLRoot().setOrientationSource(mOrientationManager); 250 251 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { 252 @Override 253 public void handleMessage(Message message) { 254 switch (message.what) { 255 case MSG_HIDE_BARS: { 256 hideBars(); 257 break; 258 } 259 case MSG_REFRESH_GRID_BUTTON: { 260 setGridButtonVisibility(mPhotoView.getFilmMode()); 261 break; 262 } 263 case MSG_REFRESH_BOTTOM_CONTROLS: { 264 if (mBottomControls != null) mBottomControls.refresh(); 265 break; 266 } 267 case MSG_LOCK_ORIENTATION: { 268 mOrientationManager.lockOrientation(); 269 break; 270 } 271 case MSG_UNLOCK_ORIENTATION: { 272 mOrientationManager.unlockOrientation(); 273 break; 274 } 275 case MSG_ON_FULL_SCREEN_CHANGED: { 276 mAppBridge.onFullScreenChanged(message.arg1 == 1); 277 break; 278 } 279 case MSG_UPDATE_ACTION_BAR: { 280 updateBars(); 281 break; 282 } 283 case MSG_WANT_BARS: { 284 wantBars(); 285 break; 286 } 287 case MSG_UNFREEZE_GLROOT: { 288 mActivity.getGLRoot().unfreeze(); 289 break; 290 } 291 default: throw new AssertionError(message.what); 292 } 293 } 294 }; 295 296 mSetPathString = data.getString(KEY_MEDIA_SET_PATH); 297 mOriginalSetPathString = mSetPathString; 298 setupNfcBeamPush(); 299 String itemPathString = data.getString(KEY_MEDIA_ITEM_PATH); 300 Path itemPath = itemPathString != null ? 301 Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)) : 302 null; 303 mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false); 304 mStartInFilmstrip = 305 data.getBoolean(KEY_START_IN_FILMSTRIP, false); 306 mStartedFromAlbumPage = 307 data.getInt(KEY_ALBUMPAGE_TRANSITION, 308 MSG_ALBUMPAGE_NONE) == MSG_ALBUMPAGE_STARTED; 309 setGridButtonVisibility(!mStartedFromAlbumPage); 310 if (mSetPathString != null) { 311 mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE); 312 if (mAppBridge != null) { 313 mShowBars = false; 314 315 mAppBridge.setServer(this); 316 mOrientationManager.lockOrientation(); 317 318 // Get the ScreenNail from AppBridge and register it. 319 int id = SnailSource.newId(); 320 Path screenNailSetPath = SnailSource.getSetPath(id); 321 Path screenNailItemPath = SnailSource.getItemPath(id); 322 mScreenNailSet = (SnailAlbum) mActivity.getDataManager() 323 .getMediaObject(screenNailSetPath); 324 mScreenNailItem = (SnailItem) mActivity.getDataManager() 325 .getMediaObject(screenNailItemPath); 326 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 327 328 // Check if the path is a secure album. 329 if (SecureSource.isSecurePath(mSetPathString)) { 330 mSecureAlbum = (SecureAlbum) mActivity.getDataManager() 331 .getMediaSet(mSetPathString); 332 } 333 if (data.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) { 334 // Set the flag to be on top of the lock screen. 335 mFlags |= FLAG_SHOW_WHEN_LOCKED; 336 } 337 338 // Combine the original MediaSet with the one for ScreenNail 339 // from AppBridge. 340 mSetPathString = "/combo/item/{" + screenNailSetPath + 341 "," + mSetPathString + "}"; 342 343 // Start from the screen nail. 344 itemPath = screenNailItemPath; 345 } 346 347 MediaSet originalSet = mActivity.getDataManager() 348 .getMediaSet(mSetPathString); 349 mSelectionManager.setSourceMediaSet(originalSet); 350 mSetPathString = "/filter/delete/{" + mSetPathString + "}"; 351 mMediaSet = (FilterDeleteSet) mActivity.getDataManager() 352 .getMediaSet(mSetPathString); 353 mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0); 354 if (mMediaSet == null) { 355 Log.w(TAG, "failed to restore " + mSetPathString); 356 } 357 if (itemPath == null) { 358 int mediaItemCount = mMediaSet.getMediaItemCount(); 359 if (mediaItemCount > 0) { 360 if (mCurrentIndex >= mediaItemCount) mCurrentIndex = 0; 361 itemPath = mMediaSet.getMediaItem(mCurrentIndex, 1) 362 .get(0).getPath(); 363 } else { 364 // Bail out, PhotoPage can't load on an empty album 365 return; 366 } 367 } 368 PhotoDataAdapter pda = new PhotoDataAdapter( 369 mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex, 370 mAppBridge == null ? -1 : 0, 371 mAppBridge == null ? false : mAppBridge.isPanorama(), 372 mAppBridge == null ? false : mAppBridge.isStaticCamera()); 373 mModel = pda; 374 mPhotoView.setModel(mModel); 375 376 pda.setDataListener(new PhotoDataAdapter.DataListener() { 377 378 @Override 379 public void onPhotoChanged(int index, Path item) { 380 int oldIndex = mCurrentIndex; 381 mCurrentIndex = index; 382 if (item != null) { 383 MediaItem photo = mModel.getMediaItem(0); 384 if (photo != null) updateCurrentPhoto(photo); 385 } 386 if (mAppBridge != null) { 387 if (oldIndex == 0 && mCurrentIndex > 0 388 && !mPhotoView.getFilmMode()) { 389 mPhotoView.setFilmMode(true); 390 } 391 } 392 updateBars(); 393 394 // Reset the timeout for the bars after a swipe 395 refreshHidingMessage(); 396 } 397 398 @Override 399 public void onLoadingFinished() { 400 if (!mModel.isEmpty()) { 401 MediaItem photo = mModel.getMediaItem(0); 402 if (photo != null) updateCurrentPhoto(photo); 403 } else if (mIsActive) { 404 // We only want to finish the PhotoPage if there is no 405 // deletion that the user can undo. 406 if (mMediaSet.getNumberOfDeletions() == 0) { 407 mActivity.getStateManager().finishState( 408 PhotoPage.this); 409 } 410 } 411 } 412 413 @Override 414 public void onLoadingStarted() { 415 } 416 }); 417 } else { 418 // Get default media set by the URI 419 MediaItem mediaItem = (MediaItem) 420 mActivity.getDataManager().getMediaObject(itemPath); 421 mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem); 422 mPhotoView.setModel(mModel); 423 updateCurrentPhoto(mediaItem); 424 } 425 426 mPhotoView.setFilmMode(mStartInFilmstrip && mMediaSet.getMediaItemCount() > 1); 427 if (mSecureAlbum == null) { 428 RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity) 429 .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root); 430 if (galleryRoot != null) { 431 mBottomControls = new PhotoPageBottomControls(this, mActivity, galleryRoot); 432 } 433 } 434 } 435 436 public boolean canDisplayBottomControls() { 437 return mShowBars && !mPhotoView.getFilmMode(); 438 } 439 440 public boolean canDisplayBottomControl(int control) { 441 if (mCurrentPhoto == null) return false; 442 switch(control) { 443 case R.id.photopage_bottom_control_edit: 444 return mCurrentPhoto.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE; 445 case R.id.photopage_bottom_control_panorama: 446 return (mCurrentPhoto.getSupportedOperations() 447 & MediaItem.SUPPORT_PANORAMA) != 0; 448 default: 449 return false; 450 } 451 } 452 453 public void onBottomControlClicked(int control) { 454 switch(control) { 455 case R.id.photopage_bottom_control_edit: 456 launchPhotoEditor(); 457 return; 458 case R.id.photopage_bottom_control_panorama: 459 LightCycleHelper.viewPanorama(mActivity, mCurrentPhoto.getContentUri()); 460 return; 461 default: 462 return; 463 } 464 } 465 466 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN) 467 private void setupNfcBeamPush() { 468 if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) return; 469 470 NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mActivity); 471 if (adapter != null) { 472 adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() { 473 @Override 474 public Uri[] createBeamUris(NfcEvent event) { 475 return mNfcPushUris; 476 } 477 }, mActivity); 478 } 479 } 480 481 private void setNfcBeamPushUri(Uri uri) { 482 mNfcPushUris[0] = uri; 483 } 484 485 private Intent createShareIntent(Path path) { 486 DataManager manager = mActivity.getDataManager(); 487 int type = manager.getMediaType(path); 488 int support = manager.getSupportedOperations(path); 489 boolean isPanorama = (support & MediaObject.SUPPORT_PANORAMA) != 0; 490 Intent intent = new Intent(Intent.ACTION_SEND); 491 intent.setType(MenuExecutor.getMimeType(type, isPanorama)); 492 Uri uri = manager.getContentUri(path); 493 intent.putExtra(Intent.EXTRA_STREAM, uri); 494 return intent; 495 496 } 497 498 private void launchPhotoEditor() { 499 MediaItem current = mModel.getMediaItem(0); 500 if (current == null) return; 501 502 Intent intent = new Intent(ACTION_NEXTGEN_EDIT); 503 intent.setData(mActivity.getDataManager().getContentUri(current.getPath())).setFlags( 504 Intent.FLAG_GRANT_READ_URI_PERMISSION); 505 if (mActivity.getPackageManager() 506 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) { 507 intent.setAction(Intent.ACTION_EDIT); 508 } 509 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null), 510 REQUEST_EDIT); 511 } 512 513 private void updateShareURI(Path path) { 514 DataManager manager = mActivity.getDataManager(); 515 Uri uri = manager.getContentUri(path); 516 mActionBar.setShareIntent(createShareIntent(path)); 517 setNfcBeamPushUri(uri); 518 } 519 520 private void updateCurrentPhoto(MediaItem photo) { 521 if (mCurrentPhoto == photo) return; 522 mCurrentPhoto = photo; 523 if (mCurrentPhoto == null) return; 524 updateMenuOperations(); 525 updateTitle(); 526 if (mBottomControls != null) mBottomControls.refresh(); 527 if (mShowDetails) { 528 mDetailsHelper.reloadDetails(); 529 } 530 if ((mSecureAlbum == null) 531 && (photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) { 532 updateShareURI(photo.getPath()); 533 } 534 } 535 536 private void updateTitle() { 537 if (mCurrentPhoto == null) return; 538 boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean( 539 R.bool.show_action_bar_title); 540 if (showTitle && mCurrentPhoto.getName() != null) { 541 mActionBar.setTitle(mCurrentPhoto.getName()); 542 } else { 543 mActionBar.setTitle(""); 544 } 545 } 546 547 private void updateMenuOperations() { 548 Menu menu = mActionBar.getMenu(); 549 550 // it could be null if onCreateActionBar has not been called yet 551 if (menu == null) return; 552 553 setGridButtonVisibility(mPhotoView.getFilmMode()); 554 555 MenuItem item = menu.findItem(R.id.action_slideshow); 556 if (item != null) { 557 item.setVisible((mSecureAlbum == null) && canDoSlideShow()); 558 } 559 if (mCurrentPhoto == null) return; 560 561 int supportedOperations = mCurrentPhoto.getSupportedOperations(); 562 if (mSecureAlbum != null) { 563 supportedOperations &= MediaObject.SUPPORT_DELETE; 564 } else if (!mHaveImageEditor) { 565 supportedOperations &= ~MediaObject.SUPPORT_EDIT; 566 } 567 MenuExecutor.updateMenuOperation(menu, supportedOperations); 568 } 569 570 private boolean canDoSlideShow() { 571 if (mMediaSet == null || mCurrentPhoto == null) { 572 return false; 573 } 574 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) { 575 return false; 576 } 577 if (MtpSource.isMtpPath(mOriginalSetPathString)) { 578 return false; 579 } 580 return true; 581 } 582 583 ////////////////////////////////////////////////////////////////////////// 584 // Action Bar show/hide management 585 ////////////////////////////////////////////////////////////////////////// 586 587 private void showBars() { 588 if (mShowBars) return; 589 mShowBars = true; 590 mOrientationManager.unlockOrientation(); 591 mActionBar.show(); 592 mActivity.getGLRoot().setLightsOutMode(false); 593 refreshHidingMessage(); 594 if (mBottomControls != null) mBottomControls.refresh(); 595 } 596 597 private void hideBars() { 598 if (!mShowBars) return; 599 mShowBars = false; 600 mActionBar.hide(); 601 mActivity.getGLRoot().setLightsOutMode(true); 602 mHandler.removeMessages(MSG_HIDE_BARS); 603 if (mBottomControls != null) mBottomControls.refresh(); 604 } 605 606 private void refreshHidingMessage() { 607 mHandler.removeMessages(MSG_HIDE_BARS); 608 if (!mIsMenuVisible && !mPhotoView.getFilmMode()) { 609 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT); 610 } 611 } 612 613 private boolean canShowBars() { 614 // No bars if we are showing camera preview. 615 if (mAppBridge != null && mCurrentIndex == 0 616 && !mPhotoView.getFilmMode()) return false; 617 618 // No bars if it's not allowed. 619 if (!mActionBarAllowed) return false; 620 621 return true; 622 } 623 624 private void wantBars() { 625 if (canShowBars()) showBars(); 626 } 627 628 private void toggleBars() { 629 if (mShowBars) { 630 hideBars(); 631 } else { 632 if (canShowBars()) showBars(); 633 } 634 } 635 636 private void updateBars() { 637 if (!canShowBars()) { 638 hideBars(); 639 } 640 } 641 642 @Override 643 public void onOrientationCompensationChanged() { 644 mActivity.getGLRoot().requestLayoutContentPane(); 645 } 646 647 @Override 648 protected void onBackPressed() { 649 if (mShowDetails) { 650 hideDetails(); 651 } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) { 652 // We are leaving this page. Set the result now. 653 setResult(); 654 if (mStartInFilmstrip && !mPhotoView.getFilmMode()) { 655 mPhotoView.setFilmMode(true); 656 } else if (mTreatBackAsUp) { 657 onUpPressed(); 658 } else { 659 super.onBackPressed(); 660 } 661 } 662 } 663 664 private void onUpPressed() { 665 if ((mStartInFilmstrip || mAppBridge != null) 666 && !mPhotoView.getFilmMode()) { 667 mPhotoView.setFilmMode(true); 668 return; 669 } 670 671 if (mActivity.getStateManager().getStateCount() > 1) { 672 setResult(); 673 super.onBackPressed(); 674 return; 675 } 676 677 if (mOriginalSetPathString == null) return; 678 679 if (mAppBridge == null) { 680 // We're in view mode so set up the stacks on our own. 681 Bundle data = new Bundle(getData()); 682 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 683 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 684 mActivity.getDataManager().getTopSetPath( 685 DataManager.INCLUDE_ALL)); 686 mActivity.getStateManager().switchState(this, AlbumPage.class, data); 687 } else { 688 GalleryUtils.startGalleryActivity(mActivity); 689 } 690 } 691 692 private void setResult() { 693 Intent result = null; 694 result = new Intent(); 695 result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex); 696 setStateResult(Activity.RESULT_OK, result); 697 } 698 699 ////////////////////////////////////////////////////////////////////////// 700 // AppBridge.Server interface 701 ////////////////////////////////////////////////////////////////////////// 702 703 @Override 704 public void setCameraRelativeFrame(Rect frame) { 705 mPhotoView.setCameraRelativeFrame(frame); 706 } 707 708 @Override 709 public boolean switchWithCaptureAnimation(int offset) { 710 return mPhotoView.switchWithCaptureAnimation(offset); 711 } 712 713 @Override 714 public void setSwipingEnabled(boolean enabled) { 715 mPhotoView.setSwipingEnabled(enabled); 716 } 717 718 @Override 719 public void notifyScreenNailChanged() { 720 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 721 mScreenNailSet.notifyChange(); 722 } 723 724 @Override 725 public void addSecureAlbumItem(boolean isVideo, int id) { 726 mSecureAlbum.addMediaItem(isVideo, id); 727 } 728 729 @Override 730 protected boolean onCreateActionBar(Menu menu) { 731 mActionBar.createActionBarMenu(R.menu.photo, menu); 732 mHaveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*"); 733 updateMenuOperations(); 734 updateTitle(); 735 return true; 736 } 737 738 private MenuExecutor.ProgressListener mConfirmDialogListener = 739 new MenuExecutor.ProgressListener() { 740 @Override 741 public void onProgressUpdate(int index) {} 742 743 @Override 744 public void onProgressComplete(int result) {} 745 746 @Override 747 public void onConfirmDialogShown() { 748 mHandler.removeMessages(MSG_HIDE_BARS); 749 } 750 751 @Override 752 public void onConfirmDialogDismissed(boolean confirmed) { 753 refreshHidingMessage(); 754 } 755 756 @Override 757 public void onProgressStart() {} 758 }; 759 760 @Override 761 protected boolean onItemSelected(MenuItem item) { 762 if (mModel == null) return true; 763 refreshHidingMessage(); 764 MediaItem current = mModel.getMediaItem(0); 765 766 if (current == null) { 767 // item is not ready, ignore 768 return true; 769 } 770 771 int currentIndex = mModel.getCurrentIndex(); 772 Path path = current.getPath(); 773 774 DataManager manager = mActivity.getDataManager(); 775 int action = item.getItemId(); 776 String confirmMsg = null; 777 switch (action) { 778 case android.R.id.home: { 779 onUpPressed(); 780 return true; 781 } 782 case R.id.action_grid: { 783 if (mStartedFromAlbumPage) { 784 onUpPressed(); 785 } else { 786 preparePhotoFallbackView(); 787 Bundle data = new Bundle(getData()); 788 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 789 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 790 mActivity.getDataManager().getTopSetPath( 791 DataManager.INCLUDE_ALL)); 792 793 // We only show cluster menu in the first AlbumPage in stack 794 // TODO: Enable this when running from the camera app 795 boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class); 796 data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum 797 && mAppBridge == null); 798 799 data.putBoolean(PhotoPage.KEY_APP_BRIDGE, mAppBridge != null); 800 801 // Account for live preview being first item 802 mActivity.getTransitionStore().put(KEY_RETURN_INDEX_HINT, 803 mAppBridge != null ? mCurrentIndex - 1 : mCurrentIndex); 804 805 mActivity.getStateManager().startState(AlbumPage.class, data); 806 } 807 return true; 808 } 809 case R.id.action_slideshow: { 810 Bundle data = new Bundle(); 811 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString()); 812 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString()); 813 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex); 814 data.putBoolean(SlideshowPage.KEY_REPEAT, true); 815 mActivity.getStateManager().startStateForResult( 816 SlideshowPage.class, REQUEST_SLIDESHOW, data); 817 return true; 818 } 819 case R.id.action_crop: { 820 Activity activity = mActivity; 821 Intent intent = new Intent(CropImage.CROP_ACTION); 822 intent.setClass(activity, CropImage.class); 823 intent.setData(manager.getContentUri(path)); 824 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current) 825 ? REQUEST_CROP_PICASA 826 : REQUEST_CROP); 827 return true; 828 } 829 case R.id.action_trim: { 830 Intent intent = new Intent(mActivity, TrimVideo.class); 831 intent.setData(manager.getContentUri(path)); 832 // We need the file path to wrap this into a RandomAccessFile. 833 intent.putExtra(KEY_MEDIA_ITEM_PATH, current.getFilePath()); 834 mActivity.startActivityForResult(intent, REQUEST_TRIM); 835 return true; 836 } 837 case R.id.action_edit: { 838 launchPhotoEditor(); 839 return true; 840 } 841 case R.id.action_details: { 842 if (mShowDetails) { 843 hideDetails(); 844 } else { 845 showDetails(); 846 } 847 return true; 848 } 849 case R.id.action_delete: 850 confirmMsg = mActivity.getResources().getQuantityString( 851 R.plurals.delete_selection, 1); 852 case R.id.action_setas: 853 case R.id.action_rotate_ccw: 854 case R.id.action_rotate_cw: 855 case R.id.action_show_on_map: 856 mSelectionManager.deSelectAll(); 857 mSelectionManager.toggle(path); 858 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener); 859 return true; 860 case R.id.action_import: 861 mSelectionManager.deSelectAll(); 862 mSelectionManager.toggle(path); 863 mMenuExecutor.onMenuClicked(item, confirmMsg, 864 new ImportCompleteListener(mActivity)); 865 return true; 866 case R.id.action_share: 867 Activity activity = mActivity; 868 Intent intent = createShareIntent(mCurrentPhoto.getPath()); 869 activity.startActivity(Intent.createChooser(intent, 870 activity.getString(R.string.share))); 871 return true; 872 default : 873 return false; 874 } 875 } 876 877 private void hideDetails() { 878 mShowDetails = false; 879 mDetailsHelper.hide(); 880 } 881 882 private void showDetails() { 883 mShowDetails = true; 884 if (mDetailsHelper == null) { 885 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource()); 886 mDetailsHelper.setCloseListener(new CloseListener() { 887 @Override 888 public void onClose() { 889 hideDetails(); 890 } 891 }); 892 } 893 mDetailsHelper.show(); 894 } 895 896 //////////////////////////////////////////////////////////////////////////// 897 // Callbacks from PhotoView 898 //////////////////////////////////////////////////////////////////////////// 899 @Override 900 public void onSingleTapUp(int x, int y) { 901 if (mAppBridge != null) { 902 if (mAppBridge.onSingleTapUp(x, y)) return; 903 } 904 905 MediaItem item = mModel.getMediaItem(0); 906 if (item == null || item == mScreenNailItem) { 907 // item is not ready or it is camera preview, ignore 908 return; 909 } 910 911 int supported = item.getSupportedOperations(); 912 boolean playVideo = (mSecureAlbum == null) && 913 ((supported & MediaItem.SUPPORT_PLAY) != 0); 914 boolean viewPanorama = (mSecureAlbum == null) && 915 ((supported & MediaItem.SUPPORT_PANORAMA) != 0); 916 boolean unlock = ((supported & MediaItem.SUPPORT_UNLOCK) != 0); 917 918 if (playVideo) { 919 // determine if the point is at center (1/6) of the photo view. 920 // (The position of the "play" icon is at center (1/6) of the photo) 921 int w = mPhotoView.getWidth(); 922 int h = mPhotoView.getHeight(); 923 playVideo = (Math.abs(x - w / 2) * 12 <= w) 924 && (Math.abs(y - h / 2) * 12 <= h); 925 } 926 927 if (playVideo) { 928 playVideo(mActivity, item.getPlayUri(), item.getName()); 929 } else if (viewPanorama) { 930 LightCycleHelper.viewPanorama(mActivity, item.getContentUri()); 931 } else if (unlock) { 932 mActivity.getStateManager().finishState(this); 933 } else { 934 toggleBars(); 935 } 936 } 937 938 @Override 939 public void lockOrientation() { 940 mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION); 941 } 942 943 @Override 944 public void unlockOrientation() { 945 mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION); 946 } 947 948 @Override 949 public void onActionBarAllowed(boolean allowed) { 950 mActionBarAllowed = allowed; 951 mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR); 952 } 953 954 @Override 955 public void onActionBarWanted() { 956 mHandler.sendEmptyMessage(MSG_WANT_BARS); 957 } 958 959 @Override 960 public void onFullScreenChanged(boolean full) { 961 Message m = mHandler.obtainMessage( 962 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0); 963 m.sendToTarget(); 964 } 965 966 // How we do delete/undo: 967 // 968 // When the user choose to delete a media item, we just tell the 969 // FilterDeleteSet to hide that item. If the user choose to undo it, we 970 // again tell FilterDeleteSet not to hide it. If the user choose to commit 971 // the deletion, we then actually delete the media item. 972 @Override 973 public void onDeleteImage(Path path, int offset) { 974 onCommitDeleteImage(); // commit the previous deletion 975 mDeletePath = path; 976 mDeleteIsFocus = (offset == 0); 977 mMediaSet.addDeletion(path, mCurrentIndex + offset); 978 } 979 980 @Override 981 public void onUndoDeleteImage() { 982 if (mDeletePath == null) return; 983 // If the deletion was done on the focused item, we want the model to 984 // focus on it when it is undeleted. 985 if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath); 986 mMediaSet.removeDeletion(mDeletePath); 987 mDeletePath = null; 988 } 989 990 @Override 991 public void onCommitDeleteImage() { 992 if (mDeletePath == null) return; 993 mSelectionManager.deSelectAll(); 994 mSelectionManager.toggle(mDeletePath); 995 mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false); 996 mDeletePath = null; 997 } 998 999 public static void playVideo(Activity activity, Uri uri, String title) { 1000 try { 1001 Intent intent = new Intent(Intent.ACTION_VIEW) 1002 .setDataAndType(uri, "video/*") 1003 .putExtra(Intent.EXTRA_TITLE, title) 1004 .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true); 1005 activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO); 1006 } catch (ActivityNotFoundException e) { 1007 Toast.makeText(activity, activity.getString(R.string.video_err), 1008 Toast.LENGTH_SHORT).show(); 1009 } 1010 } 1011 1012 private void setCurrentPhotoByIntent(Intent intent) { 1013 if (intent == null) return; 1014 Path path = mApplication.getDataManager() 1015 .findPathByUri(intent.getData(), intent.getType()); 1016 if (path != null) { 1017 mModel.setCurrentPhoto(path, mCurrentIndex); 1018 } 1019 } 1020 1021 @Override 1022 protected void onStateResult(int requestCode, int resultCode, Intent data) { 1023 mHasActivityResult = true; 1024 switch (requestCode) { 1025 case REQUEST_EDIT: 1026 setCurrentPhotoByIntent(data); 1027 break; 1028 case REQUEST_CROP: 1029 if (resultCode == Activity.RESULT_OK) { 1030 setCurrentPhotoByIntent(data); 1031 } 1032 break; 1033 case REQUEST_CROP_PICASA: { 1034 if (resultCode == Activity.RESULT_OK) { 1035 Context context = mActivity.getAndroidContext(); 1036 String message = context.getString(R.string.crop_saved, 1037 context.getString(R.string.folder_download)); 1038 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 1039 } 1040 break; 1041 } 1042 case REQUEST_SLIDESHOW: { 1043 if (data == null) break; 1044 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH); 1045 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0); 1046 if (path != null) { 1047 mModel.setCurrentPhoto(Path.fromString(path), index); 1048 } 1049 } 1050 } 1051 } 1052 1053 @Override 1054 protected void clearStateResult() { 1055 mHasActivityResult = false; 1056 } 1057 1058 private class PreparePhotoFallback implements OnGLIdleListener { 1059 private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect(); 1060 private boolean mResultReady = false; 1061 1062 public synchronized PhotoFallbackEffect get() { 1063 while (!mResultReady) { 1064 Utils.waitWithoutInterrupt(this); 1065 } 1066 return mPhotoFallback; 1067 } 1068 1069 @Override 1070 public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { 1071 mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas); 1072 synchronized (this) { 1073 mResultReady = true; 1074 notifyAll(); 1075 } 1076 return false; 1077 } 1078 } 1079 1080 private void preparePhotoFallbackView() { 1081 GLRoot root = mActivity.getGLRoot(); 1082 PreparePhotoFallback task = new PreparePhotoFallback(); 1083 root.unlockRenderThread(); 1084 PhotoFallbackEffect anim; 1085 try { 1086 root.addOnGLIdleListener(task); 1087 anim = task.get(); 1088 } finally { 1089 root.lockRenderThread(); 1090 } 1091 mActivity.getTransitionStore().put( 1092 AlbumPage.KEY_RESUME_ANIMATION, anim); 1093 } 1094 1095 @Override 1096 public void onPause() { 1097 super.onPause(); 1098 mIsActive = false; 1099 1100 mActivity.getGLRoot().unfreeze(); 1101 mHandler.removeMessages(MSG_UNFREEZE_GLROOT); 1102 1103 DetailsHelper.pause(); 1104 if (mModel != null) { 1105 if (isFinishing()) preparePhotoFallbackView(); 1106 mModel.pause(); 1107 } 1108 mPhotoView.pause(); 1109 mHandler.removeMessages(MSG_HIDE_BARS); 1110 mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener); 1111 1112 onCommitDeleteImage(); 1113 mMenuExecutor.pause(); 1114 if (mMediaSet != null) mMediaSet.clearDeletion(); 1115 } 1116 1117 @Override 1118 public void onCurrentImageUpdated() { 1119 mActivity.getGLRoot().unfreeze(); 1120 } 1121 1122 private void setGridButtonVisibility(boolean enabled) { 1123 Menu menu = mActionBar.getMenu(); 1124 if (menu == null) return; 1125 MenuItem item = menu.findItem(R.id.action_grid); 1126 if (item != null) item.setVisible((mSecureAlbum == null) && enabled); 1127 } 1128 1129 public void onFilmModeChanged(boolean enabled) { 1130 mHandler.sendEmptyMessage(MSG_REFRESH_GRID_BUTTON); 1131 mHandler.sendEmptyMessage(MSG_REFRESH_BOTTOM_CONTROLS); 1132 if (enabled) { 1133 mHandler.removeMessages(MSG_HIDE_BARS); 1134 } else { 1135 refreshHidingMessage(); 1136 } 1137 } 1138 1139 private void transitionFromAlbumPageIfNeeded() { 1140 TransitionStore transitions = mActivity.getTransitionStore(); 1141 1142 int albumPageTransition = transitions.get( 1143 KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE); 1144 1145 if (albumPageTransition == MSG_ALBUMPAGE_NONE && mAppBridge != null) { 1146 // Generally, resuming the PhotoPage when in Camera should 1147 // reset to the capture mode to allow quick photo taking 1148 mCurrentIndex = 0; 1149 mPhotoView.resetToFirstPicture(); 1150 } else { 1151 int resumeIndex = transitions.get(KEY_INDEX_HINT, -1); 1152 if (resumeIndex >= 0) { 1153 if (mAppBridge != null) { 1154 // Account for live preview being the first item 1155 resumeIndex++; 1156 } 1157 if (resumeIndex < mMediaSet.getMediaItemCount()) { 1158 mCurrentIndex = resumeIndex; 1159 mModel.moveTo(mCurrentIndex); 1160 } 1161 } 1162 } 1163 1164 if (albumPageTransition == MSG_ALBUMPAGE_RESUMED) { 1165 mPhotoView.setFilmMode(mStartInFilmstrip || mAppBridge != null); 1166 } else if (albumPageTransition == MSG_ALBUMPAGE_PICKED) { 1167 mPhotoView.setFilmMode(false); 1168 } 1169 1170 mFadeOutTexture = transitions.get(PreparePageFadeoutTexture.KEY_FADE_TEXTURE); 1171 if (mFadeOutTexture != null) { 1172 mBackgroundFade.start(); 1173 BitmapScreenNail.disableDrawPlaceholder(); 1174 mOpenAnimationRect = 1175 albumPageTransition == MSG_ALBUMPAGE_NONE ? 1176 (Rect) mData.getParcelable(KEY_OPEN_ANIMATION_RECT) : 1177 (Rect) transitions.get(KEY_OPEN_ANIMATION_RECT); 1178 mPhotoView.setOpenAnimationRect(mOpenAnimationRect); 1179 mBackgroundFade.start(); 1180 } 1181 } 1182 1183 @Override 1184 protected void onResume() { 1185 super.onResume(); 1186 1187 if (mModel == null) { 1188 mActivity.getStateManager().finishState(this); 1189 return; 1190 } 1191 transitionFromAlbumPageIfNeeded(); 1192 1193 mActivity.getGLRoot().freeze(); 1194 mIsActive = true; 1195 setContentPane(mRootPane); 1196 1197 mModel.resume(); 1198 mPhotoView.resume(); 1199 mActionBar.setDisplayOptions( 1200 ((mSecureAlbum == null) && (mSetPathString != null)), true); 1201 mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener); 1202 if (!mShowBars) { 1203 mActionBar.hide(); 1204 mActivity.getGLRoot().setLightsOutMode(true); 1205 } 1206 boolean haveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*"); 1207 if (haveImageEditor != mHaveImageEditor) { 1208 mHaveImageEditor = haveImageEditor; 1209 updateMenuOperations(); 1210 } 1211 1212 mHasActivityResult = false; 1213 mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT); 1214 } 1215 1216 @Override 1217 protected void onDestroy() { 1218 if (mAppBridge != null) { 1219 mAppBridge.setServer(null); 1220 mScreenNailItem.setScreenNail(null); 1221 mAppBridge.detachScreenNail(); 1222 mAppBridge = null; 1223 mScreenNailSet = null; 1224 mScreenNailItem = null; 1225 } 1226 mOrientationManager.removeListener(this); 1227 mActivity.getGLRoot().setOrientationSource(null); 1228 if (mBottomControls != null) mBottomControls.cleanup(); 1229 1230 // Remove all pending messages. 1231 mHandler.removeCallbacksAndMessages(null); 1232 super.onDestroy(); 1233 } 1234 1235 private class MyDetailsSource implements DetailsSource { 1236 1237 @Override 1238 public MediaDetails getDetails() { 1239 return mModel.getMediaItem(0).getDetails(); 1240 } 1241 1242 @Override 1243 public int size() { 1244 return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1; 1245 } 1246 1247 @Override 1248 public int setIndex() { 1249 return mModel.getCurrentIndex(); 1250 } 1251 } 1252} 1253