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