PhotoPage.java revision a9b52df63ae6edbe0f2158bfc3c701fde41782c1
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.LocalImage; 49import com.android.gallery3d.data.MediaDetails; 50import com.android.gallery3d.data.MediaItem; 51import com.android.gallery3d.data.MediaObject; 52import com.android.gallery3d.data.MediaSet; 53import com.android.gallery3d.data.MtpSource; 54import com.android.gallery3d.data.Path; 55import com.android.gallery3d.data.SecureAlbum; 56import com.android.gallery3d.data.SecureSource; 57import com.android.gallery3d.data.SnailAlbum; 58import com.android.gallery3d.data.SnailItem; 59import com.android.gallery3d.data.SnailSource; 60import com.android.gallery3d.picasasource.PicasaSource; 61import com.android.gallery3d.ui.AnimationTime; 62import com.android.gallery3d.ui.BitmapScreenNail; 63import com.android.gallery3d.ui.DetailsHelper; 64import com.android.gallery3d.ui.DetailsHelper.CloseListener; 65import com.android.gallery3d.ui.DetailsHelper.DetailsSource; 66import com.android.gallery3d.ui.GLCanvas; 67import com.android.gallery3d.ui.GLRoot; 68import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; 69import com.android.gallery3d.ui.GLView; 70import com.android.gallery3d.ui.ImportCompleteListener; 71import com.android.gallery3d.ui.MenuExecutor; 72import com.android.gallery3d.ui.PhotoFallbackEffect; 73import com.android.gallery3d.ui.PhotoView; 74import com.android.gallery3d.ui.PreparePageFadeoutTexture; 75import com.android.gallery3d.ui.RawTexture; 76import com.android.gallery3d.ui.SelectionManager; 77import com.android.gallery3d.ui.SynchronizedHandler; 78import com.android.gallery3d.util.GalleryUtils; 79import com.android.gallery3d.util.LightCycleHelper; 80import com.android.gallery3d.util.MediaSetUtils; 81 82public class PhotoPage extends ActivityState implements 83 PhotoView.Listener, OrientationManager.Listener, AppBridge.Server { 84 private static final String TAG = "PhotoPage"; 85 86 private static final int MSG_HIDE_BARS = 1; 87 private static final int MSG_LOCK_ORIENTATION = 2; 88 private static final int MSG_UNLOCK_ORIENTATION = 3; 89 private static final int MSG_ON_FULL_SCREEN_CHANGED = 4; 90 private static final int MSG_UPDATE_ACTION_BAR = 5; 91 private static final int MSG_UNFREEZE_GLROOT = 6; 92 private static final int MSG_WANT_BARS = 7; 93 private static final int MSG_REFRESH_GRID_BUTTON = 8; 94 private static final int MSG_REFRESH_EDIT_BUTTON = 9; 95 96 private static final int HIDE_BARS_TIMEOUT = 3500; 97 private static final int UNFREEZE_GLROOT_TIMEOUT = 250; 98 99 private static final int REQUEST_SLIDESHOW = 1; 100 private static final int REQUEST_CROP = 2; 101 private static final int REQUEST_CROP_PICASA = 3; 102 private static final int REQUEST_EDIT = 4; 103 private static final int REQUEST_PLAY_VIDEO = 5; 104 private static final int REQUEST_TRIM = 6; 105 106 public static final String KEY_MEDIA_SET_PATH = "media-set-path"; 107 public static final String KEY_MEDIA_ITEM_PATH = "media-item-path"; 108 public static final String KEY_INDEX_HINT = "index-hint"; 109 public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect"; 110 public static final String KEY_APP_BRIDGE = "app-bridge"; 111 public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up"; 112 public static final String KEY_START_IN_FILMSTRIP = "start-in-filmstrip"; 113 public static final String KEY_RETURN_INDEX_HINT = "return-index-hint"; 114 115 public static final String KEY_ALBUMPAGE_TRANSITION = "albumpage-transition"; 116 public static final int MSG_ALBUMPAGE_NONE = 0; 117 public static final int MSG_ALBUMPAGE_STARTED = 1; 118 public static final int MSG_ALBUMPAGE_RESUMED = 2; 119 public static final int MSG_ALBUMPAGE_PICKED = 4; 120 121 public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit"; 122 123 private GalleryApp mApplication; 124 private SelectionManager mSelectionManager; 125 126 private PhotoView mPhotoView; 127 private PhotoPage.Model mModel; 128 private DetailsHelper mDetailsHelper; 129 private boolean mShowDetails; 130 131 // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied. 132 // E.g., viewing a photo in gmail attachment 133 private FilterDeleteSet mMediaSet; 134 135 // The mediaset used by camera launched from secure lock screen. 136 private SecureAlbum mSecureAlbum; 137 138 private int mCurrentIndex = 0; 139 private Handler mHandler; 140 private boolean mShowBars = true; 141 private volatile boolean mActionBarAllowed = true; 142 private GalleryActionBar mActionBar; 143 private boolean mIsMenuVisible; 144 private MediaItem mCurrentPhoto = null; 145 private MenuExecutor mMenuExecutor; 146 private boolean mIsActive; 147 private String mSetPathString; 148 // This is the original mSetPathString before adding the camera preview item. 149 private String mOriginalSetPathString; 150 private AppBridge mAppBridge; 151 private SnailItem mScreenNailItem; 152 private SnailAlbum mScreenNailSet; 153 private OrientationManager mOrientationManager; 154 private boolean mHasActivityResult; 155 private boolean mTreatBackAsUp; 156 private boolean mStartInFilmstrip; 157 private boolean mStartedFromAlbumPage; 158 159 private RawTexture mFadeOutTexture; 160 private Rect mOpenAnimationRect; 161 public static final int ANIM_TIME_OPENING = 300; 162 163 // The item that is deleted (but it can still be undeleted before commiting) 164 private Path mDeletePath; 165 private boolean mDeleteIsFocus; // whether the deleted item was in focus 166 167 private NfcAdapter mNfcAdapter; 168 169 private final MyMenuVisibilityListener mMenuVisibilityListener = 170 new MyMenuVisibilityListener(); 171 172 public static interface Model extends PhotoView.Model { 173 public void resume(); 174 public void pause(); 175 public boolean isEmpty(); 176 public void setCurrentPhoto(Path path, int indexHint); 177 } 178 179 private class MyMenuVisibilityListener implements OnMenuVisibilityListener { 180 @Override 181 public void onMenuVisibilityChanged(boolean isVisible) { 182 mIsMenuVisible = isVisible; 183 refreshHidingMessage(); 184 } 185 } 186 187 private static class BackgroundFadeOut extends FloatAnimation { 188 public BackgroundFadeOut() { 189 super(1f, 0f, ANIM_TIME_OPENING); 190 setInterpolator(new AccelerateInterpolator(2f)); 191 } 192 } 193 194 private final FloatAnimation mBackgroundFade = new BackgroundFadeOut(); 195 196 @Override 197 protected int getBackgroundColorId() { 198 return R.color.photo_background; 199 } 200 201 private final GLView mRootPane = new GLView() { 202 @Override 203 protected void renderBackground(GLCanvas view) { 204 if (mFadeOutTexture != null) { 205 if (mBackgroundFade.calculate(AnimationTime.get())) invalidate(); 206 if (!mBackgroundFade.isActive()) { 207 mFadeOutTexture = null; 208 mOpenAnimationRect = null; 209 BitmapScreenNail.enableDrawPlaceholder(); 210 } else { 211 float fadeAlpha = mBackgroundFade.get(); 212 if (fadeAlpha < 1f) { 213 view.clearBuffer(getBackgroundColor()); 214 view.setAlpha(fadeAlpha); 215 } 216 mFadeOutTexture.draw(view, 0, 0); 217 view.setAlpha(1f - fadeAlpha); 218 return; 219 } 220 } 221 view.clearBuffer(getBackgroundColor()); 222 } 223 224 @Override 225 protected void onLayout( 226 boolean changed, int left, int top, int right, int bottom) { 227 mPhotoView.layout(0, 0, right - left, bottom - top); 228 if (mShowDetails) { 229 mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom); 230 } 231 } 232 }; 233 234 @Override 235 public void onCreate(Bundle data, Bundle restoreState) { 236 super.onCreate(data, restoreState); 237 mActionBar = mActivity.getGalleryActionBar(); 238 mSelectionManager = new SelectionManager(mActivity, false); 239 mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager); 240 241 mPhotoView = new PhotoView(mActivity); 242 mPhotoView.setListener(this); 243 mRootPane.addComponent(mPhotoView); 244 mApplication = (GalleryApp) ((Activity) mActivity).getApplication(); 245 mOrientationManager = mActivity.getOrientationManager(); 246 mOrientationManager.addListener(this); 247 mActivity.getGLRoot().setOrientationSource(mOrientationManager); 248 249 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { 250 @Override 251 public void handleMessage(Message message) { 252 switch (message.what) { 253 case MSG_HIDE_BARS: { 254 hideBars(); 255 break; 256 } 257 case MSG_REFRESH_GRID_BUTTON: { 258 setGridButtonVisibility(mPhotoView.getFilmMode()); 259 break; 260 } 261 case MSG_REFRESH_EDIT_BUTTON: { 262 refreshEditButton(); 263 break; 264 } 265 case MSG_LOCK_ORIENTATION: { 266 mOrientationManager.lockOrientation(); 267 break; 268 } 269 case MSG_UNLOCK_ORIENTATION: { 270 mOrientationManager.unlockOrientation(); 271 break; 272 } 273 case MSG_ON_FULL_SCREEN_CHANGED: { 274 mAppBridge.onFullScreenChanged(message.arg1 == 1); 275 break; 276 } 277 case MSG_UPDATE_ACTION_BAR: { 278 updateBars(); 279 break; 280 } 281 case MSG_WANT_BARS: { 282 wantBars(); 283 break; 284 } 285 case MSG_UNFREEZE_GLROOT: { 286 mActivity.getGLRoot().unfreeze(); 287 break; 288 } 289 default: throw new AssertionError(message.what); 290 } 291 } 292 }; 293 294 mSetPathString = data.getString(KEY_MEDIA_SET_PATH); 295 mOriginalSetPathString = mSetPathString; 296 mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext()); 297 String itemPathString = data.getString(KEY_MEDIA_ITEM_PATH); 298 Path itemPath = itemPathString != null ? 299 Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)) : 300 null; 301 mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false); 302 mStartInFilmstrip = 303 data.getBoolean(KEY_START_IN_FILMSTRIP, false); 304 mStartedFromAlbumPage = 305 data.getInt(KEY_ALBUMPAGE_TRANSITION, 306 MSG_ALBUMPAGE_NONE) == MSG_ALBUMPAGE_STARTED; 307 setGridButtonVisibility(!mStartedFromAlbumPage); 308 if (mSetPathString != null) { 309 mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE); 310 if (mAppBridge != null) { 311 mFlags |= FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR; 312 mShowBars = false; 313 314 mAppBridge.setServer(this); 315 mOrientationManager.lockOrientation(); 316 317 // Get the ScreenNail from AppBridge and register it. 318 int id = SnailSource.newId(); 319 Path screenNailSetPath = SnailSource.getSetPath(id); 320 Path screenNailItemPath = SnailSource.getItemPath(id); 321 mScreenNailSet = (SnailAlbum) mActivity.getDataManager() 322 .getMediaObject(screenNailSetPath); 323 mScreenNailItem = (SnailItem) mActivity.getDataManager() 324 .getMediaObject(screenNailItemPath); 325 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 326 327 // Check if the path is a secure album. 328 if (SecureSource.isSecurePath(mSetPathString)) { 329 mSecureAlbum = (SecureAlbum) mActivity.getDataManager() 330 .getMediaSet(mSetPathString); 331 // Set the flag to be on top of the lock screen. 332 mFlags |= FLAG_SHOW_WHEN_LOCKED; 333 } 334 335 // Combine the original MediaSet with the one for ScreenNail 336 // from AppBridge. 337 mSetPathString = "/combo/item/{" + screenNailSetPath + 338 "," + mSetPathString + "}"; 339 340 // Start from the screen nail. 341 itemPath = screenNailItemPath; 342 } 343 344 MediaSet originalSet = mActivity.getDataManager() 345 .getMediaSet(mSetPathString); 346 mSelectionManager.setSourceMediaSet(originalSet); 347 mSetPathString = "/filter/delete/{" + mSetPathString + "}"; 348 mMediaSet = (FilterDeleteSet) mActivity.getDataManager() 349 .getMediaSet(mSetPathString); 350 mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0); 351 if (mMediaSet == null) { 352 Log.w(TAG, "failed to restore " + mSetPathString); 353 } 354 if (itemPath == null) { 355 if (mMediaSet.getMediaItemCount() > 0) { 356 itemPath = mMediaSet.getMediaItem(mCurrentIndex, 1) 357 .get(0).getPath(); 358 } else { 359 return; 360 } 361 } 362 PhotoDataAdapter pda = new PhotoDataAdapter( 363 mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex, 364 mAppBridge == null ? -1 : 0, 365 mAppBridge == null ? false : mAppBridge.isPanorama(), 366 mAppBridge == null ? false : mAppBridge.isStaticCamera()); 367 mModel = pda; 368 mPhotoView.setModel(mModel); 369 370 pda.setDataListener(new PhotoDataAdapter.DataListener() { 371 372 @Override 373 public void onPhotoChanged(int index, Path item) { 374 mCurrentIndex = index; 375 if (item != null) { 376 MediaItem photo = mModel.getMediaItem(0); 377 if (photo != null) updateCurrentPhoto(photo); 378 } 379 updateBars(); 380 } 381 382 @Override 383 public void onLoadingFinished() { 384 if (!mModel.isEmpty()) { 385 MediaItem photo = mModel.getMediaItem(0); 386 if (photo != null) updateCurrentPhoto(photo); 387 } else if (mIsActive) { 388 // We only want to finish the PhotoPage if there is no 389 // deletion that the user can undo. 390 if (mMediaSet.getNumberOfDeletions() == 0) { 391 mActivity.getStateManager().finishState( 392 PhotoPage.this); 393 } 394 } 395 } 396 397 @Override 398 public void onLoadingStarted() { 399 } 400 }); 401 } else { 402 // Get default media set by the URI 403 MediaItem mediaItem = (MediaItem) 404 mActivity.getDataManager().getMediaObject(itemPath); 405 mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem); 406 mPhotoView.setModel(mModel); 407 updateCurrentPhoto(mediaItem); 408 } 409 410 mPhotoView.setFilmMode(mStartInFilmstrip && mMediaSet.getMediaItemCount() > 1); 411 setupEditButton(); 412 } 413 414 private ImageView mEditButton; 415 private void setupEditButton() { 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 RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity) 438 .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root); 439 if (galleryRoot == null) return; 440 galleryRoot.removeView(mEditButton); 441 mEditButton = null; 442 } 443 444 private void refreshEditButton() { 445 if (mEditButton == null) return; 446 if (mShowBars && mCurrentPhoto != null && !mPhotoView.getFilmMode() 447 && mCurrentPhoto.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE) { 448 mEditButton.setVisibility(View.VISIBLE); 449 } else { 450 mEditButton.setVisibility(View.GONE); 451 } 452 } 453 454 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN) 455 private void setNfcBeamPushUris(Uri[] uris) { 456 if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) { 457 mNfcAdapter.setBeamPushUris(uris, mActivity); 458 } 459 } 460 461 private Intent createShareIntent(Path path) { 462 DataManager manager = mActivity.getDataManager(); 463 int type = manager.getMediaType(path); 464 Intent intent = new Intent(Intent.ACTION_SEND); 465 intent.setType(MenuExecutor.getMimeType(type)); 466 Uri uri = manager.getContentUri(path); 467 intent.putExtra(Intent.EXTRA_STREAM, uri); 468 return intent; 469 470 } 471 472 private void launchPhotoEditor() { 473 MediaItem current = mModel.getMediaItem(0); 474 if (current == null) return; 475 476 Intent intent = new Intent(ACTION_NEXTGEN_EDIT); 477 intent.setData(mActivity.getDataManager().getContentUri(current.getPath())).setFlags( 478 Intent.FLAG_GRANT_READ_URI_PERMISSION); 479 if (mActivity.getPackageManager() 480 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) { 481 intent.setAction(Intent.ACTION_EDIT); 482 } 483 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null), 484 REQUEST_EDIT); 485 } 486 487 private void updateShareURI(Path path) { 488 DataManager manager = mActivity.getDataManager(); 489 Uri uri = manager.getContentUri(path); 490 mActionBar.setShareIntent(createShareIntent(path)); 491 setNfcBeamPushUris(new Uri[]{uri}); 492 } 493 494 private void updateCurrentPhoto(MediaItem photo) { 495 if (mCurrentPhoto == photo) return; 496 mCurrentPhoto = photo; 497 if (mCurrentPhoto == null) return; 498 updateMenuOperations(); 499 updateTitle(); 500 refreshEditButton(); 501 if (mShowDetails) { 502 mDetailsHelper.reloadDetails(); 503 } 504 if ((mSecureAlbum == null) 505 && (photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) { 506 updateShareURI(photo.getPath()); 507 } 508 } 509 510 private void updateTitle() { 511 if (mCurrentPhoto == null) return; 512 boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean( 513 R.bool.show_action_bar_title); 514 if (showTitle && mCurrentPhoto.getName() != null) { 515 mActionBar.setTitle(mCurrentPhoto.getName()); 516 } else { 517 mActionBar.setTitle(""); 518 } 519 } 520 521 private void updateMenuOperations() { 522 Menu menu = mActionBar.getMenu(); 523 524 // it could be null if onCreateActionBar has not been called yet 525 if (menu == null) return; 526 527 setGridButtonVisibility(mPhotoView.getFilmMode()); 528 529 MenuItem item = menu.findItem(R.id.action_slideshow); 530 item.setVisible((mSecureAlbum == null) && canDoSlideShow()); 531 if (mCurrentPhoto == null) return; 532 533 int supportedOperations = mCurrentPhoto.getSupportedOperations(); 534 if (mSecureAlbum != null) { 535 supportedOperations &= MediaObject.SUPPORT_DELETE; 536 } else if (!GalleryUtils.isEditorAvailable(mActivity, "image/*")) { 537 supportedOperations &= ~MediaObject.SUPPORT_EDIT; 538 } 539 MenuExecutor.updateMenuOperation(menu, supportedOperations); 540 } 541 542 private boolean canDoSlideShow() { 543 if (mMediaSet == null || mCurrentPhoto == null) { 544 return false; 545 } 546 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) { 547 return false; 548 } 549 if (MtpSource.isMtpPath(mOriginalSetPathString)) { 550 return false; 551 } 552 return true; 553 } 554 555 ////////////////////////////////////////////////////////////////////////// 556 // Action Bar show/hide management 557 ////////////////////////////////////////////////////////////////////////// 558 559 private void showBars() { 560 if (mShowBars) return; 561 mShowBars = true; 562 mOrientationManager.unlockOrientation(); 563 mActionBar.show(); 564 mActivity.getGLRoot().setLightsOutMode(false); 565 refreshHidingMessage(); 566 refreshEditButton(); 567 } 568 569 private void hideBars() { 570 if (!mShowBars) return; 571 mShowBars = false; 572 mActionBar.hide(); 573 mActivity.getGLRoot().setLightsOutMode(true); 574 mHandler.removeMessages(MSG_HIDE_BARS); 575 refreshEditButton(); 576 } 577 578 private void refreshHidingMessage() { 579 mHandler.removeMessages(MSG_HIDE_BARS); 580 if (!mIsMenuVisible && !mPhotoView.getFilmMode()) { 581 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT); 582 } 583 } 584 585 private boolean canShowBars() { 586 // No bars if we are showing camera preview. 587 if (mAppBridge != null && mCurrentIndex == 0) return false; 588 // No bars if it's not allowed. 589 if (!mActionBarAllowed) return false; 590 591 return true; 592 } 593 594 private void wantBars() { 595 if (canShowBars()) showBars(); 596 } 597 598 private void toggleBars() { 599 if (mShowBars) { 600 hideBars(); 601 } else { 602 if (canShowBars()) showBars(); 603 } 604 } 605 606 private void updateBars() { 607 if (!canShowBars()) { 608 hideBars(); 609 } 610 } 611 612 @Override 613 public void onOrientationCompensationChanged() { 614 mActivity.getGLRoot().requestLayoutContentPane(); 615 } 616 617 @Override 618 protected void onBackPressed() { 619 if (mShowDetails) { 620 hideDetails(); 621 } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) { 622 // We are leaving this page. Set the result now. 623 setResult(); 624 if (mStartInFilmstrip && !mPhotoView.getFilmMode()) { 625 mPhotoView.setFilmMode(true); 626 } else if (mTreatBackAsUp) { 627 onUpPressed(); 628 } else { 629 super.onBackPressed(); 630 } 631 } 632 } 633 634 private void onUpPressed() { 635 if (mStartInFilmstrip && !mPhotoView.getFilmMode()) { 636 mPhotoView.setFilmMode(true); 637 return; 638 } 639 640 if (mActivity.getStateManager().getStateCount() > 1) { 641 setResult(); 642 super.onBackPressed(); 643 return; 644 } 645 646 if (mOriginalSetPathString == null) return; 647 648 if (mAppBridge == null) { 649 // We're in view mode so set up the stacks on our own. 650 Bundle data = new Bundle(getData()); 651 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 652 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 653 mActivity.getDataManager().getTopSetPath( 654 DataManager.INCLUDE_ALL)); 655 mActivity.getStateManager().switchState(this, AlbumPage.class, data); 656 } else { 657 // Start the real gallery activity to view the camera roll. 658 Uri uri = Uri.parse("content://media/external/file?bucketId=" 659 + MediaSetUtils.CAMERA_BUCKET_ID); 660 Intent intent = new Intent(Intent.ACTION_VIEW); 661 intent.setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE + "/image"); 662 ((Activity) mActivity).startActivity(intent); 663 } 664 } 665 666 private void setResult() { 667 Intent result = null; 668 result = new Intent(); 669 result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex); 670 setStateResult(Activity.RESULT_OK, result); 671 } 672 673 ////////////////////////////////////////////////////////////////////////// 674 // AppBridge.Server interface 675 ////////////////////////////////////////////////////////////////////////// 676 677 @Override 678 public void setCameraRelativeFrame(Rect frame) { 679 mPhotoView.setCameraRelativeFrame(frame); 680 } 681 682 @Override 683 public boolean switchWithCaptureAnimation(int offset) { 684 return mPhotoView.switchWithCaptureAnimation(offset); 685 } 686 687 @Override 688 public void setSwipingEnabled(boolean enabled) { 689 mPhotoView.setSwipingEnabled(enabled); 690 } 691 692 @Override 693 public void notifyScreenNailChanged() { 694 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 695 mScreenNailSet.notifyChange(); 696 } 697 698 @Override 699 public void addSecureAlbumItem(boolean isVideo, int id) { 700 mSecureAlbum.addMediaItem(isVideo, id); 701 } 702 703 @Override 704 protected boolean onCreateActionBar(Menu menu) { 705 mActionBar.createActionBarMenu(R.menu.photo, menu); 706 updateMenuOperations(); 707 updateTitle(); 708 return true; 709 } 710 711 private MenuExecutor.ProgressListener mConfirmDialogListener = 712 new MenuExecutor.ProgressListener() { 713 @Override 714 public void onProgressUpdate(int index) {} 715 716 @Override 717 public void onProgressComplete(int result) {} 718 719 @Override 720 public void onConfirmDialogShown() { 721 mHandler.removeMessages(MSG_HIDE_BARS); 722 } 723 724 @Override 725 public void onConfirmDialogDismissed(boolean confirmed) { 726 refreshHidingMessage(); 727 } 728 729 @Override 730 public void onProgressStart() {} 731 }; 732 733 @Override 734 protected boolean onItemSelected(MenuItem item) { 735 if (mModel == null) return true; 736 refreshHidingMessage(); 737 MediaItem current = mModel.getMediaItem(0); 738 739 if (current == null) { 740 // item is not ready, ignore 741 return true; 742 } 743 744 int currentIndex = mModel.getCurrentIndex(); 745 Path path = current.getPath(); 746 747 DataManager manager = mActivity.getDataManager(); 748 int action = item.getItemId(); 749 String confirmMsg = null; 750 switch (action) { 751 case android.R.id.home: { 752 onUpPressed(); 753 return true; 754 } 755 case R.id.action_grid: { 756 if (mStartedFromAlbumPage) { 757 onUpPressed(); 758 } else { 759 preparePhotoFallbackView(); 760 Bundle data = new Bundle(getData()); 761 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 762 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 763 mActivity.getDataManager().getTopSetPath( 764 DataManager.INCLUDE_ALL)); 765 mActivity.getTransitionStore().put( 766 KEY_RETURN_INDEX_HINT, mCurrentIndex); 767 mActivity.getStateManager().startState(AlbumPage.class, data); 768 } 769 return true; 770 } 771 case R.id.action_slideshow: { 772 Bundle data = new Bundle(); 773 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString()); 774 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString()); 775 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex); 776 data.putBoolean(SlideshowPage.KEY_REPEAT, true); 777 mActivity.getStateManager().startStateForResult( 778 SlideshowPage.class, REQUEST_SLIDESHOW, data); 779 return true; 780 } 781 case R.id.action_crop: { 782 Activity activity = mActivity; 783 Intent intent = new Intent(CropImage.CROP_ACTION); 784 intent.setClass(activity, CropImage.class); 785 intent.setData(manager.getContentUri(path)); 786 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current) 787 ? REQUEST_CROP_PICASA 788 : REQUEST_CROP); 789 return true; 790 } 791 case R.id.action_trim: { 792 Intent intent = new Intent(mActivity, TrimVideo.class); 793 intent.setData(manager.getContentUri(path)); 794 mActivity.startActivityForResult(intent, REQUEST_TRIM); 795 return true; 796 } 797 case R.id.action_edit: { 798 launchPhotoEditor(); 799 return true; 800 } 801 case R.id.action_details: { 802 if (mShowDetails) { 803 hideDetails(); 804 } else { 805 showDetails(); 806 } 807 return true; 808 } 809 case R.id.action_delete: 810 confirmMsg = mActivity.getResources().getQuantityString( 811 R.plurals.delete_selection, 1); 812 case R.id.action_setas: 813 case R.id.action_rotate_ccw: 814 case R.id.action_rotate_cw: 815 case R.id.action_show_on_map: 816 mSelectionManager.deSelectAll(); 817 mSelectionManager.toggle(path); 818 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener); 819 return true; 820 case R.id.action_import: 821 mSelectionManager.deSelectAll(); 822 mSelectionManager.toggle(path); 823 mMenuExecutor.onMenuClicked(item, confirmMsg, 824 new ImportCompleteListener(mActivity)); 825 return true; 826 case R.id.action_share: 827 Activity activity = mActivity; 828 Intent intent = createShareIntent(mCurrentPhoto.getPath()); 829 activity.startActivity(Intent.createChooser(intent, 830 activity.getString(R.string.share))); 831 return true; 832 default : 833 return false; 834 } 835 } 836 837 private void hideDetails() { 838 mShowDetails = false; 839 mDetailsHelper.hide(); 840 } 841 842 private void showDetails() { 843 mShowDetails = true; 844 if (mDetailsHelper == null) { 845 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource()); 846 mDetailsHelper.setCloseListener(new CloseListener() { 847 @Override 848 public void onClose() { 849 hideDetails(); 850 } 851 }); 852 } 853 mDetailsHelper.show(); 854 } 855 856 //////////////////////////////////////////////////////////////////////////// 857 // Callbacks from PhotoView 858 //////////////////////////////////////////////////////////////////////////// 859 @Override 860 public void onSingleTapUp(int x, int y) { 861 if (mAppBridge != null) { 862 if (mAppBridge.onSingleTapUp(x, y)) return; 863 } 864 865 MediaItem item = mModel.getMediaItem(0); 866 if (item == null || item == mScreenNailItem) { 867 // item is not ready or it is camera preview, ignore 868 return; 869 } 870 871 boolean playVideo = (mSecureAlbum == null) && 872 ((item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0); 873 boolean viewPanorama = (mSecureAlbum == null) && 874 (item.getSupportedOperations() & MediaItem.SUPPORT_VIEW_PANORAMA) != 0; 875 876 if (playVideo) { 877 // determine if the point is at center (1/6) of the photo view. 878 // (The position of the "play" icon is at center (1/6) of the photo) 879 int w = mPhotoView.getWidth(); 880 int h = mPhotoView.getHeight(); 881 playVideo = (Math.abs(x - w / 2) * 12 <= w) 882 && (Math.abs(y - h / 2) * 12 <= h); 883 } 884 885 if (playVideo) { 886 playVideo(mActivity, item.getPlayUri(), item.getName()); 887 } else if (viewPanorama) { 888 LocalImage img = (LocalImage) item; 889 LightCycleHelper.viewPanorama(mActivity, img.getFilePath()); 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(enabled); 1084 } 1085 1086 public void onFilmModeChanged(boolean enabled) { 1087 mHandler.sendEmptyMessage(MSG_REFRESH_GRID_BUTTON); 1088 mHandler.sendEmptyMessage(MSG_REFRESH_EDIT_BUTTON); 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 cleanupEditButton(); 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