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