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