PhotoPage.java revision a9948ef755f665f1327ef80760df3858f1030454
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.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 mSetPathString = data.getString(KEY_MEDIA_SET_PATH); 249 mOriginalSetPathString = mSetPathString; 250 mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext()); 251 String itemPathString = data.getString(KEY_MEDIA_ITEM_PATH); 252 Path itemPath = itemPathString != null ? 253 Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)) : 254 null; 255 mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false); 256 mStartInFilmstrip = 257 data.getBoolean(KEY_START_IN_FILMSTRIP, false); 258 mStartedFromAlbumPage = 259 data.getInt(KEY_ALBUMPAGE_TRANSITION, 260 MSG_ALBUMPAGE_NONE) == MSG_ALBUMPAGE_STARTED; 261 setGridButtonVisibility(!mStartedFromAlbumPage); 262 if (mSetPathString != null) { 263 mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE); 264 if (mAppBridge != null) { 265 mFlags |= FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR; 266 mShowBars = false; 267 268 mAppBridge.setServer(this); 269 mOrientationManager.lockOrientation(); 270 271 // Get the ScreenNail from AppBridge and register it. 272 int id = SnailSource.newId(); 273 Path screenNailSetPath = SnailSource.getSetPath(id); 274 Path screenNailItemPath = SnailSource.getItemPath(id); 275 mScreenNailSet = (SnailAlbum) mActivity.getDataManager() 276 .getMediaObject(screenNailSetPath); 277 mScreenNailItem = (SnailItem) mActivity.getDataManager() 278 .getMediaObject(screenNailItemPath); 279 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 280 281 // Check if the path is a secure album. 282 if (SecureSource.isSecurePath(mSetPathString)) { 283 mSecureAlbum = (SecureAlbum) mActivity.getDataManager() 284 .getMediaSet(mSetPathString); 285 // Set the flag to be on top of the lock screen. 286 mFlags |= FLAG_SHOW_WHEN_LOCKED; 287 } 288 289 // Combine the original MediaSet with the one for ScreenNail 290 // from AppBridge. 291 mSetPathString = "/combo/item/{" + screenNailSetPath + 292 "," + mSetPathString + "}"; 293 294 // Start from the screen nail. 295 itemPath = screenNailItemPath; 296 } 297 298 MediaSet originalSet = mActivity.getDataManager() 299 .getMediaSet(mSetPathString); 300 mSelectionManager.setSourceMediaSet(originalSet); 301 mSetPathString = "/filter/delete/{" + mSetPathString + "}"; 302 mMediaSet = (FilterDeleteSet) mActivity.getDataManager() 303 .getMediaSet(mSetPathString); 304 mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0); 305 if (mMediaSet == null) { 306 Log.w(TAG, "failed to restore " + mSetPathString); 307 } 308 if (itemPath == null) { 309 itemPath = mMediaSet.getMediaItem(mCurrentIndex, 1) 310 .get(0).getPath(); 311 } 312 PhotoDataAdapter pda = new PhotoDataAdapter( 313 mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex, 314 mAppBridge == null ? -1 : 0, 315 mAppBridge == null ? false : mAppBridge.isPanorama(), 316 mAppBridge == null ? false : mAppBridge.isStaticCamera()); 317 mModel = pda; 318 mPhotoView.setModel(mModel); 319 320 pda.setDataListener(new PhotoDataAdapter.DataListener() { 321 322 @Override 323 public void onPhotoChanged(int index, Path item) { 324 mCurrentIndex = index; 325 if (item != null) { 326 MediaItem photo = mModel.getMediaItem(0); 327 if (photo != null) updateCurrentPhoto(photo); 328 } 329 updateBars(); 330 } 331 332 @Override 333 public void onLoadingFinished() { 334 if (!mModel.isEmpty()) { 335 MediaItem photo = mModel.getMediaItem(0); 336 if (photo != null) updateCurrentPhoto(photo); 337 } else if (mIsActive) { 338 // We only want to finish the PhotoPage if there is no 339 // deletion that the user can undo. 340 if (mMediaSet.getNumberOfDeletions() == 0) { 341 mActivity.getStateManager().finishState( 342 PhotoPage.this); 343 } 344 } 345 } 346 347 @Override 348 public void onLoadingStarted() { 349 } 350 }); 351 } else { 352 // Get default media set by the URI 353 MediaItem mediaItem = (MediaItem) 354 mActivity.getDataManager().getMediaObject(itemPath); 355 mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem); 356 mPhotoView.setModel(mModel); 357 updateCurrentPhoto(mediaItem); 358 } 359 360 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { 361 @Override 362 public void handleMessage(Message message) { 363 switch (message.what) { 364 case MSG_HIDE_BARS: { 365 hideBars(); 366 break; 367 } 368 case MSG_REFRESH_GRID_BUTTON: { 369 setGridButtonVisibility(mPhotoView.getFilmMode()); 370 break; 371 } 372 case MSG_REFRESH_EDIT_BUTTON: { 373 refreshEditButton(); 374 break; 375 } 376 case MSG_LOCK_ORIENTATION: { 377 mOrientationManager.lockOrientation(); 378 break; 379 } 380 case MSG_UNLOCK_ORIENTATION: { 381 mOrientationManager.unlockOrientation(); 382 break; 383 } 384 case MSG_ON_FULL_SCREEN_CHANGED: { 385 mAppBridge.onFullScreenChanged(message.arg1 == 1); 386 break; 387 } 388 case MSG_UPDATE_ACTION_BAR: { 389 updateBars(); 390 break; 391 } 392 case MSG_WANT_BARS: { 393 wantBars(); 394 break; 395 } 396 case MSG_UNFREEZE_GLROOT: { 397 mActivity.getGLRoot().unfreeze(); 398 break; 399 } 400 default: throw new AssertionError(message.what); 401 } 402 } 403 }; 404 405 mPhotoView.setFilmMode(mStartInFilmstrip); 406 setupEditButton(); 407 } 408 409 private ImageView mEditButton; 410 private void setupEditButton() { 411 RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity) 412 .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root); 413 if (galleryRoot == null) return; 414 415 mEditButton = new ImageView(mActivity); 416 mEditButton.setImageResource(R.drawable.photoeditor_artistic); 417 mEditButton.setOnClickListener(new OnClickListener() { 418 @Override 419 public void onClick(View arg0) { 420 launchPhotoEditor(); 421 } 422 }); 423 RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams( 424 RelativeLayout.LayoutParams.WRAP_CONTENT, 425 RelativeLayout.LayoutParams.WRAP_CONTENT); 426 lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); 427 galleryRoot.addView(mEditButton, lp); 428 refreshEditButton(); 429 } 430 431 private void cleanupEditButton() { 432 RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity) 433 .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root); 434 if (galleryRoot == null) return; 435 galleryRoot.removeView(mEditButton); 436 mEditButton = null; 437 } 438 439 private void refreshEditButton() { 440 if (mEditButton == null) return; 441 if(mShowBars) { 442 mEditButton.setVisibility(View.VISIBLE); 443 } else { 444 mEditButton.setVisibility(View.GONE); 445 } 446 } 447 448 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN) 449 private void setNfcBeamPushUris(Uri[] uris) { 450 if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) { 451 mNfcAdapter.setBeamPushUris(uris, mActivity); 452 } 453 } 454 455 private Intent createShareIntent(Path path) { 456 DataManager manager = mActivity.getDataManager(); 457 int type = manager.getMediaType(path); 458 Intent intent = new Intent(Intent.ACTION_SEND); 459 intent.setType(MenuExecutor.getMimeType(type)); 460 Uri uri = manager.getContentUri(path); 461 intent.putExtra(Intent.EXTRA_STREAM, uri); 462 return intent; 463 464 } 465 466 private void launchPhotoEditor() { 467 MediaItem current = mModel.getMediaItem(0); 468 if (current == null) return; 469 470 Intent intent = new Intent(ACTION_NEXTGEN_EDIT); 471 intent.setData(mActivity.getDataManager().getContentUri(current.getPath())).setFlags( 472 Intent.FLAG_GRANT_READ_URI_PERMISSION); 473 if (mActivity.getPackageManager() 474 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) { 475 intent.setAction(Intent.ACTION_EDIT); 476 } 477 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null), 478 REQUEST_EDIT); 479 } 480 481 private void updateShareURI(Path path) { 482 DataManager manager = mActivity.getDataManager(); 483 Uri uri = manager.getContentUri(path); 484 mActionBar.setShareIntent(createShareIntent(path)); 485 setNfcBeamPushUris(new Uri[]{uri}); 486 } 487 488 private void updateCurrentPhoto(MediaItem photo) { 489 if (mCurrentPhoto == photo) return; 490 mCurrentPhoto = photo; 491 if (mCurrentPhoto == null) return; 492 updateMenuOperations(); 493 updateTitle(); 494 if (mShowDetails) { 495 mDetailsHelper.reloadDetails(); 496 } 497 if ((mSecureAlbum == null) 498 && (photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) { 499 updateShareURI(photo.getPath()); 500 } 501 } 502 503 private void updateTitle() { 504 if (mCurrentPhoto == null) return; 505 boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean( 506 R.bool.show_action_bar_title); 507 if (showTitle && mCurrentPhoto.getName() != null) { 508 mActionBar.setTitle(mCurrentPhoto.getName()); 509 } else { 510 mActionBar.setTitle(""); 511 } 512 } 513 514 private void updateMenuOperations() { 515 Menu menu = mActionBar.getMenu(); 516 517 // it could be null if onCreateActionBar has not been called yet 518 if (menu == null) return; 519 520 setGridButtonVisibility(mPhotoView.getFilmMode()); 521 522 MenuItem item = menu.findItem(R.id.action_slideshow); 523 item.setVisible((mSecureAlbum == null) && canDoSlideShow()); 524 if (mCurrentPhoto == null) return; 525 526 int supportedOperations = mCurrentPhoto.getSupportedOperations(); 527 if (mSecureAlbum != null) { 528 supportedOperations &= MediaObject.SUPPORT_DELETE; 529 } else if (!GalleryUtils.isEditorAvailable(mActivity, "image/*")) { 530 supportedOperations &= ~MediaObject.SUPPORT_EDIT; 531 } 532 MenuExecutor.updateMenuOperation(menu, supportedOperations); 533 } 534 535 private boolean canDoSlideShow() { 536 if (mMediaSet == null || mCurrentPhoto == null) { 537 return false; 538 } 539 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) { 540 return false; 541 } 542 if (MtpSource.isMtpPath(mOriginalSetPathString)) { 543 return false; 544 } 545 return true; 546 } 547 548 ////////////////////////////////////////////////////////////////////////// 549 // Action Bar show/hide management 550 ////////////////////////////////////////////////////////////////////////// 551 552 private void showBars() { 553 if (mShowBars) return; 554 mShowBars = true; 555 mOrientationManager.unlockOrientation(); 556 mActionBar.show(); 557 mActivity.getGLRoot().setLightsOutMode(false); 558 refreshHidingMessage(); 559 refreshEditButton(); 560 } 561 562 private void hideBars() { 563 if (!mShowBars) return; 564 mShowBars = false; 565 mActionBar.hide(); 566 mActivity.getGLRoot().setLightsOutMode(true); 567 mHandler.removeMessages(MSG_HIDE_BARS); 568 refreshEditButton(); 569 } 570 571 private void refreshHidingMessage() { 572 mHandler.removeMessages(MSG_HIDE_BARS); 573 if (!mIsMenuVisible && !mPhotoView.getFilmMode()) { 574 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT); 575 } 576 } 577 578 private boolean canShowBars() { 579 // No bars if we are showing camera preview. 580 if (mAppBridge != null && mCurrentIndex == 0) return false; 581 // No bars if it's not allowed. 582 if (!mActionBarAllowed) return false; 583 584 return true; 585 } 586 587 private void wantBars() { 588 if (canShowBars()) showBars(); 589 } 590 591 private void toggleBars() { 592 if (mShowBars) { 593 hideBars(); 594 } else { 595 if (canShowBars()) showBars(); 596 } 597 } 598 599 private void updateBars() { 600 if (!canShowBars()) { 601 hideBars(); 602 } 603 } 604 605 @Override 606 public void onOrientationCompensationChanged() { 607 mActivity.getGLRoot().requestLayoutContentPane(); 608 } 609 610 @Override 611 protected void onBackPressed() { 612 if (mShowDetails) { 613 hideDetails(); 614 } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) { 615 // We are leaving this page. Set the result now. 616 setResult(); 617 if (mStartInFilmstrip && !mPhotoView.getFilmMode()) { 618 mPhotoView.setFilmMode(true); 619 } else if (mTreatBackAsUp) { 620 onUpPressed(); 621 } else { 622 super.onBackPressed(); 623 } 624 } 625 } 626 627 private void onUpPressed() { 628 if (mStartInFilmstrip && !mPhotoView.getFilmMode()) { 629 mPhotoView.setFilmMode(true); 630 return; 631 } 632 633 if (mActivity.getStateManager().getStateCount() > 1) { 634 setResult(); 635 super.onBackPressed(); 636 return; 637 } 638 639 if (mOriginalSetPathString == null) return; 640 641 if (mAppBridge == null) { 642 // We're in view mode so set up the stacks on our own. 643 Bundle data = new Bundle(getData()); 644 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 645 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 646 mActivity.getDataManager().getTopSetPath( 647 DataManager.INCLUDE_ALL)); 648 mActivity.getStateManager().switchState(this, AlbumPage.class, data); 649 } else { 650 // Start the real gallery activity to view the camera roll. 651 Uri uri = Uri.parse("content://media/external/file?bucketId=" 652 + MediaSetUtils.CAMERA_BUCKET_ID); 653 Intent intent = new Intent(Intent.ACTION_VIEW); 654 intent.setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE + "/image"); 655 ((Activity) mActivity).startActivity(intent); 656 } 657 } 658 659 private void setResult() { 660 Intent result = null; 661 result = new Intent(); 662 result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex); 663 setStateResult(Activity.RESULT_OK, result); 664 } 665 666 ////////////////////////////////////////////////////////////////////////// 667 // AppBridge.Server interface 668 ////////////////////////////////////////////////////////////////////////// 669 670 @Override 671 public void setCameraRelativeFrame(Rect frame) { 672 mPhotoView.setCameraRelativeFrame(frame); 673 } 674 675 @Override 676 public boolean switchWithCaptureAnimation(int offset) { 677 return mPhotoView.switchWithCaptureAnimation(offset); 678 } 679 680 @Override 681 public void setSwipingEnabled(boolean enabled) { 682 mPhotoView.setSwipingEnabled(enabled); 683 } 684 685 @Override 686 public void notifyScreenNailChanged() { 687 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 688 mScreenNailSet.notifyChange(); 689 } 690 691 @Override 692 public void addSecureAlbumItem(boolean isVideo, int id) { 693 mSecureAlbum.addMediaItem(isVideo, id); 694 } 695 696 @Override 697 protected boolean onCreateActionBar(Menu menu) { 698 mActionBar.createActionBarMenu(R.menu.photo, menu); 699 updateMenuOperations(); 700 updateTitle(); 701 return true; 702 } 703 704 private MenuExecutor.ProgressListener mConfirmDialogListener = 705 new MenuExecutor.ProgressListener() { 706 @Override 707 public void onProgressUpdate(int index) {} 708 709 @Override 710 public void onProgressComplete(int result) {} 711 712 @Override 713 public void onConfirmDialogShown() { 714 mHandler.removeMessages(MSG_HIDE_BARS); 715 } 716 717 @Override 718 public void onConfirmDialogDismissed(boolean confirmed) { 719 refreshHidingMessage(); 720 } 721 722 @Override 723 public void onProgressStart() {} 724 }; 725 726 @Override 727 protected boolean onItemSelected(MenuItem item) { 728 refreshHidingMessage(); 729 MediaItem current = mModel.getMediaItem(0); 730 731 if (current == null) { 732 // item is not ready, ignore 733 return true; 734 } 735 736 int currentIndex = mModel.getCurrentIndex(); 737 Path path = current.getPath(); 738 739 DataManager manager = mActivity.getDataManager(); 740 int action = item.getItemId(); 741 String confirmMsg = null; 742 switch (action) { 743 case android.R.id.home: { 744 onUpPressed(); 745 return true; 746 } 747 case R.id.action_grid: { 748 if (mStartedFromAlbumPage) { 749 onUpPressed(); 750 } else { 751 preparePhotoFallbackView(); 752 Bundle data = new Bundle(getData()); 753 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 754 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 755 mActivity.getDataManager().getTopSetPath( 756 DataManager.INCLUDE_ALL)); 757 mActivity.getTransitionStore().put( 758 KEY_RETURN_INDEX_HINT, mCurrentIndex); 759 mActivity.getStateManager().startState(AlbumPage.class, data); 760 } 761 return true; 762 } 763 case R.id.action_slideshow: { 764 Bundle data = new Bundle(); 765 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString()); 766 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString()); 767 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex); 768 data.putBoolean(SlideshowPage.KEY_REPEAT, true); 769 mActivity.getStateManager().startStateForResult( 770 SlideshowPage.class, REQUEST_SLIDESHOW, data); 771 return true; 772 } 773 case R.id.action_crop: { 774 Activity activity = mActivity; 775 Intent intent = new Intent(CropImage.CROP_ACTION); 776 intent.setClass(activity, CropImage.class); 777 intent.setData(manager.getContentUri(path)); 778 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current) 779 ? REQUEST_CROP_PICASA 780 : REQUEST_CROP); 781 return true; 782 } 783 case R.id.action_trim: { 784 Intent intent = new Intent(mActivity, TrimVideo.class); 785 intent.setData(manager.getContentUri(path)); 786 mActivity.startActivityForResult(intent, REQUEST_TRIM); 787 return true; 788 } 789 case R.id.action_edit: { 790 launchPhotoEditor(); 791 return true; 792 } 793 case R.id.action_details: { 794 if (mShowDetails) { 795 hideDetails(); 796 } else { 797 showDetails(); 798 } 799 return true; 800 } 801 case R.id.action_delete: 802 confirmMsg = mActivity.getResources().getQuantityString( 803 R.plurals.delete_selection, 1); 804 case R.id.action_setas: 805 case R.id.action_rotate_ccw: 806 case R.id.action_rotate_cw: 807 case R.id.action_show_on_map: 808 mSelectionManager.deSelectAll(); 809 mSelectionManager.toggle(path); 810 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener); 811 return true; 812 case R.id.action_import: 813 mSelectionManager.deSelectAll(); 814 mSelectionManager.toggle(path); 815 mMenuExecutor.onMenuClicked(item, confirmMsg, 816 new ImportCompleteListener(mActivity)); 817 return true; 818 case R.id.action_share: 819 Activity activity = mActivity; 820 Intent intent = createShareIntent(mCurrentPhoto.getPath()); 821 activity.startActivity(Intent.createChooser(intent, 822 activity.getString(R.string.share))); 823 return true; 824 default : 825 return false; 826 } 827 } 828 829 private void hideDetails() { 830 mShowDetails = false; 831 mDetailsHelper.hide(); 832 } 833 834 private void showDetails() { 835 mShowDetails = true; 836 if (mDetailsHelper == null) { 837 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource()); 838 mDetailsHelper.setCloseListener(new CloseListener() { 839 @Override 840 public void onClose() { 841 hideDetails(); 842 } 843 }); 844 } 845 mDetailsHelper.show(); 846 } 847 848 //////////////////////////////////////////////////////////////////////////// 849 // Callbacks from PhotoView 850 //////////////////////////////////////////////////////////////////////////// 851 @Override 852 public void onSingleTapUp(int x, int y) { 853 if (mAppBridge != null) { 854 if (mAppBridge.onSingleTapUp(x, y)) return; 855 } 856 857 MediaItem item = mModel.getMediaItem(0); 858 if (item == null || item == mScreenNailItem) { 859 // item is not ready or it is camera preview, ignore 860 return; 861 } 862 863 boolean playVideo = (mSecureAlbum == null) && 864 ((item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0); 865 boolean viewPanorama = (mSecureAlbum == null) && 866 (item.getSupportedOperations() & MediaItem.SUPPORT_VIEW_PANORAMA) != 0; 867 868 if (playVideo) { 869 // determine if the point is at center (1/6) of the photo view. 870 // (The position of the "play" icon is at center (1/6) of the photo) 871 int w = mPhotoView.getWidth(); 872 int h = mPhotoView.getHeight(); 873 playVideo = (Math.abs(x - w / 2) * 12 <= w) 874 && (Math.abs(y - h / 2) * 12 <= h); 875 } 876 877 if (playVideo) { 878 playVideo(mActivity, item.getPlayUri(), item.getName()); 879 } else if (viewPanorama) { 880 LocalImage img = (LocalImage) item; 881 LightCycleHelper.viewPanorama(mActivity, img.getFilePath()); 882 } else { 883 toggleBars(); 884 } 885 } 886 887 @Override 888 public void lockOrientation() { 889 mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION); 890 } 891 892 @Override 893 public void unlockOrientation() { 894 mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION); 895 } 896 897 @Override 898 public void onActionBarAllowed(boolean allowed) { 899 mActionBarAllowed = allowed; 900 mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR); 901 } 902 903 @Override 904 public void onActionBarWanted() { 905 mHandler.sendEmptyMessage(MSG_WANT_BARS); 906 } 907 908 @Override 909 public void onFullScreenChanged(boolean full) { 910 Message m = mHandler.obtainMessage( 911 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0); 912 m.sendToTarget(); 913 } 914 915 // How we do delete/undo: 916 // 917 // When the user choose to delete a media item, we just tell the 918 // FilterDeleteSet to hide that item. If the user choose to undo it, we 919 // again tell FilterDeleteSet not to hide it. If the user choose to commit 920 // the deletion, we then actually delete the media item. 921 @Override 922 public void onDeleteImage(Path path, int offset) { 923 onCommitDeleteImage(); // commit the previous deletion 924 mDeletePath = path; 925 mDeleteIsFocus = (offset == 0); 926 mMediaSet.addDeletion(path, mCurrentIndex + offset); 927 } 928 929 @Override 930 public void onUndoDeleteImage() { 931 if (mDeletePath == null) return; 932 // If the deletion was done on the focused item, we want the model to 933 // focus on it when it is undeleted. 934 if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath); 935 mMediaSet.removeDeletion(mDeletePath); 936 mDeletePath = null; 937 } 938 939 @Override 940 public void onCommitDeleteImage() { 941 if (mDeletePath == null) return; 942 mSelectionManager.deSelectAll(); 943 mSelectionManager.toggle(mDeletePath); 944 mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false); 945 mDeletePath = null; 946 } 947 948 public static void playVideo(Activity activity, Uri uri, String title) { 949 try { 950 Intent intent = new Intent(Intent.ACTION_VIEW) 951 .setDataAndType(uri, "video/*") 952 .putExtra(Intent.EXTRA_TITLE, title) 953 .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true); 954 activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO); 955 } catch (ActivityNotFoundException e) { 956 Toast.makeText(activity, activity.getString(R.string.video_err), 957 Toast.LENGTH_SHORT).show(); 958 } 959 } 960 961 private void setCurrentPhotoByIntent(Intent intent) { 962 if (intent == null) return; 963 Path path = mApplication.getDataManager() 964 .findPathByUri(intent.getData(), intent.getType()); 965 if (path != null) { 966 mModel.setCurrentPhoto(path, mCurrentIndex); 967 } 968 } 969 970 @Override 971 protected void onStateResult(int requestCode, int resultCode, Intent data) { 972 mHasActivityResult = true; 973 switch (requestCode) { 974 case REQUEST_EDIT: 975 setCurrentPhotoByIntent(data); 976 break; 977 case REQUEST_CROP: 978 if (resultCode == Activity.RESULT_OK) { 979 setCurrentPhotoByIntent(data); 980 } 981 break; 982 case REQUEST_CROP_PICASA: { 983 if (resultCode == Activity.RESULT_OK) { 984 Context context = mActivity.getAndroidContext(); 985 String message = context.getString(R.string.crop_saved, 986 context.getString(R.string.folder_download)); 987 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 988 } 989 break; 990 } 991 case REQUEST_SLIDESHOW: { 992 if (data == null) break; 993 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH); 994 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0); 995 if (path != null) { 996 mModel.setCurrentPhoto(Path.fromString(path), index); 997 } 998 } 999 } 1000 } 1001 1002 @Override 1003 protected void clearStateResult() { 1004 mHasActivityResult = false; 1005 } 1006 1007 private class PreparePhotoFallback implements OnGLIdleListener { 1008 private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect(); 1009 private boolean mResultReady = false; 1010 1011 public synchronized PhotoFallbackEffect get() { 1012 while (!mResultReady) { 1013 Utils.waitWithoutInterrupt(this); 1014 } 1015 return mPhotoFallback; 1016 } 1017 1018 @Override 1019 public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { 1020 mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas); 1021 synchronized (this) { 1022 mResultReady = true; 1023 notifyAll(); 1024 } 1025 return false; 1026 } 1027 } 1028 1029 private void preparePhotoFallbackView() { 1030 GLRoot root = mActivity.getGLRoot(); 1031 PreparePhotoFallback task = new PreparePhotoFallback(); 1032 root.unlockRenderThread(); 1033 PhotoFallbackEffect anim; 1034 try { 1035 root.addOnGLIdleListener(task); 1036 anim = task.get(); 1037 } finally { 1038 root.lockRenderThread(); 1039 } 1040 mActivity.getTransitionStore().put( 1041 AlbumPage.KEY_RESUME_ANIMATION, anim); 1042 } 1043 1044 @Override 1045 public void onPause() { 1046 super.onPause(); 1047 mIsActive = false; 1048 1049 mActivity.getGLRoot().unfreeze(); 1050 mHandler.removeMessages(MSG_UNFREEZE_GLROOT); 1051 if (isFinishing()) preparePhotoFallbackView(); 1052 1053 DetailsHelper.pause(); 1054 mPhotoView.pause(); 1055 mModel.pause(); 1056 mHandler.removeMessages(MSG_HIDE_BARS); 1057 mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener); 1058 1059 onCommitDeleteImage(); 1060 mMenuExecutor.pause(); 1061 if (mMediaSet != null) mMediaSet.clearDeletion(); 1062 } 1063 1064 @Override 1065 public void onCurrentImageUpdated() { 1066 mActivity.getGLRoot().unfreeze(); 1067 } 1068 1069 private void setGridButtonVisibility(boolean enabled) { 1070 Menu menu = mActionBar.getMenu(); 1071 if (menu == null) return; 1072 MenuItem item = menu.findItem(R.id.action_grid); 1073 if (item != null) item.setVisible(enabled); 1074 } 1075 1076 public void onFilmModeChanged(boolean enabled) { 1077 mHandler.sendEmptyMessage(MSG_REFRESH_GRID_BUTTON); 1078 mHandler.sendEmptyMessage(MSG_REFRESH_EDIT_BUTTON); 1079 if (enabled) { 1080 mHandler.removeMessages(MSG_HIDE_BARS); 1081 } else { 1082 refreshHidingMessage(); 1083 } 1084 } 1085 1086 private void transitionFromAlbumPageIfNeeded() { 1087 TransitionStore transitions = mActivity.getTransitionStore(); 1088 1089 int resumeIndex = transitions.get(KEY_INDEX_HINT, -1); 1090 if (resumeIndex >= 0) { 1091 mCurrentIndex = resumeIndex; 1092 mModel.setCurrentPhoto((Path)transitions.get(KEY_MEDIA_SET_PATH), mCurrentIndex); 1093 mPhotoView.switchToImage(mCurrentIndex); 1094 } 1095 1096 int albumPageTransition = transitions.get( 1097 KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE); 1098 1099 if(albumPageTransition != MSG_ALBUMPAGE_NONE) { 1100 mPhotoView.setFilmMode(mStartInFilmstrip 1101 && albumPageTransition == MSG_ALBUMPAGE_RESUMED); 1102 } 1103 1104 mFadeOutTexture = transitions.get(AlbumPage.KEY_FADE_TEXTURE); 1105 if (mFadeOutTexture != null) { 1106 mBackgroundFade.start(); 1107 BitmapScreenNail.disableDrawPlaceholder(); 1108 mOpenAnimationRect = 1109 albumPageTransition == MSG_ALBUMPAGE_NONE ? 1110 (Rect) mData.getParcelable(KEY_OPEN_ANIMATION_RECT) : 1111 (Rect) transitions.get(KEY_OPEN_ANIMATION_RECT); 1112 mPhotoView.setOpenAnimationRect(mOpenAnimationRect); 1113 mBackgroundFade.start(); 1114 } 1115 } 1116 1117 @Override 1118 protected void onResume() { 1119 super.onResume(); 1120 1121 transitionFromAlbumPageIfNeeded(); 1122 1123 mActivity.getGLRoot().freeze(); 1124 mIsActive = true; 1125 setContentPane(mRootPane); 1126 1127 mModel.resume(); 1128 mPhotoView.resume(); 1129 mActionBar.setDisplayOptions( 1130 ((mSecureAlbum == null) && (mSetPathString != null)), true); 1131 mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener); 1132 1133 if (mAppBridge != null && !mHasActivityResult) { 1134 mPhotoView.resetToFirstPicture(); 1135 } 1136 mHasActivityResult = false; 1137 mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT); 1138 } 1139 1140 @Override 1141 protected void onDestroy() { 1142 if (mAppBridge != null) { 1143 mAppBridge.setServer(null); 1144 mScreenNailItem.setScreenNail(null); 1145 mAppBridge.detachScreenNail(); 1146 mAppBridge = null; 1147 mScreenNailSet = null; 1148 mScreenNailItem = null; 1149 } 1150 mOrientationManager.removeListener(this); 1151 mActivity.getGLRoot().setOrientationSource(null); 1152 cleanupEditButton(); 1153 1154 // Remove all pending messages. 1155 mHandler.removeCallbacksAndMessages(null); 1156 super.onDestroy(); 1157 } 1158 1159 private class MyDetailsSource implements DetailsSource { 1160 1161 @Override 1162 public MediaDetails getDetails() { 1163 return mModel.getMediaItem(0).getDetails(); 1164 } 1165 1166 @Override 1167 public int size() { 1168 return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1; 1169 } 1170 1171 @Override 1172 public int setIndex() { 1173 return mModel.getCurrentIndex(); 1174 } 1175 } 1176} 1177