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