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 17 package com.android.gallery3d.app; 18 19 import android.app.ActionBar.OnMenuVisibilityListener; 20 import android.app.Activity; 21 import android.content.ActivityNotFoundException; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.graphics.Rect; 26 import android.net.Uri; 27 import android.nfc.NfcAdapter; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.view.Menu; 32 import android.view.MenuInflater; 33 import android.view.MenuItem; 34 import android.view.View; 35 import android.view.WindowManager; 36 import android.widget.ShareActionProvider; 37 import android.widget.Toast; 38 39 import com.android.gallery3d.R; 40 import com.android.gallery3d.common.Utils; 41 import com.android.gallery3d.data.DataManager; 42 import com.android.gallery3d.data.FilterDeleteSet; 43 import com.android.gallery3d.data.MediaDetails; 44 import com.android.gallery3d.data.MediaItem; 45 import com.android.gallery3d.data.MediaObject; 46 import com.android.gallery3d.data.MediaSet; 47 import com.android.gallery3d.data.MtpSource; 48 import com.android.gallery3d.data.Path; 49 import com.android.gallery3d.data.SnailAlbum; 50 import com.android.gallery3d.data.SnailItem; 51 import com.android.gallery3d.data.SnailSource; 52 import com.android.gallery3d.picasasource.PicasaSource; 53 import com.android.gallery3d.ui.DetailsHelper; 54 import com.android.gallery3d.ui.DetailsHelper.CloseListener; 55 import com.android.gallery3d.ui.DetailsHelper.DetailsSource; 56 import com.android.gallery3d.ui.GLCanvas; 57 import com.android.gallery3d.ui.GLRoot; 58 import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; 59 import com.android.gallery3d.ui.GLView; 60 import com.android.gallery3d.ui.ImportCompleteListener; 61 import com.android.gallery3d.ui.MenuExecutor; 62 import com.android.gallery3d.ui.PhotoFallbackEffect; 63 import com.android.gallery3d.ui.PhotoView; 64 import com.android.gallery3d.ui.SelectionManager; 65 import com.android.gallery3d.ui.SynchronizedHandler; 66 import com.android.gallery3d.util.GalleryUtils; 67 import com.android.gallery3d.util.MediaSetUtils; 68 69 public 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 ShareActionProvider mShareActionProvider; 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 { resume()141 public void resume(); pause()142 public void pause(); isEmpty()143 public boolean isEmpty(); setCurrentPhoto(Path path, int indexHint)144 public void setCurrentPhoto(Path path, int indexHint); 145 } 146 147 private class MyMenuVisibilityListener implements OnMenuVisibilityListener { 148 @Override onMenuVisibilityChanged(boolean isVisible)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 onCreate(Bundle data, Bundle restoreState)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 updateShareURI(Path path)321 private void updateShareURI(Path path) { 322 if (mShareActionProvider != null) { 323 DataManager manager = mActivity.getDataManager(); 324 int type = manager.getMediaType(path); 325 Intent intent = new Intent(Intent.ACTION_SEND); 326 intent.setType(MenuExecutor.getMimeType(type)); 327 intent.putExtra(Intent.EXTRA_STREAM, manager.getContentUri(path)); 328 mShareActionProvider.setShareIntent(intent); 329 if (mNfcAdapter != null) { 330 mNfcAdapter.setBeamPushUris(new Uri[]{manager.getContentUri(path)}, 331 (Activity)mActivity); 332 } 333 mPendingSharePath = null; 334 } else { 335 // This happens when ActionBar is not created yet. 336 mPendingSharePath = path; 337 } 338 } 339 updateCurrentPhoto(MediaItem photo)340 private void updateCurrentPhoto(MediaItem photo) { 341 if (mCurrentPhoto == photo) return; 342 mCurrentPhoto = photo; 343 if (mCurrentPhoto == null) return; 344 updateMenuOperations(); 345 updateTitle(); 346 if (mShowDetails) { 347 mDetailsHelper.reloadDetails(mModel.getCurrentIndex()); 348 } 349 if ((photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) { 350 updateShareURI(photo.getPath()); 351 } 352 } 353 updateTitle()354 private void updateTitle() { 355 if (mCurrentPhoto == null) return; 356 boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean( 357 R.bool.show_action_bar_title); 358 if (showTitle && mCurrentPhoto.getName() != null) 359 mActionBar.setTitle(mCurrentPhoto.getName()); 360 else 361 mActionBar.setTitle(""); 362 } 363 updateMenuOperations()364 private void updateMenuOperations() { 365 if (mMenu == null) return; 366 MenuItem item = mMenu.findItem(R.id.action_slideshow); 367 if (item != null) { 368 item.setVisible(canDoSlideShow()); 369 } 370 if (mCurrentPhoto == null) return; 371 int supportedOperations = mCurrentPhoto.getSupportedOperations(); 372 if (!GalleryUtils.isEditorAvailable((Context) mActivity, "image/*")) { 373 supportedOperations &= ~MediaObject.SUPPORT_EDIT; 374 } 375 376 MenuExecutor.updateMenuOperation(mMenu, supportedOperations); 377 } 378 canDoSlideShow()379 private boolean canDoSlideShow() { 380 if (mMediaSet == null || mCurrentPhoto == null) { 381 return false; 382 } 383 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) { 384 return false; 385 } 386 if (MtpSource.isMtpPath(mOriginalSetPathString)) { 387 return false; 388 } 389 return true; 390 } 391 392 ////////////////////////////////////////////////////////////////////////// 393 // Action Bar show/hide management 394 ////////////////////////////////////////////////////////////////////////// 395 showBars()396 private void showBars() { 397 if (mShowBars) return; 398 mShowBars = true; 399 mOrientationManager.unlockOrientation(); 400 mActionBar.show(); 401 mActivity.getGLRoot().setLightsOutMode(false); 402 refreshHidingMessage(); 403 } 404 hideBars()405 private void hideBars() { 406 if (!mShowBars) return; 407 mShowBars = false; 408 mActionBar.hide(); 409 mActivity.getGLRoot().setLightsOutMode(true); 410 mHandler.removeMessages(MSG_HIDE_BARS); 411 } 412 refreshHidingMessage()413 private void refreshHidingMessage() { 414 mHandler.removeMessages(MSG_HIDE_BARS); 415 if (!mIsMenuVisible) { 416 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT); 417 } 418 } 419 canShowBars()420 private boolean canShowBars() { 421 // No bars if we are showing camera preview. 422 if (mAppBridge != null && mCurrentIndex == 0) return false; 423 // No bars if it's not allowed. 424 if (!mActionBarAllowed) return false; 425 426 return true; 427 } 428 wantBars()429 private void wantBars() { 430 if (canShowBars()) showBars(); 431 } 432 toggleBars()433 private void toggleBars() { 434 if (mShowBars) { 435 hideBars(); 436 } else { 437 if (canShowBars()) showBars(); 438 } 439 } 440 updateBars()441 private void updateBars() { 442 if (!canShowBars()) { 443 hideBars(); 444 } 445 } 446 447 @Override onOrientationCompensationChanged()448 public void onOrientationCompensationChanged() { 449 mActivity.getGLRoot().requestLayoutContentPane(); 450 } 451 452 @Override onBackPressed()453 protected void onBackPressed() { 454 if (mShowDetails) { 455 hideDetails(); 456 } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) { 457 // We are leaving this page. Set the result now. 458 setResult(); 459 if (mTreatBackAsUp) { 460 onUpPressed(); 461 } else { 462 super.onBackPressed(); 463 } 464 } 465 } 466 onUpPressed()467 private void onUpPressed() { 468 if (mActivity.getStateManager().getStateCount() > 1) { 469 super.onBackPressed(); 470 return; 471 } 472 473 if (mOriginalSetPathString == null) return; 474 475 if (mAppBridge == null) { 476 // We're in view mode so set up the stacks on our own. 477 Bundle data = new Bundle(getData()); 478 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 479 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 480 mActivity.getDataManager().getTopSetPath( 481 DataManager.INCLUDE_ALL)); 482 mActivity.getStateManager().switchState(this, AlbumPage.class, data); 483 } else { 484 // Start the real gallery activity to view the camera roll. 485 Uri uri = Uri.parse("content://media/external/file?bucketId=" 486 + MediaSetUtils.CAMERA_BUCKET_ID); 487 Intent intent = new Intent(Intent.ACTION_VIEW); 488 intent.setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE + "/image"); 489 ((Activity) mActivity).startActivity(intent); 490 } 491 } 492 setResult()493 private void setResult() { 494 Intent result = null; 495 if (!mPhotoView.getFilmMode()) { 496 result = new Intent(); 497 result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex); 498 } 499 setStateResult(Activity.RESULT_OK, result); 500 } 501 502 ////////////////////////////////////////////////////////////////////////// 503 // AppBridge.Server interface 504 ////////////////////////////////////////////////////////////////////////// 505 506 @Override setCameraRelativeFrame(Rect frame)507 public void setCameraRelativeFrame(Rect frame) { 508 mPhotoView.setCameraRelativeFrame(frame); 509 } 510 511 @Override switchWithCaptureAnimation(int offset)512 public boolean switchWithCaptureAnimation(int offset) { 513 return mPhotoView.switchWithCaptureAnimation(offset); 514 } 515 516 @Override setSwipingEnabled(boolean enabled)517 public void setSwipingEnabled(boolean enabled) { 518 mPhotoView.setSwipingEnabled(enabled); 519 } 520 521 @Override notifyScreenNailChanged()522 public void notifyScreenNailChanged() { 523 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 524 mScreenNailSet.notifyChange(); 525 } 526 527 @Override onCreateActionBar(Menu menu)528 protected boolean onCreateActionBar(Menu menu) { 529 MenuInflater inflater = ((Activity) mActivity).getMenuInflater(); 530 inflater.inflate(R.menu.photo, menu); 531 mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu); 532 if (mPendingSharePath != null) updateShareURI(mPendingSharePath); 533 mMenu = menu; 534 updateMenuOperations(); 535 updateTitle(); 536 return true; 537 } 538 539 private MenuExecutor.ProgressListener mConfirmDialogListener = 540 new MenuExecutor.ProgressListener() { 541 @Override 542 public void onProgressUpdate(int index) {} 543 544 @Override 545 public void onProgressComplete(int result) {} 546 547 @Override 548 public void onConfirmDialogShown() { 549 mHandler.removeMessages(MSG_HIDE_BARS); 550 } 551 552 @Override 553 public void onConfirmDialogDismissed(boolean confirmed) { 554 refreshHidingMessage(); 555 } 556 }; 557 558 @Override onItemSelected(MenuItem item)559 protected boolean onItemSelected(MenuItem item) { 560 refreshHidingMessage(); 561 MediaItem current = mModel.getMediaItem(0); 562 563 if (current == null) { 564 // item is not ready, ignore 565 return true; 566 } 567 568 int currentIndex = mModel.getCurrentIndex(); 569 Path path = current.getPath(); 570 571 DataManager manager = mActivity.getDataManager(); 572 int action = item.getItemId(); 573 String confirmMsg = null; 574 switch (action) { 575 case android.R.id.home: { 576 onUpPressed(); 577 return true; 578 } 579 case R.id.action_slideshow: { 580 Bundle data = new Bundle(); 581 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString()); 582 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString()); 583 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex); 584 data.putBoolean(SlideshowPage.KEY_REPEAT, true); 585 mActivity.getStateManager().startStateForResult( 586 SlideshowPage.class, REQUEST_SLIDESHOW, data); 587 return true; 588 } 589 case R.id.action_crop: { 590 Activity activity = (Activity) mActivity; 591 Intent intent = new Intent(CropImage.CROP_ACTION); 592 intent.setClass(activity, CropImage.class); 593 intent.setData(manager.getContentUri(path)); 594 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current) 595 ? REQUEST_CROP_PICASA 596 : REQUEST_CROP); 597 return true; 598 } 599 case R.id.action_edit: { 600 Intent intent = new Intent(Intent.ACTION_EDIT) 601 .setData(manager.getContentUri(path)) 602 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 603 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null), 604 REQUEST_EDIT); 605 return true; 606 } 607 case R.id.action_details: { 608 if (mShowDetails) { 609 hideDetails(); 610 } else { 611 showDetails(currentIndex); 612 } 613 return true; 614 } 615 case R.id.action_delete: 616 confirmMsg = mActivity.getResources().getQuantityString( 617 R.plurals.delete_selection, 1); 618 case R.id.action_setas: 619 case R.id.action_rotate_ccw: 620 case R.id.action_rotate_cw: 621 case R.id.action_show_on_map: 622 mSelectionManager.deSelectAll(); 623 mSelectionManager.toggle(path); 624 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener); 625 return true; 626 case R.id.action_import: 627 mSelectionManager.deSelectAll(); 628 mSelectionManager.toggle(path); 629 mMenuExecutor.onMenuClicked(item, confirmMsg, 630 new ImportCompleteListener(mActivity)); 631 return true; 632 default : 633 return false; 634 } 635 } 636 hideDetails()637 private void hideDetails() { 638 mShowDetails = false; 639 mDetailsHelper.hide(); 640 } 641 showDetails(int index)642 private void showDetails(int index) { 643 mShowDetails = true; 644 if (mDetailsHelper == null) { 645 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource()); 646 mDetailsHelper.setCloseListener(new CloseListener() { 647 @Override 648 public void onClose() { 649 hideDetails(); 650 } 651 }); 652 } 653 mDetailsHelper.reloadDetails(index); 654 mDetailsHelper.show(); 655 } 656 657 //////////////////////////////////////////////////////////////////////////// 658 // Callbacks from PhotoView 659 //////////////////////////////////////////////////////////////////////////// 660 @Override onSingleTapUp(int x, int y)661 public void onSingleTapUp(int x, int y) { 662 if (mAppBridge != null) { 663 if (mAppBridge.onSingleTapUp(x, y)) return; 664 } 665 666 MediaItem item = mModel.getMediaItem(0); 667 if (item == null || item == mScreenNailItem) { 668 // item is not ready or it is camera preview, ignore 669 return; 670 } 671 672 boolean playVideo = 673 (item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0; 674 675 if (playVideo) { 676 // determine if the point is at center (1/6) of the photo view. 677 // (The position of the "play" icon is at center (1/6) of the photo) 678 int w = mPhotoView.getWidth(); 679 int h = mPhotoView.getHeight(); 680 playVideo = (Math.abs(x - w / 2) * 12 <= w) 681 && (Math.abs(y - h / 2) * 12 <= h); 682 } 683 684 if (playVideo) { 685 playVideo((Activity) mActivity, item.getPlayUri(), item.getName()); 686 } else { 687 toggleBars(); 688 } 689 } 690 691 @Override lockOrientation()692 public void lockOrientation() { 693 mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION); 694 } 695 696 @Override unlockOrientation()697 public void unlockOrientation() { 698 mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION); 699 } 700 701 @Override onActionBarAllowed(boolean allowed)702 public void onActionBarAllowed(boolean allowed) { 703 mActionBarAllowed = allowed; 704 mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR); 705 } 706 707 @Override onActionBarWanted()708 public void onActionBarWanted() { 709 mHandler.sendEmptyMessage(MSG_WANT_BARS); 710 } 711 712 @Override onFullScreenChanged(boolean full)713 public void onFullScreenChanged(boolean full) { 714 Message m = mHandler.obtainMessage( 715 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0); 716 m.sendToTarget(); 717 } 718 719 // How we do delete/undo: 720 // 721 // When the user choose to delete a media item, we just tell the 722 // FilterDeleteSet to hide that item. If the user choose to undo it, we 723 // again tell FilterDeleteSet not to hide it. If the user choose to commit 724 // the deletion, we then actually delete the media item. 725 @Override onDeleteImage(Path path, int offset)726 public void onDeleteImage(Path path, int offset) { 727 onCommitDeleteImage(); // commit the previous deletion 728 mDeletePath = path; 729 mDeleteIsFocus = (offset == 0); 730 mMediaSet.addDeletion(path, mCurrentIndex + offset); 731 } 732 733 @Override onUndoDeleteImage()734 public void onUndoDeleteImage() { 735 if (mDeletePath == null) return; 736 // If the deletion was done on the focused item, we want the model to 737 // focus on it when it is undeleted. 738 if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath); 739 mMediaSet.removeDeletion(mDeletePath); 740 mDeletePath = null; 741 } 742 743 @Override onCommitDeleteImage()744 public void onCommitDeleteImage() { 745 if (mDeletePath == null) return; 746 mSelectionManager.deSelectAll(); 747 mSelectionManager.toggle(mDeletePath); 748 mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false); 749 mDeletePath = null; 750 } 751 playVideo(Activity activity, Uri uri, String title)752 public static void playVideo(Activity activity, Uri uri, String title) { 753 try { 754 Intent intent = new Intent(Intent.ACTION_VIEW) 755 .setDataAndType(uri, "video/*") 756 .putExtra(Intent.EXTRA_TITLE, title) 757 .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true); 758 activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO); 759 } catch (ActivityNotFoundException e) { 760 Toast.makeText(activity, activity.getString(R.string.video_err), 761 Toast.LENGTH_SHORT).show(); 762 } 763 } 764 setCurrentPhotoByIntent(Intent intent)765 private void setCurrentPhotoByIntent(Intent intent) { 766 if (intent == null) return; 767 Path path = mApplication.getDataManager() 768 .findPathByUri(intent.getData(), intent.getType()); 769 if (path != null) { 770 mModel.setCurrentPhoto(path, mCurrentIndex); 771 } 772 } 773 774 @Override onStateResult(int requestCode, int resultCode, Intent data)775 protected void onStateResult(int requestCode, int resultCode, Intent data) { 776 mHasActivityResult = true; 777 switch (requestCode) { 778 case REQUEST_EDIT: 779 setCurrentPhotoByIntent(data); 780 break; 781 case REQUEST_CROP: 782 if (resultCode == Activity.RESULT_OK) { 783 setCurrentPhotoByIntent(data); 784 } 785 break; 786 case REQUEST_CROP_PICASA: { 787 if (resultCode == Activity.RESULT_OK) { 788 Context context = mActivity.getAndroidContext(); 789 String message = context.getString(R.string.crop_saved, 790 context.getString(R.string.folder_download)); 791 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 792 } 793 break; 794 } 795 case REQUEST_SLIDESHOW: { 796 if (data == null) break; 797 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH); 798 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0); 799 if (path != null) { 800 mModel.setCurrentPhoto(Path.fromString(path), index); 801 } 802 } 803 } 804 } 805 806 private class PreparePhotoFallback implements OnGLIdleListener { 807 private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect(); 808 private boolean mResultReady = false; 809 get()810 public synchronized PhotoFallbackEffect get() { 811 while (!mResultReady) { 812 Utils.waitWithoutInterrupt(this); 813 } 814 return mPhotoFallback; 815 } 816 817 @Override onGLIdle(GLCanvas canvas, boolean renderRequested)818 public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { 819 mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas); 820 synchronized (this) { 821 mResultReady = true; 822 notifyAll(); 823 } 824 return false; 825 } 826 } 827 preparePhotoFallbackView()828 private void preparePhotoFallbackView() { 829 GLRoot root = mActivity.getGLRoot(); 830 PreparePhotoFallback task = new PreparePhotoFallback(); 831 root.unlockRenderThread(); 832 PhotoFallbackEffect anim; 833 try { 834 root.addOnGLIdleListener(task); 835 anim = task.get(); 836 } finally { 837 root.lockRenderThread(); 838 } 839 mActivity.getTransitionStore().put( 840 AlbumPage.KEY_RESUME_ANIMATION, anim); 841 } 842 843 @Override onPause()844 public void onPause() { 845 super.onPause(); 846 mIsActive = false; 847 848 mActivity.getGLRoot().unfreeze(); 849 mHandler.removeMessages(MSG_UNFREEZE_GLROOT); 850 if (isFinishing()) preparePhotoFallbackView(); 851 852 DetailsHelper.pause(); 853 mPhotoView.pause(); 854 mModel.pause(); 855 mHandler.removeMessages(MSG_HIDE_BARS); 856 mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener); 857 858 onCommitDeleteImage(); 859 mMenuExecutor.pause(); 860 if (mMediaSet != null) mMediaSet.clearDeletion(); 861 } 862 863 @Override onCurrentImageUpdated()864 public void onCurrentImageUpdated() { 865 mActivity.getGLRoot().unfreeze(); 866 } 867 868 @Override onResume()869 protected void onResume() { 870 super.onResume(); 871 mActivity.getGLRoot().freeze(); 872 mIsActive = true; 873 setContentPane(mRootPane); 874 875 mModel.resume(); 876 mPhotoView.resume(); 877 if (mMenuVisibilityListener == null) { 878 mMenuVisibilityListener = new MyMenuVisibilityListener(); 879 } 880 mActionBar.setDisplayOptions(mSetPathString != null, true); 881 mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener); 882 883 if (mAppBridge != null && !mHasActivityResult) { 884 mPhotoView.resetToFirstPicture(); 885 } 886 mHasActivityResult = false; 887 mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT); 888 } 889 890 @Override onDestroy()891 protected void onDestroy() { 892 if (mAppBridge != null) { 893 mAppBridge.setServer(null); 894 mScreenNailItem.setScreenNail(null); 895 mAppBridge.detachScreenNail(); 896 mAppBridge = null; 897 mScreenNailSet = null; 898 mScreenNailItem = null; 899 } 900 mOrientationManager.removeListener(this); 901 mActivity.getGLRoot().setOrientationSource(null); 902 903 // Remove all pending messages. 904 mHandler.removeCallbacksAndMessages(null); 905 super.onDestroy(); 906 } 907 908 private class MyDetailsSource implements DetailsSource { 909 private int mIndex; 910 911 @Override getDetails()912 public MediaDetails getDetails() { 913 return mModel.getMediaItem(0).getDetails(); 914 } 915 916 @Override size()917 public int size() { 918 return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1; 919 } 920 921 @Override findIndex(int indexHint)922 public int findIndex(int indexHint) { 923 mIndex = indexHint; 924 return indexHint; 925 } 926 927 @Override getIndex()928 public int getIndex() { 929 return mIndex; 930 } 931 } 932 } 933