PhotoPage.java revision 4f32d514ee7474d867d04bf3934f9e11e9e92783
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 mActivity.startActivityForResult(intent, REQUEST_TRIM); 796 return true; 797 } 798 case R.id.action_edit: { 799 launchPhotoEditor(); 800 return true; 801 } 802 case R.id.action_details: { 803 if (mShowDetails) { 804 hideDetails(); 805 } else { 806 showDetails(); 807 } 808 return true; 809 } 810 case R.id.action_delete: 811 confirmMsg = mActivity.getResources().getQuantityString( 812 R.plurals.delete_selection, 1); 813 case R.id.action_setas: 814 case R.id.action_rotate_ccw: 815 case R.id.action_rotate_cw: 816 case R.id.action_show_on_map: 817 mSelectionManager.deSelectAll(); 818 mSelectionManager.toggle(path); 819 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener); 820 return true; 821 case R.id.action_import: 822 mSelectionManager.deSelectAll(); 823 mSelectionManager.toggle(path); 824 mMenuExecutor.onMenuClicked(item, confirmMsg, 825 new ImportCompleteListener(mActivity)); 826 return true; 827 case R.id.action_share: 828 Activity activity = mActivity; 829 Intent intent = createShareIntent(mCurrentPhoto.getPath()); 830 activity.startActivity(Intent.createChooser(intent, 831 activity.getString(R.string.share))); 832 return true; 833 default : 834 return false; 835 } 836 } 837 838 private void hideDetails() { 839 mShowDetails = false; 840 mDetailsHelper.hide(); 841 } 842 843 private void showDetails() { 844 mShowDetails = true; 845 if (mDetailsHelper == null) { 846 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource()); 847 mDetailsHelper.setCloseListener(new CloseListener() { 848 @Override 849 public void onClose() { 850 hideDetails(); 851 } 852 }); 853 } 854 mDetailsHelper.show(); 855 } 856 857 //////////////////////////////////////////////////////////////////////////// 858 // Callbacks from PhotoView 859 //////////////////////////////////////////////////////////////////////////// 860 @Override 861 public void onSingleTapUp(int x, int y) { 862 if (mAppBridge != null) { 863 if (mAppBridge.onSingleTapUp(x, y)) return; 864 } 865 866 MediaItem item = mModel.getMediaItem(0); 867 if (item == null || item == mScreenNailItem) { 868 // item is not ready or it is camera preview, ignore 869 return; 870 } 871 872 boolean playVideo = (mSecureAlbum == null) && 873 ((item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0); 874 boolean viewPanorama = (mSecureAlbum == null) && 875 (item.getSupportedOperations() & MediaItem.SUPPORT_PANORAMA) != 0; 876 877 if (playVideo) { 878 // determine if the point is at center (1/6) of the photo view. 879 // (The position of the "play" icon is at center (1/6) of the photo) 880 int w = mPhotoView.getWidth(); 881 int h = mPhotoView.getHeight(); 882 playVideo = (Math.abs(x - w / 2) * 12 <= w) 883 && (Math.abs(y - h / 2) * 12 <= h); 884 } 885 886 if (playVideo) { 887 playVideo(mActivity, item.getPlayUri(), item.getName()); 888 } else if (viewPanorama) { 889 LightCycleHelper.viewPanorama(mActivity, item.getContentUri()); 890 } else { 891 toggleBars(); 892 } 893 } 894 895 @Override 896 public void lockOrientation() { 897 mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION); 898 } 899 900 @Override 901 public void unlockOrientation() { 902 mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION); 903 } 904 905 @Override 906 public void onActionBarAllowed(boolean allowed) { 907 mActionBarAllowed = allowed; 908 mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR); 909 } 910 911 @Override 912 public void onActionBarWanted() { 913 mHandler.sendEmptyMessage(MSG_WANT_BARS); 914 } 915 916 @Override 917 public void onFullScreenChanged(boolean full) { 918 Message m = mHandler.obtainMessage( 919 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0); 920 m.sendToTarget(); 921 } 922 923 // How we do delete/undo: 924 // 925 // When the user choose to delete a media item, we just tell the 926 // FilterDeleteSet to hide that item. If the user choose to undo it, we 927 // again tell FilterDeleteSet not to hide it. If the user choose to commit 928 // the deletion, we then actually delete the media item. 929 @Override 930 public void onDeleteImage(Path path, int offset) { 931 onCommitDeleteImage(); // commit the previous deletion 932 mDeletePath = path; 933 mDeleteIsFocus = (offset == 0); 934 mMediaSet.addDeletion(path, mCurrentIndex + offset); 935 } 936 937 @Override 938 public void onUndoDeleteImage() { 939 if (mDeletePath == null) return; 940 // If the deletion was done on the focused item, we want the model to 941 // focus on it when it is undeleted. 942 if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath); 943 mMediaSet.removeDeletion(mDeletePath); 944 mDeletePath = null; 945 } 946 947 @Override 948 public void onCommitDeleteImage() { 949 if (mDeletePath == null) return; 950 mSelectionManager.deSelectAll(); 951 mSelectionManager.toggle(mDeletePath); 952 mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false); 953 mDeletePath = null; 954 } 955 956 public static void playVideo(Activity activity, Uri uri, String title) { 957 try { 958 Intent intent = new Intent(Intent.ACTION_VIEW) 959 .setDataAndType(uri, "video/*") 960 .putExtra(Intent.EXTRA_TITLE, title) 961 .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true); 962 activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO); 963 } catch (ActivityNotFoundException e) { 964 Toast.makeText(activity, activity.getString(R.string.video_err), 965 Toast.LENGTH_SHORT).show(); 966 } 967 } 968 969 private void setCurrentPhotoByIntent(Intent intent) { 970 if (intent == null) return; 971 Path path = mApplication.getDataManager() 972 .findPathByUri(intent.getData(), intent.getType()); 973 if (path != null) { 974 mModel.setCurrentPhoto(path, mCurrentIndex); 975 } 976 } 977 978 @Override 979 protected void onStateResult(int requestCode, int resultCode, Intent data) { 980 mHasActivityResult = true; 981 switch (requestCode) { 982 case REQUEST_EDIT: 983 setCurrentPhotoByIntent(data); 984 break; 985 case REQUEST_CROP: 986 if (resultCode == Activity.RESULT_OK) { 987 setCurrentPhotoByIntent(data); 988 } 989 break; 990 case REQUEST_CROP_PICASA: { 991 if (resultCode == Activity.RESULT_OK) { 992 Context context = mActivity.getAndroidContext(); 993 String message = context.getString(R.string.crop_saved, 994 context.getString(R.string.folder_download)); 995 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 996 } 997 break; 998 } 999 case REQUEST_SLIDESHOW: { 1000 if (data == null) break; 1001 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH); 1002 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0); 1003 if (path != null) { 1004 mModel.setCurrentPhoto(Path.fromString(path), index); 1005 } 1006 } 1007 } 1008 } 1009 1010 @Override 1011 protected void clearStateResult() { 1012 mHasActivityResult = false; 1013 } 1014 1015 private class PreparePhotoFallback implements OnGLIdleListener { 1016 private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect(); 1017 private boolean mResultReady = false; 1018 1019 public synchronized PhotoFallbackEffect get() { 1020 while (!mResultReady) { 1021 Utils.waitWithoutInterrupt(this); 1022 } 1023 return mPhotoFallback; 1024 } 1025 1026 @Override 1027 public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { 1028 mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas); 1029 synchronized (this) { 1030 mResultReady = true; 1031 notifyAll(); 1032 } 1033 return false; 1034 } 1035 } 1036 1037 private void preparePhotoFallbackView() { 1038 GLRoot root = mActivity.getGLRoot(); 1039 PreparePhotoFallback task = new PreparePhotoFallback(); 1040 root.unlockRenderThread(); 1041 PhotoFallbackEffect anim; 1042 try { 1043 root.addOnGLIdleListener(task); 1044 anim = task.get(); 1045 } finally { 1046 root.lockRenderThread(); 1047 } 1048 mActivity.getTransitionStore().put( 1049 AlbumPage.KEY_RESUME_ANIMATION, anim); 1050 } 1051 1052 @Override 1053 public void onPause() { 1054 super.onPause(); 1055 mIsActive = false; 1056 1057 mActivity.getGLRoot().unfreeze(); 1058 mHandler.removeMessages(MSG_UNFREEZE_GLROOT); 1059 1060 DetailsHelper.pause(); 1061 if (mModel != null) { 1062 if (isFinishing()) preparePhotoFallbackView(); 1063 mModel.pause(); 1064 } 1065 mPhotoView.pause(); 1066 mHandler.removeMessages(MSG_HIDE_BARS); 1067 mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener); 1068 1069 onCommitDeleteImage(); 1070 mMenuExecutor.pause(); 1071 if (mMediaSet != null) mMediaSet.clearDeletion(); 1072 } 1073 1074 @Override 1075 public void onCurrentImageUpdated() { 1076 mActivity.getGLRoot().unfreeze(); 1077 } 1078 1079 private void setGridButtonVisibility(boolean enabled) { 1080 Menu menu = mActionBar.getMenu(); 1081 if (menu == null) return; 1082 MenuItem item = menu.findItem(R.id.action_grid); 1083 if (item != null) item.setVisible((mSecureAlbum == null) && enabled); 1084 } 1085 1086 public void onFilmModeChanged(boolean enabled) { 1087 mHandler.sendEmptyMessage(MSG_REFRESH_GRID_BUTTON); 1088 mHandler.sendEmptyMessage(MSG_REFRESH_BOTTOM_CONTROLS); 1089 if (enabled) { 1090 mHandler.removeMessages(MSG_HIDE_BARS); 1091 } else { 1092 refreshHidingMessage(); 1093 } 1094 } 1095 1096 private void transitionFromAlbumPageIfNeeded() { 1097 TransitionStore transitions = mActivity.getTransitionStore(); 1098 1099 int resumeIndex = transitions.get(KEY_INDEX_HINT, -1); 1100 if (resumeIndex >= 0) { 1101 mCurrentIndex = resumeIndex; 1102 mModel.setCurrentPhoto((Path)transitions.get(KEY_MEDIA_SET_PATH), mCurrentIndex); 1103 mPhotoView.switchToImage(mCurrentIndex); 1104 } 1105 1106 int albumPageTransition = transitions.get( 1107 KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE); 1108 1109 if(albumPageTransition != MSG_ALBUMPAGE_NONE) { 1110 mPhotoView.setFilmMode(mStartInFilmstrip 1111 && albumPageTransition == MSG_ALBUMPAGE_RESUMED); 1112 } 1113 1114 mFadeOutTexture = transitions.get(PreparePageFadeoutTexture.KEY_FADE_TEXTURE); 1115 if (mFadeOutTexture != null) { 1116 mBackgroundFade.start(); 1117 BitmapScreenNail.disableDrawPlaceholder(); 1118 mOpenAnimationRect = 1119 albumPageTransition == MSG_ALBUMPAGE_NONE ? 1120 (Rect) mData.getParcelable(KEY_OPEN_ANIMATION_RECT) : 1121 (Rect) transitions.get(KEY_OPEN_ANIMATION_RECT); 1122 mPhotoView.setOpenAnimationRect(mOpenAnimationRect); 1123 mBackgroundFade.start(); 1124 } 1125 } 1126 1127 @Override 1128 protected void onResume() { 1129 super.onResume(); 1130 1131 if (mModel == null) { 1132 mActivity.getStateManager().finishState(this); 1133 return; 1134 } 1135 transitionFromAlbumPageIfNeeded(); 1136 1137 mActivity.getGLRoot().freeze(); 1138 mIsActive = true; 1139 setContentPane(mRootPane); 1140 1141 mModel.resume(); 1142 mPhotoView.resume(); 1143 mActionBar.setDisplayOptions( 1144 ((mSecureAlbum == null) && (mSetPathString != null)), true); 1145 mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener); 1146 1147 if (mAppBridge != null && !mHasActivityResult) { 1148 mPhotoView.resetToFirstPicture(); 1149 } 1150 mHasActivityResult = false; 1151 mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT); 1152 } 1153 1154 @Override 1155 protected void onDestroy() { 1156 if (mAppBridge != null) { 1157 mAppBridge.setServer(null); 1158 mScreenNailItem.setScreenNail(null); 1159 mAppBridge.detachScreenNail(); 1160 mAppBridge = null; 1161 mScreenNailSet = null; 1162 mScreenNailItem = null; 1163 } 1164 mOrientationManager.removeListener(this); 1165 mActivity.getGLRoot().setOrientationSource(null); 1166 if (mBottomControls != null) mBottomControls.cleanup(); 1167 1168 // Remove all pending messages. 1169 mHandler.removeCallbacksAndMessages(null); 1170 super.onDestroy(); 1171 } 1172 1173 private class MyDetailsSource implements DetailsSource { 1174 1175 @Override 1176 public MediaDetails getDetails() { 1177 return mModel.getMediaItem(0).getDetails(); 1178 } 1179 1180 @Override 1181 public int size() { 1182 return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1; 1183 } 1184 1185 @Override 1186 public int setIndex() { 1187 return mModel.getCurrentIndex(); 1188 } 1189 } 1190} 1191