PhotoPage.java revision 56bdcb7272f222fde5e4c6fc133cb490b31c7667
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.View; 33import android.view.View.OnClickListener; 34import android.view.animation.AccelerateInterpolator; 35import android.widget.ImageView; 36import android.widget.RelativeLayout; 37import android.widget.Toast; 38 39import com.actionbarsherlock.app.ActionBar.OnMenuVisibilityListener; 40import com.actionbarsherlock.view.Menu; 41import com.actionbarsherlock.view.MenuItem; 42import com.android.gallery3d.R; 43import com.android.gallery3d.anim.FloatAnimation; 44import com.android.gallery3d.common.ApiHelper; 45import com.android.gallery3d.common.Utils; 46import com.android.gallery3d.data.DataManager; 47import com.android.gallery3d.data.FilterDeleteSet; 48import com.android.gallery3d.data.MediaDetails; 49import com.android.gallery3d.data.MediaItem; 50import com.android.gallery3d.data.MediaObject; 51import com.android.gallery3d.data.MediaSet; 52import com.android.gallery3d.data.MtpSource; 53import com.android.gallery3d.data.Path; 54import com.android.gallery3d.data.SecureAlbum; 55import com.android.gallery3d.data.SecureSource; 56import com.android.gallery3d.data.SnailAlbum; 57import com.android.gallery3d.data.SnailItem; 58import com.android.gallery3d.data.SnailSource; 59import com.android.gallery3d.picasasource.PicasaSource; 60import com.android.gallery3d.ui.AnimationTime; 61import com.android.gallery3d.ui.BitmapScreenNail; 62import com.android.gallery3d.ui.DetailsHelper; 63import com.android.gallery3d.ui.DetailsHelper.CloseListener; 64import com.android.gallery3d.ui.DetailsHelper.DetailsSource; 65import com.android.gallery3d.ui.GLCanvas; 66import com.android.gallery3d.ui.GLRoot; 67import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; 68import com.android.gallery3d.ui.GLView; 69import com.android.gallery3d.ui.ImportCompleteListener; 70import com.android.gallery3d.ui.MenuExecutor; 71import com.android.gallery3d.ui.PhotoFallbackEffect; 72import com.android.gallery3d.ui.PhotoView; 73import com.android.gallery3d.ui.PreparePageFadeoutTexture; 74import com.android.gallery3d.ui.RawTexture; 75import com.android.gallery3d.ui.SelectionManager; 76import com.android.gallery3d.ui.SynchronizedHandler; 77import com.android.gallery3d.util.GalleryUtils; 78import com.android.gallery3d.util.LightCycleHelper; 79import com.android.gallery3d.util.MediaSetUtils; 80 81public class PhotoPage extends ActivityState implements 82 PhotoView.Listener, OrientationManager.Listener, AppBridge.Server { 83 private static final String TAG = "PhotoPage"; 84 85 private static final int MSG_HIDE_BARS = 1; 86 private static final int MSG_LOCK_ORIENTATION = 2; 87 private static final int MSG_UNLOCK_ORIENTATION = 3; 88 private static final int MSG_ON_FULL_SCREEN_CHANGED = 4; 89 private static final int MSG_UPDATE_ACTION_BAR = 5; 90 private static final int MSG_UNFREEZE_GLROOT = 6; 91 private static final int MSG_WANT_BARS = 7; 92 private static final int MSG_REFRESH_GRID_BUTTON = 8; 93 private static final int MSG_REFRESH_EDIT_BUTTON = 9; 94 95 private static final int HIDE_BARS_TIMEOUT = 3500; 96 private static final int UNFREEZE_GLROOT_TIMEOUT = 250; 97 98 private static final int REQUEST_SLIDESHOW = 1; 99 private static final int REQUEST_CROP = 2; 100 private static final int REQUEST_CROP_PICASA = 3; 101 private static final int REQUEST_EDIT = 4; 102 private static final int REQUEST_PLAY_VIDEO = 5; 103 private static final int REQUEST_TRIM = 6; 104 105 public static final String KEY_MEDIA_SET_PATH = "media-set-path"; 106 public static final String KEY_MEDIA_ITEM_PATH = "media-item-path"; 107 public static final String KEY_INDEX_HINT = "index-hint"; 108 public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect"; 109 public static final String KEY_APP_BRIDGE = "app-bridge"; 110 public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up"; 111 public static final String KEY_START_IN_FILMSTRIP = "start-in-filmstrip"; 112 public static final String KEY_RETURN_INDEX_HINT = "return-index-hint"; 113 114 public static final String KEY_ALBUMPAGE_TRANSITION = "albumpage-transition"; 115 public static final int MSG_ALBUMPAGE_NONE = 0; 116 public static final int MSG_ALBUMPAGE_STARTED = 1; 117 public static final int MSG_ALBUMPAGE_RESUMED = 2; 118 public static final int MSG_ALBUMPAGE_PICKED = 4; 119 120 public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit"; 121 122 private GalleryApp mApplication; 123 private SelectionManager mSelectionManager; 124 125 private PhotoView mPhotoView; 126 private PhotoPage.Model mModel; 127 private DetailsHelper mDetailsHelper; 128 private boolean mShowDetails; 129 130 // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied. 131 // E.g., viewing a photo in gmail attachment 132 private FilterDeleteSet mMediaSet; 133 134 // The mediaset used by camera launched from secure lock screen. 135 private SecureAlbum mSecureAlbum; 136 137 private int mCurrentIndex = 0; 138 private Handler mHandler; 139 private boolean mShowBars = true; 140 private volatile boolean mActionBarAllowed = true; 141 private GalleryActionBar mActionBar; 142 private boolean mIsMenuVisible; 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_EDIT_BUTTON: { 261 refreshEditButton(); 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 // Set the flag to be on top of the lock screen. 331 mFlags |= FLAG_SHOW_WHEN_LOCKED; 332 } 333 334 // Combine the original MediaSet with the one for ScreenNail 335 // from AppBridge. 336 mSetPathString = "/combo/item/{" + screenNailSetPath + 337 "," + mSetPathString + "}"; 338 339 // Start from the screen nail. 340 itemPath = screenNailItemPath; 341 } 342 343 MediaSet originalSet = mActivity.getDataManager() 344 .getMediaSet(mSetPathString); 345 mSelectionManager.setSourceMediaSet(originalSet); 346 mSetPathString = "/filter/delete/{" + mSetPathString + "}"; 347 mMediaSet = (FilterDeleteSet) mActivity.getDataManager() 348 .getMediaSet(mSetPathString); 349 mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0); 350 if (mMediaSet == null) { 351 Log.w(TAG, "failed to restore " + mSetPathString); 352 } 353 if (itemPath == null) { 354 if (mMediaSet.getMediaItemCount() > 0) { 355 itemPath = mMediaSet.getMediaItem(mCurrentIndex, 1) 356 .get(0).getPath(); 357 } else { 358 return; 359 } 360 } 361 PhotoDataAdapter pda = new PhotoDataAdapter( 362 mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex, 363 mAppBridge == null ? -1 : 0, 364 mAppBridge == null ? false : mAppBridge.isPanorama(), 365 mAppBridge == null ? false : mAppBridge.isStaticCamera()); 366 mModel = pda; 367 mPhotoView.setModel(mModel); 368 369 pda.setDataListener(new PhotoDataAdapter.DataListener() { 370 371 @Override 372 public void onPhotoChanged(int index, Path item) { 373 mCurrentIndex = index; 374 if (item != null) { 375 MediaItem photo = mModel.getMediaItem(0); 376 if (photo != null) updateCurrentPhoto(photo); 377 } 378 updateBars(); 379 } 380 381 @Override 382 public void onLoadingFinished() { 383 if (!mModel.isEmpty()) { 384 MediaItem photo = mModel.getMediaItem(0); 385 if (photo != null) updateCurrentPhoto(photo); 386 } else if (mIsActive) { 387 // We only want to finish the PhotoPage if there is no 388 // deletion that the user can undo. 389 if (mMediaSet.getNumberOfDeletions() == 0) { 390 mActivity.getStateManager().finishState( 391 PhotoPage.this); 392 } 393 } 394 } 395 396 @Override 397 public void onLoadingStarted() { 398 } 399 }); 400 } else { 401 // Get default media set by the URI 402 MediaItem mediaItem = (MediaItem) 403 mActivity.getDataManager().getMediaObject(itemPath); 404 mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem); 405 mPhotoView.setModel(mModel); 406 updateCurrentPhoto(mediaItem); 407 } 408 409 mPhotoView.setFilmMode(mStartInFilmstrip && mMediaSet.getMediaItemCount() > 1); 410 setupEditButton(); 411 } 412 413 private ImageView mEditButton; 414 private void setupEditButton() { 415 if (mSecureAlbum != null) return; 416 RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity) 417 .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root); 418 if (galleryRoot == null) return; 419 420 mEditButton = new ImageView(mActivity); 421 mEditButton.setImageResource(R.drawable.photoeditor_artistic); 422 mEditButton.setOnClickListener(new OnClickListener() { 423 @Override 424 public void onClick(View arg0) { 425 launchPhotoEditor(); 426 } 427 }); 428 RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( 429 RelativeLayout.LayoutParams.WRAP_CONTENT, 430 RelativeLayout.LayoutParams.WRAP_CONTENT); 431 lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); 432 galleryRoot.addView(mEditButton, lp); 433 refreshEditButton(); 434 } 435 436 private void cleanupEditButton() { 437 if (mEditButton == null) return; 438 RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity) 439 .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root); 440 if (galleryRoot == null) return; 441 galleryRoot.removeView(mEditButton); 442 mEditButton = null; 443 } 444 445 private void refreshEditButton() { 446 if (mEditButton == null) return; 447 if (mShowBars && mCurrentPhoto != null && !mPhotoView.getFilmMode() 448 && mCurrentPhoto.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE) { 449 mEditButton.setVisibility(View.VISIBLE); 450 } else { 451 mEditButton.setVisibility(View.GONE); 452 } 453 } 454 455 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN) 456 private void setNfcBeamPushUris(Uri[] uris) { 457 if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) { 458 mNfcAdapter.setBeamPushUris(uris, mActivity); 459 } 460 } 461 462 private Intent createShareIntent(Path path) { 463 DataManager manager = mActivity.getDataManager(); 464 int type = manager.getMediaType(path); 465 Intent intent = new Intent(Intent.ACTION_SEND); 466 intent.setType(MenuExecutor.getMimeType(type)); 467 Uri uri = manager.getContentUri(path); 468 intent.putExtra(Intent.EXTRA_STREAM, uri); 469 return intent; 470 471 } 472 473 private void launchPhotoEditor() { 474 MediaItem current = mModel.getMediaItem(0); 475 if (current == null) return; 476 477 Intent intent = new Intent(ACTION_NEXTGEN_EDIT); 478 intent.setData(mActivity.getDataManager().getContentUri(current.getPath())).setFlags( 479 Intent.FLAG_GRANT_READ_URI_PERMISSION); 480 if (mActivity.getPackageManager() 481 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) { 482 intent.setAction(Intent.ACTION_EDIT); 483 } 484 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null), 485 REQUEST_EDIT); 486 } 487 488 private void updateShareURI(Path path) { 489 DataManager manager = mActivity.getDataManager(); 490 Uri uri = manager.getContentUri(path); 491 mActionBar.setShareIntent(createShareIntent(path)); 492 setNfcBeamPushUris(new Uri[]{uri}); 493 } 494 495 private void updateCurrentPhoto(MediaItem photo) { 496 if (mCurrentPhoto == photo) return; 497 mCurrentPhoto = photo; 498 if (mCurrentPhoto == null) return; 499 updateMenuOperations(); 500 updateTitle(); 501 refreshEditButton(); 502 if (mShowDetails) { 503 mDetailsHelper.reloadDetails(); 504 } 505 if ((mSecureAlbum == null) 506 && (photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) { 507 updateShareURI(photo.getPath()); 508 } 509 } 510 511 private void updateTitle() { 512 if (mCurrentPhoto == null) return; 513 boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean( 514 R.bool.show_action_bar_title); 515 if (showTitle && mCurrentPhoto.getName() != null) { 516 mActionBar.setTitle(mCurrentPhoto.getName()); 517 } else { 518 mActionBar.setTitle(""); 519 } 520 } 521 522 private void updateMenuOperations() { 523 Menu menu = mActionBar.getMenu(); 524 525 // it could be null if onCreateActionBar has not been called yet 526 if (menu == null) return; 527 528 setGridButtonVisibility(mPhotoView.getFilmMode()); 529 530 MenuItem item = menu.findItem(R.id.action_slideshow); 531 item.setVisible((mSecureAlbum == null) && canDoSlideShow()); 532 if (mCurrentPhoto == null) return; 533 534 int supportedOperations = mCurrentPhoto.getSupportedOperations(); 535 if (mSecureAlbum != null) { 536 supportedOperations &= MediaObject.SUPPORT_DELETE; 537 } else if (!GalleryUtils.isEditorAvailable(mActivity, "image/*")) { 538 supportedOperations &= ~MediaObject.SUPPORT_EDIT; 539 } 540 MenuExecutor.updateMenuOperation(menu, supportedOperations); 541 } 542 543 private boolean canDoSlideShow() { 544 if (mMediaSet == null || mCurrentPhoto == null) { 545 return false; 546 } 547 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) { 548 return false; 549 } 550 if (MtpSource.isMtpPath(mOriginalSetPathString)) { 551 return false; 552 } 553 return true; 554 } 555 556 ////////////////////////////////////////////////////////////////////////// 557 // Action Bar show/hide management 558 ////////////////////////////////////////////////////////////////////////// 559 560 private void showBars() { 561 if (mShowBars) return; 562 mShowBars = true; 563 mOrientationManager.unlockOrientation(); 564 mActionBar.show(); 565 mActivity.getGLRoot().setLightsOutMode(false); 566 refreshHidingMessage(); 567 refreshEditButton(); 568 } 569 570 private void hideBars() { 571 if (!mShowBars) return; 572 mShowBars = false; 573 mActionBar.hide(); 574 mActivity.getGLRoot().setLightsOutMode(true); 575 mHandler.removeMessages(MSG_HIDE_BARS); 576 refreshEditButton(); 577 } 578 579 private void refreshHidingMessage() { 580 mHandler.removeMessages(MSG_HIDE_BARS); 581 if (!mIsMenuVisible && !mPhotoView.getFilmMode()) { 582 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT); 583 } 584 } 585 586 private boolean canShowBars() { 587 // No bars if we are showing camera preview. 588 if (mAppBridge != null && mCurrentIndex == 0 589 && !mPhotoView.getFilmMode()) return false; 590 591 // No bars if it's not allowed. 592 if (!mActionBarAllowed) return false; 593 594 return true; 595 } 596 597 private void wantBars() { 598 if (canShowBars()) showBars(); 599 } 600 601 private void toggleBars() { 602 if (mShowBars) { 603 hideBars(); 604 } else { 605 if (canShowBars()) showBars(); 606 } 607 } 608 609 private void updateBars() { 610 if (!canShowBars()) { 611 hideBars(); 612 } 613 } 614 615 @Override 616 public void onOrientationCompensationChanged() { 617 mActivity.getGLRoot().requestLayoutContentPane(); 618 } 619 620 @Override 621 protected void onBackPressed() { 622 if (mShowDetails) { 623 hideDetails(); 624 } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) { 625 // We are leaving this page. Set the result now. 626 setResult(); 627 if (mStartInFilmstrip && !mPhotoView.getFilmMode()) { 628 mPhotoView.setFilmMode(true); 629 } else if (mTreatBackAsUp) { 630 onUpPressed(); 631 } else { 632 super.onBackPressed(); 633 } 634 } 635 } 636 637 private void onUpPressed() { 638 if (mStartInFilmstrip && !mPhotoView.getFilmMode()) { 639 mPhotoView.setFilmMode(true); 640 return; 641 } 642 643 if (mActivity.getStateManager().getStateCount() > 1) { 644 setResult(); 645 super.onBackPressed(); 646 return; 647 } 648 649 if (mOriginalSetPathString == null) return; 650 651 if (mAppBridge == null) { 652 // We're in view mode so set up the stacks on our own. 653 Bundle data = new Bundle(getData()); 654 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 655 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 656 mActivity.getDataManager().getTopSetPath( 657 DataManager.INCLUDE_ALL)); 658 mActivity.getStateManager().switchState(this, AlbumPage.class, data); 659 } else { 660 // Start the real gallery activity to view the camera roll. 661 Uri uri = Uri.parse("content://media/external/file?bucketId=" 662 + MediaSetUtils.CAMERA_BUCKET_ID); 663 Intent intent = new Intent(Intent.ACTION_VIEW); 664 intent.setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE + "/image"); 665 ((Activity) mActivity).startActivity(intent); 666 } 667 } 668 669 private void setResult() { 670 Intent result = null; 671 result = new Intent(); 672 result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex); 673 setStateResult(Activity.RESULT_OK, result); 674 } 675 676 ////////////////////////////////////////////////////////////////////////// 677 // AppBridge.Server interface 678 ////////////////////////////////////////////////////////////////////////// 679 680 @Override 681 public void setCameraRelativeFrame(Rect frame) { 682 mPhotoView.setCameraRelativeFrame(frame); 683 } 684 685 @Override 686 public boolean switchWithCaptureAnimation(int offset) { 687 return mPhotoView.switchWithCaptureAnimation(offset); 688 } 689 690 @Override 691 public void setSwipingEnabled(boolean enabled) { 692 mPhotoView.setSwipingEnabled(enabled); 693 } 694 695 @Override 696 public void notifyScreenNailChanged() { 697 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 698 mScreenNailSet.notifyChange(); 699 } 700 701 @Override 702 public void addSecureAlbumItem(boolean isVideo, int id) { 703 mSecureAlbum.addMediaItem(isVideo, id); 704 } 705 706 @Override 707 protected boolean onCreateActionBar(Menu menu) { 708 mActionBar.createActionBarMenu(R.menu.photo, menu); 709 updateMenuOperations(); 710 updateTitle(); 711 return true; 712 } 713 714 private MenuExecutor.ProgressListener mConfirmDialogListener = 715 new MenuExecutor.ProgressListener() { 716 @Override 717 public void onProgressUpdate(int index) {} 718 719 @Override 720 public void onProgressComplete(int result) {} 721 722 @Override 723 public void onConfirmDialogShown() { 724 mHandler.removeMessages(MSG_HIDE_BARS); 725 } 726 727 @Override 728 public void onConfirmDialogDismissed(boolean confirmed) { 729 refreshHidingMessage(); 730 } 731 732 @Override 733 public void onProgressStart() {} 734 }; 735 736 @Override 737 protected boolean onItemSelected(MenuItem item) { 738 if (mModel == null) return true; 739 refreshHidingMessage(); 740 MediaItem current = mModel.getMediaItem(0); 741 742 if (current == null) { 743 // item is not ready, ignore 744 return true; 745 } 746 747 int currentIndex = mModel.getCurrentIndex(); 748 Path path = current.getPath(); 749 750 DataManager manager = mActivity.getDataManager(); 751 int action = item.getItemId(); 752 String confirmMsg = null; 753 switch (action) { 754 case android.R.id.home: { 755 onUpPressed(); 756 return true; 757 } 758 case R.id.action_grid: { 759 if (mStartedFromAlbumPage) { 760 onUpPressed(); 761 } else { 762 preparePhotoFallbackView(); 763 Bundle data = new Bundle(getData()); 764 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 765 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 766 mActivity.getDataManager().getTopSetPath( 767 DataManager.INCLUDE_ALL)); 768 mActivity.getTransitionStore().put( 769 KEY_RETURN_INDEX_HINT, mCurrentIndex); 770 mActivity.getStateManager().startState(AlbumPage.class, data); 771 } 772 return true; 773 } 774 case R.id.action_slideshow: { 775 Bundle data = new Bundle(); 776 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString()); 777 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString()); 778 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex); 779 data.putBoolean(SlideshowPage.KEY_REPEAT, true); 780 mActivity.getStateManager().startStateForResult( 781 SlideshowPage.class, REQUEST_SLIDESHOW, data); 782 return true; 783 } 784 case R.id.action_crop: { 785 Activity activity = mActivity; 786 Intent intent = new Intent(CropImage.CROP_ACTION); 787 intent.setClass(activity, CropImage.class); 788 intent.setData(manager.getContentUri(path)); 789 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current) 790 ? REQUEST_CROP_PICASA 791 : REQUEST_CROP); 792 return true; 793 } 794 case R.id.action_trim: { 795 Intent intent = new Intent(mActivity, TrimVideo.class); 796 intent.setData(manager.getContentUri(path)); 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_VIEW_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(enabled); 1086 } 1087 1088 public void onFilmModeChanged(boolean enabled) { 1089 mHandler.sendEmptyMessage(MSG_REFRESH_GRID_BUTTON); 1090 mHandler.sendEmptyMessage(MSG_REFRESH_EDIT_BUTTON); 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 cleanupEditButton(); 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