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