PhotoPage.java revision 2bb717f1ea38e2ce33dd102a23afe6bfacb5675c
10c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi/* 20c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * Copyright (C) 2010 The Android Open Source Project 30c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * 40c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * Licensed under the Apache License, Version 2.0 (the "License"); 50c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * you may not use this file except in compliance with the License. 60c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * You may obtain a copy of the License at 70c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * 80c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * http://www.apache.org/licenses/LICENSE-2.0 90c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * 100c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * Unless required by applicable law or agreed to in writing, software 110c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * distributed under the License is distributed on an "AS IS" BASIS, 120c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * See the License for the specific language governing permissions and 140c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi * limitations under the License. 150c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi */ 160c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi 170c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yipackage com.android.gallery3d.app; 180c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi 190c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.annotation.TargetApi; 200c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.app.Activity; 210c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.content.ActivityNotFoundException; 220c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.content.ContentResolver; 230c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.content.Context; 240c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.content.Intent; 250c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.graphics.Rect; 260c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.net.Uri; 270c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.nfc.NfcAdapter; 280c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.os.Bundle; 290c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.os.Handler; 300c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.os.Message; 310c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.view.Menu; 320c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.view.MenuItem; 330c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport android.widget.Toast; 340c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi 350c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.R; 360c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.actionbar.ActionBarInterface.OnMenuVisibilityListener; 370c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.common.ApiHelper; 380c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.common.Utils; 390c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.DataManager; 400c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.FilterDeleteSet; 410c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.MediaDetails; 420c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.MediaItem; 430c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.MediaObject; 440c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.MediaSet; 450c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.MtpSource; 460c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.Path; 470c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.SnailAlbum; 480c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.SnailItem; 490c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.data.SnailSource; 500c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.picasasource.PicasaSource; 510c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.DetailsHelper; 520c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.DetailsHelper.CloseListener; 530c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.DetailsHelper.DetailsSource; 540c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.GLCanvas; 550c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.GLRoot; 560c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.GLRoot.OnGLIdleListener; 570c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.GLView; 580c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.ImportCompleteListener; 590c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.MenuExecutor; 600c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.PhotoFallbackEffect; 610c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.PhotoView; 620c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.SelectionManager; 630c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.ui.SynchronizedHandler; 640c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.util.GalleryUtils; 650c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yiimport com.android.gallery3d.util.MediaSetUtils; 660c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi 670c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yipublic class PhotoPage extends ActivityState implements 680c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi PhotoView.Listener, OrientationManager.Listener, AppBridge.Server { 690c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final String TAG = "PhotoPage"; 700c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi 710c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int MSG_HIDE_BARS = 1; 720c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int MSG_LOCK_ORIENTATION = 2; 730c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int MSG_UNLOCK_ORIENTATION = 3; 740c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int MSG_ON_FULL_SCREEN_CHANGED = 4; 750c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int MSG_UPDATE_ACTION_BAR = 5; 760c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int MSG_UNFREEZE_GLROOT = 6; 770c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int MSG_WANT_BARS = 7; 780c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi 790c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int HIDE_BARS_TIMEOUT = 3500; 800c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int UNFREEZE_GLROOT_TIMEOUT = 250; 810c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi 820c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int REQUEST_SLIDESHOW = 1; 830c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int REQUEST_CROP = 2; 840c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int REQUEST_CROP_PICASA = 3; 850c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int REQUEST_EDIT = 4; 860c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi private static final int REQUEST_PLAY_VIDEO = 5; 870c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi 880c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi public static final String KEY_MEDIA_SET_PATH = "media-set-path"; 890c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi public static final String KEY_MEDIA_ITEM_PATH = "media-item-path"; 900c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi public static final String KEY_INDEX_HINT = "index-hint"; 910c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect"; 920c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi public static final String KEY_APP_BRIDGE = "app-bridge"; 930c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up"; 940c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi 950c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi public static final String KEY_RETURN_INDEX_HINT = "return-index-hint"; 960c5958b1636c47ed7c284f859c8e805fd06a0e6Bill Yi 97 private GalleryApp mApplication; 98 private SelectionManager mSelectionManager; 99 100 private PhotoView mPhotoView; 101 private PhotoPage.Model mModel; 102 private DetailsHelper mDetailsHelper; 103 private boolean mShowDetails; 104 private Path mPendingSharePath; 105 106 // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied. 107 // E.g., viewing a photo in gmail attachment 108 private FilterDeleteSet mMediaSet; 109 110 private int mCurrentIndex = 0; 111 private Handler mHandler; 112 private boolean mShowBars = true; 113 private volatile boolean mActionBarAllowed = true; 114 private GalleryActionBar mActionBar; 115 private boolean mIsMenuVisible; 116 private MediaItem mCurrentPhoto = null; 117 private MenuExecutor mMenuExecutor; 118 private boolean mIsActive; 119 private String mSetPathString; 120 // This is the original mSetPathString before adding the camera preview item. 121 private String mOriginalSetPathString; 122 private AppBridge mAppBridge; 123 private SnailItem mScreenNailItem; 124 private SnailAlbum mScreenNailSet; 125 private OrientationManager mOrientationManager; 126 private boolean mHasActivityResult; 127 private boolean mTreatBackAsUp; 128 129 // The item that is deleted (but it can still be undeleted before commiting) 130 private Path mDeletePath; 131 private boolean mDeleteIsFocus; // whether the deleted item was in focus 132 133 private NfcAdapter mNfcAdapter; 134 135 private final MyMenuVisibilityListener mMenuVisibilityListener = 136 new MyMenuVisibilityListener(); 137 138 public static interface Model extends PhotoView.Model { 139 public void resume(); 140 public void pause(); 141 public boolean isEmpty(); 142 public void setCurrentPhoto(Path path, int indexHint); 143 } 144 145 private class MyMenuVisibilityListener implements OnMenuVisibilityListener { 146 @Override 147 public void onMenuVisibilityChanged(boolean isVisible) { 148 mIsMenuVisible = isVisible; 149 refreshHidingMessage(); 150 } 151 } 152 153 private final GLView mRootPane = new GLView() { 154 155 @Override 156 protected void renderBackground(GLCanvas view) { 157 view.clearBuffer(0f, 0f, 0f, 1f); 158 } 159 160 @Override 161 protected void onLayout( 162 boolean changed, int left, int top, int right, int bottom) { 163 mPhotoView.layout(0, 0, right - left, bottom - top); 164 if (mShowDetails) { 165 mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom); 166 } 167 } 168 }; 169 170 @Override 171 public void onCreate(Bundle data, Bundle restoreState) { 172 mActionBar = mActivity.getGalleryActionBar(); 173 mSelectionManager = new SelectionManager(mActivity, false); 174 mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager); 175 176 mPhotoView = new PhotoView(mActivity); 177 mPhotoView.setListener(this); 178 mRootPane.addComponent(mPhotoView); 179 mApplication = (GalleryApp)((Activity) mActivity).getApplication(); 180 mOrientationManager = mActivity.getOrientationManager(); 181 mOrientationManager.addListener(this); 182 mActivity.getGLRoot().setOrientationSource(mOrientationManager); 183 184 mSetPathString = data.getString(KEY_MEDIA_SET_PATH); 185 mOriginalSetPathString = mSetPathString; 186 mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext()); 187 Path itemPath = Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)); 188 mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false); 189 190 if (mSetPathString != null) { 191 mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE); 192 if (mAppBridge != null) { 193 mAppBridge.setServer(this); 194 mOrientationManager.lockOrientation(); 195 196 // Get the ScreenNail from AppBridge and register it. 197 int id = SnailSource.newId(); 198 Path screenNailSetPath = SnailSource.getSetPath(id); 199 Path screenNailItemPath = SnailSource.getItemPath(id); 200 mScreenNailSet = (SnailAlbum) mActivity.getDataManager() 201 .getMediaObject(screenNailSetPath); 202 mScreenNailItem = (SnailItem) mActivity.getDataManager() 203 .getMediaObject(screenNailItemPath); 204 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 205 206 // Combine the original MediaSet with the one for ScreenNail 207 // from AppBridge. 208 mSetPathString = "/combo/item/{" + screenNailSetPath + 209 "," + mSetPathString + "}"; 210 211 // Start from the screen nail. 212 itemPath = screenNailItemPath; 213 214 // Action bar should not be displayed when camera starts. 215 mFlags |= FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR; 216 mShowBars = false; 217 } 218 219 MediaSet originalSet = mActivity.getDataManager() 220 .getMediaSet(mSetPathString); 221 mSelectionManager.setSourceMediaSet(originalSet); 222 mSetPathString = "/filter/delete/{" + mSetPathString + "}"; 223 mMediaSet = (FilterDeleteSet) mActivity.getDataManager() 224 .getMediaSet(mSetPathString); 225 mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0); 226 if (mMediaSet == null) { 227 Log.w(TAG, "failed to restore " + mSetPathString); 228 } 229 PhotoDataAdapter pda = new PhotoDataAdapter( 230 mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex, 231 mAppBridge == null ? -1 : 0, 232 mAppBridge == null ? false : mAppBridge.isPanorama()); 233 mModel = pda; 234 mPhotoView.setModel(mModel); 235 236 pda.setDataListener(new PhotoDataAdapter.DataListener() { 237 238 @Override 239 public void onPhotoChanged(int index, Path item) { 240 mCurrentIndex = index; 241 if (item != null) { 242 MediaItem photo = mModel.getMediaItem(0); 243 if (photo != null) updateCurrentPhoto(photo); 244 } 245 updateBars(); 246 } 247 248 @Override 249 public void onLoadingFinished() { 250 if (!mModel.isEmpty()) { 251 MediaItem photo = mModel.getMediaItem(0); 252 if (photo != null) updateCurrentPhoto(photo); 253 } else if (mIsActive) { 254 // We only want to finish the PhotoPage if there is no 255 // deletion that the user can undo. 256 if (mMediaSet.getNumberOfDeletions() == 0) { 257 mActivity.getStateManager().finishState( 258 PhotoPage.this); 259 } 260 } 261 } 262 263 @Override 264 public void onLoadingStarted() { 265 } 266 }); 267 } else { 268 // Get default media set by the URI 269 MediaItem mediaItem = (MediaItem) 270 mActivity.getDataManager().getMediaObject(itemPath); 271 mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem); 272 mPhotoView.setModel(mModel); 273 updateCurrentPhoto(mediaItem); 274 } 275 276 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { 277 @Override 278 public void handleMessage(Message message) { 279 switch (message.what) { 280 case MSG_HIDE_BARS: { 281 hideBars(); 282 break; 283 } 284 case MSG_LOCK_ORIENTATION: { 285 mOrientationManager.lockOrientation(); 286 break; 287 } 288 case MSG_UNLOCK_ORIENTATION: { 289 mOrientationManager.unlockOrientation(); 290 break; 291 } 292 case MSG_ON_FULL_SCREEN_CHANGED: { 293 mAppBridge.onFullScreenChanged(message.arg1 == 1); 294 break; 295 } 296 case MSG_UPDATE_ACTION_BAR: { 297 updateBars(); 298 break; 299 } 300 case MSG_WANT_BARS: { 301 wantBars(); 302 break; 303 } 304 case MSG_UNFREEZE_GLROOT: { 305 mActivity.getGLRoot().unfreeze(); 306 break; 307 } 308 default: throw new AssertionError(message.what); 309 } 310 } 311 }; 312 313 // start the opening animation only if it's not restored. 314 if (restoreState == null) { 315 mPhotoView.setOpenAnimationRect((Rect) data.getParcelable(KEY_OPEN_ANIMATION_RECT)); 316 } 317 } 318 319 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN) 320 private void setNfcBeamPushUris(Uri[] uris) { 321 if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) { 322 mNfcAdapter.setBeamPushUris(uris, (Activity)mActivity); 323 } 324 } 325 326 private Intent createShareIntent(Path path) { 327 DataManager manager = mActivity.getDataManager(); 328 int type = manager.getMediaType(path); 329 Intent intent = new Intent(Intent.ACTION_SEND); 330 intent.setType(MenuExecutor.getMimeType(type)); 331 Uri uri = manager.getContentUri(path); 332 intent.putExtra(Intent.EXTRA_STREAM, uri); 333 return intent; 334 335 } 336 337 private void updateShareURI(Path path) { 338 if (mActionBar.hasShareMenuItem()) { 339 DataManager manager = mActivity.getDataManager(); 340 Uri uri = manager.getContentUri(path); 341 mActionBar.setShareIntent(createShareIntent(path)); 342 setNfcBeamPushUris(new Uri[]{uri}); 343 mPendingSharePath = null; 344 } else { 345 // This happens when ActionBar is not created yet. 346 mPendingSharePath = path; 347 } 348 } 349 350 private void updateCurrentPhoto(MediaItem photo) { 351 if (mCurrentPhoto == photo) return; 352 mCurrentPhoto = photo; 353 if (mCurrentPhoto == null) return; 354 updateMenuOperations(); 355 updateTitle(); 356 if (mShowDetails) { 357 mDetailsHelper.reloadDetails(); 358 } 359 if ((photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) { 360 updateShareURI(photo.getPath()); 361 } 362 } 363 364 private void updateTitle() { 365 if (mCurrentPhoto == null) return; 366 boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean( 367 R.bool.show_action_bar_title); 368 if (showTitle && mCurrentPhoto.getName() != null) { 369 mActionBar.setTitle(mCurrentPhoto.getName()); 370 } else { 371 mActionBar.setTitle(""); 372 } 373 } 374 375 private void updateMenuOperations() { 376 mActionBar.setMenuItemVisible(R.id.action_slideshow, canDoSlideShow()); 377 if (mCurrentPhoto == null) return; 378 int supportedOperations = mCurrentPhoto.getSupportedOperations(); 379 if (!GalleryUtils.isEditorAvailable((Context) mActivity, "image/*")) { 380 supportedOperations &= ~MediaObject.SUPPORT_EDIT; 381 } 382 MenuExecutor.updateMenuOperation(mActionBar, supportedOperations); 383 } 384 385 private boolean canDoSlideShow() { 386 if (mMediaSet == null || mCurrentPhoto == null) { 387 return false; 388 } 389 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) { 390 return false; 391 } 392 if (MtpSource.isMtpPath(mOriginalSetPathString)) { 393 return false; 394 } 395 return true; 396 } 397 398 ////////////////////////////////////////////////////////////////////////// 399 // Action Bar show/hide management 400 ////////////////////////////////////////////////////////////////////////// 401 402 private void showBars() { 403 if (mShowBars) return; 404 mShowBars = true; 405 mOrientationManager.unlockOrientation(); 406 mActionBar.show(); 407 mActivity.getGLRoot().setLightsOutMode(false); 408 refreshHidingMessage(); 409 } 410 411 private void hideBars() { 412 if (!mShowBars) return; 413 mShowBars = false; 414 mActionBar.hide(); 415 mActivity.getGLRoot().setLightsOutMode(true); 416 mHandler.removeMessages(MSG_HIDE_BARS); 417 } 418 419 private void refreshHidingMessage() { 420 mHandler.removeMessages(MSG_HIDE_BARS); 421 if (!mIsMenuVisible) { 422 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT); 423 } 424 } 425 426 private boolean canShowBars() { 427 // No bars if we are showing camera preview. 428 if (mAppBridge != null && mCurrentIndex == 0) return false; 429 // No bars if it's not allowed. 430 if (!mActionBarAllowed) return false; 431 432 return true; 433 } 434 435 private void wantBars() { 436 if (canShowBars()) showBars(); 437 } 438 439 private void toggleBars() { 440 if (mShowBars) { 441 hideBars(); 442 } else { 443 if (canShowBars()) showBars(); 444 } 445 } 446 447 private void updateBars() { 448 if (!canShowBars()) { 449 hideBars(); 450 } 451 } 452 453 @Override 454 public void onOrientationCompensationChanged() { 455 mActivity.getGLRoot().requestLayoutContentPane(); 456 } 457 458 @Override 459 protected void onBackPressed() { 460 if (mShowDetails) { 461 hideDetails(); 462 } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) { 463 // We are leaving this page. Set the result now. 464 setResult(); 465 if (mTreatBackAsUp) { 466 onUpPressed(); 467 } else { 468 super.onBackPressed(); 469 } 470 } 471 } 472 473 private void onUpPressed() { 474 if (mActivity.getStateManager().getStateCount() > 1) { 475 super.onBackPressed(); 476 return; 477 } 478 479 if (mOriginalSetPathString == null) return; 480 481 if (mAppBridge == null) { 482 // We're in view mode so set up the stacks on our own. 483 Bundle data = new Bundle(getData()); 484 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 485 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 486 mActivity.getDataManager().getTopSetPath( 487 DataManager.INCLUDE_ALL)); 488 mActivity.getStateManager().switchState(this, AlbumPage.class, data); 489 } else { 490 // Start the real gallery activity to view the camera roll. 491 Uri uri = Uri.parse("content://media/external/file?bucketId=" 492 + MediaSetUtils.CAMERA_BUCKET_ID); 493 Intent intent = new Intent(Intent.ACTION_VIEW); 494 intent.setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE + "/image"); 495 ((Activity) mActivity).startActivity(intent); 496 } 497 } 498 499 private void setResult() { 500 Intent result = null; 501 if (!mPhotoView.getFilmMode()) { 502 result = new Intent(); 503 result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex); 504 } 505 setStateResult(Activity.RESULT_OK, result); 506 } 507 508 ////////////////////////////////////////////////////////////////////////// 509 // AppBridge.Server interface 510 ////////////////////////////////////////////////////////////////////////// 511 512 @Override 513 public void setCameraRelativeFrame(Rect frame) { 514 mPhotoView.setCameraRelativeFrame(frame); 515 } 516 517 @Override 518 public boolean switchWithCaptureAnimation(int offset) { 519 return mPhotoView.switchWithCaptureAnimation(offset); 520 } 521 522 @Override 523 public void setSwipingEnabled(boolean enabled) { 524 mPhotoView.setSwipingEnabled(enabled); 525 } 526 527 @Override 528 public void notifyScreenNailChanged() { 529 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 530 mScreenNailSet.notifyChange(); 531 } 532 533 @Override 534 protected boolean onCreateActionBar(Menu menu) { 535 GalleryActionBar actionBar = mActionBar; 536 boolean result = actionBar.createActionMenu(menu, R.menu.photo); 537 if (mPendingSharePath != null) updateShareURI(mPendingSharePath); 538 updateMenuOperations(); 539 updateTitle(); 540 return result; 541 } 542 543 private MenuExecutor.ProgressListener mConfirmDialogListener = 544 new MenuExecutor.ProgressListener() { 545 @Override 546 public void onProgressUpdate(int index) {} 547 548 @Override 549 public void onProgressComplete(int result) {} 550 551 @Override 552 public void onConfirmDialogShown() { 553 mHandler.removeMessages(MSG_HIDE_BARS); 554 } 555 556 @Override 557 public void onConfirmDialogDismissed(boolean confirmed) { 558 refreshHidingMessage(); 559 } 560 561 @Override 562 public void onProgressStart() {} 563 }; 564 565 @Override 566 protected boolean onItemSelected(MenuItem item) { 567 refreshHidingMessage(); 568 MediaItem current = mModel.getMediaItem(0); 569 570 if (current == null) { 571 // item is not ready, ignore 572 return true; 573 } 574 575 int currentIndex = mModel.getCurrentIndex(); 576 Path path = current.getPath(); 577 578 DataManager manager = mActivity.getDataManager(); 579 int action = item.getItemId(); 580 String confirmMsg = null; 581 switch (action) { 582 case android.R.id.home: { 583 onUpPressed(); 584 return true; 585 } 586 case R.id.action_slideshow: { 587 Bundle data = new Bundle(); 588 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString()); 589 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString()); 590 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex); 591 data.putBoolean(SlideshowPage.KEY_REPEAT, true); 592 mActivity.getStateManager().startStateForResult( 593 SlideshowPage.class, REQUEST_SLIDESHOW, data); 594 return true; 595 } 596 case R.id.action_crop: { 597 Activity activity = (Activity) mActivity; 598 Intent intent = new Intent(CropImage.CROP_ACTION); 599 intent.setClass(activity, CropImage.class); 600 intent.setData(manager.getContentUri(path)); 601 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current) 602 ? REQUEST_CROP_PICASA 603 : REQUEST_CROP); 604 return true; 605 } 606 case R.id.action_edit: { 607 Intent intent = new Intent(Intent.ACTION_EDIT) 608 .setData(manager.getContentUri(path)) 609 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 610 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null), 611 REQUEST_EDIT); 612 return true; 613 } 614 case R.id.action_details: { 615 if (mShowDetails) { 616 hideDetails(); 617 } else { 618 showDetails(); 619 } 620 return true; 621 } 622 case R.id.action_delete: 623 confirmMsg = mActivity.getResources().getQuantityString( 624 R.plurals.delete_selection, 1); 625 case R.id.action_setas: 626 case R.id.action_rotate_ccw: 627 case R.id.action_rotate_cw: 628 case R.id.action_show_on_map: 629 mSelectionManager.deSelectAll(); 630 mSelectionManager.toggle(path); 631 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener); 632 return true; 633 case R.id.action_import: 634 mSelectionManager.deSelectAll(); 635 mSelectionManager.toggle(path); 636 mMenuExecutor.onMenuClicked(item, confirmMsg, 637 new ImportCompleteListener(mActivity)); 638 return true; 639 case R.id.action_share: 640 Activity activity = (Activity) mActivity; 641 Intent intent = createShareIntent(mCurrentPhoto.getPath()); 642 activity.startActivity(Intent.createChooser(intent, 643 activity.getString(R.string.share))); 644 return true; 645 default : 646 return false; 647 } 648 } 649 650 private void hideDetails() { 651 mShowDetails = false; 652 mDetailsHelper.hide(); 653 } 654 655 private void showDetails() { 656 mShowDetails = true; 657 if (mDetailsHelper == null) { 658 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource()); 659 mDetailsHelper.setCloseListener(new CloseListener() { 660 @Override 661 public void onClose() { 662 hideDetails(); 663 } 664 }); 665 } 666 mDetailsHelper.show(); 667 } 668 669 //////////////////////////////////////////////////////////////////////////// 670 // Callbacks from PhotoView 671 //////////////////////////////////////////////////////////////////////////// 672 @Override 673 public void onSingleTapUp(int x, int y) { 674 if (mAppBridge != null) { 675 if (mAppBridge.onSingleTapUp(x, y)) return; 676 } 677 678 MediaItem item = mModel.getMediaItem(0); 679 if (item == null || item == mScreenNailItem) { 680 // item is not ready or it is camera preview, ignore 681 return; 682 } 683 684 boolean playVideo = 685 (item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0; 686 687 if (playVideo) { 688 // determine if the point is at center (1/6) of the photo view. 689 // (The position of the "play" icon is at center (1/6) of the photo) 690 int w = mPhotoView.getWidth(); 691 int h = mPhotoView.getHeight(); 692 playVideo = (Math.abs(x - w / 2) * 12 <= w) 693 && (Math.abs(y - h / 2) * 12 <= h); 694 } 695 696 if (playVideo) { 697 playVideo((Activity) mActivity, item.getPlayUri(), item.getName()); 698 } else { 699 toggleBars(); 700 } 701 } 702 703 @Override 704 public void lockOrientation() { 705 mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION); 706 } 707 708 @Override 709 public void unlockOrientation() { 710 mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION); 711 } 712 713 @Override 714 public void onActionBarAllowed(boolean allowed) { 715 mActionBarAllowed = allowed; 716 mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR); 717 } 718 719 @Override 720 public void onActionBarWanted() { 721 mHandler.sendEmptyMessage(MSG_WANT_BARS); 722 } 723 724 @Override 725 public void onFullScreenChanged(boolean full) { 726 Message m = mHandler.obtainMessage( 727 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0); 728 m.sendToTarget(); 729 } 730 731 // How we do delete/undo: 732 // 733 // When the user choose to delete a media item, we just tell the 734 // FilterDeleteSet to hide that item. If the user choose to undo it, we 735 // again tell FilterDeleteSet not to hide it. If the user choose to commit 736 // the deletion, we then actually delete the media item. 737 @Override 738 public void onDeleteImage(Path path, int offset) { 739 onCommitDeleteImage(); // commit the previous deletion 740 mDeletePath = path; 741 mDeleteIsFocus = (offset == 0); 742 mMediaSet.addDeletion(path, mCurrentIndex + offset); 743 } 744 745 @Override 746 public void onUndoDeleteImage() { 747 if (mDeletePath == null) return; 748 // If the deletion was done on the focused item, we want the model to 749 // focus on it when it is undeleted. 750 if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath); 751 mMediaSet.removeDeletion(mDeletePath); 752 mDeletePath = null; 753 } 754 755 @Override 756 public void onCommitDeleteImage() { 757 if (mDeletePath == null) return; 758 mSelectionManager.deSelectAll(); 759 mSelectionManager.toggle(mDeletePath); 760 mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false); 761 mDeletePath = null; 762 } 763 764 public static void playVideo(Activity activity, Uri uri, String title) { 765 try { 766 Intent intent = new Intent(Intent.ACTION_VIEW) 767 .setDataAndType(uri, "video/*") 768 .putExtra(Intent.EXTRA_TITLE, title) 769 .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true); 770 activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO); 771 } catch (ActivityNotFoundException e) { 772 Toast.makeText(activity, activity.getString(R.string.video_err), 773 Toast.LENGTH_SHORT).show(); 774 } 775 } 776 777 private void setCurrentPhotoByIntent(Intent intent) { 778 if (intent == null) return; 779 Path path = mApplication.getDataManager() 780 .findPathByUri(intent.getData(), intent.getType()); 781 if (path != null) { 782 mModel.setCurrentPhoto(path, mCurrentIndex); 783 } 784 } 785 786 @Override 787 protected void onStateResult(int requestCode, int resultCode, Intent data) { 788 mHasActivityResult = true; 789 switch (requestCode) { 790 case REQUEST_EDIT: 791 setCurrentPhotoByIntent(data); 792 break; 793 case REQUEST_CROP: 794 if (resultCode == Activity.RESULT_OK) { 795 setCurrentPhotoByIntent(data); 796 } 797 break; 798 case REQUEST_CROP_PICASA: { 799 if (resultCode == Activity.RESULT_OK) { 800 Context context = mActivity.getAndroidContext(); 801 String message = context.getString(R.string.crop_saved, 802 context.getString(R.string.folder_download)); 803 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 804 } 805 break; 806 } 807 case REQUEST_SLIDESHOW: { 808 if (data == null) break; 809 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH); 810 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0); 811 if (path != null) { 812 mModel.setCurrentPhoto(Path.fromString(path), index); 813 } 814 } 815 } 816 } 817 818 private class PreparePhotoFallback implements OnGLIdleListener { 819 private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect(); 820 private boolean mResultReady = false; 821 822 public synchronized PhotoFallbackEffect get() { 823 while (!mResultReady) { 824 Utils.waitWithoutInterrupt(this); 825 } 826 return mPhotoFallback; 827 } 828 829 @Override 830 public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { 831 mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas); 832 synchronized (this) { 833 mResultReady = true; 834 notifyAll(); 835 } 836 return false; 837 } 838 } 839 840 private void preparePhotoFallbackView() { 841 GLRoot root = mActivity.getGLRoot(); 842 PreparePhotoFallback task = new PreparePhotoFallback(); 843 root.unlockRenderThread(); 844 PhotoFallbackEffect anim; 845 try { 846 root.addOnGLIdleListener(task); 847 anim = task.get(); 848 } finally { 849 root.lockRenderThread(); 850 } 851 mActivity.getTransitionStore().put( 852 AlbumPage.KEY_RESUME_ANIMATION, anim); 853 } 854 855 @Override 856 public void onPause() { 857 super.onPause(); 858 mIsActive = false; 859 860 mActivity.getGLRoot().unfreeze(); 861 mHandler.removeMessages(MSG_UNFREEZE_GLROOT); 862 if (isFinishing()) preparePhotoFallbackView(); 863 864 DetailsHelper.pause(); 865 mPhotoView.pause(); 866 mModel.pause(); 867 mHandler.removeMessages(MSG_HIDE_BARS); 868 mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener); 869 870 onCommitDeleteImage(); 871 mMenuExecutor.pause(); 872 if (mMediaSet != null) mMediaSet.clearDeletion(); 873 } 874 875 @Override 876 public void onCurrentImageUpdated() { 877 mActivity.getGLRoot().unfreeze(); 878 } 879 880 @Override 881 protected void onResume() { 882 super.onResume(); 883 mActivity.getGLRoot().freeze(); 884 mIsActive = true; 885 setContentPane(mRootPane); 886 887 mModel.resume(); 888 mPhotoView.resume(); 889 mActionBar.setDisplayOptions(mSetPathString != null, true); 890 mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener); 891 892 if (mAppBridge != null && !mHasActivityResult) { 893 mPhotoView.resetToFirstPicture(); 894 } 895 mHasActivityResult = false; 896 mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT); 897 } 898 899 @Override 900 protected void onDestroy() { 901 if (mAppBridge != null) { 902 mAppBridge.setServer(null); 903 mScreenNailItem.setScreenNail(null); 904 mAppBridge.detachScreenNail(); 905 mAppBridge = null; 906 mScreenNailSet = null; 907 mScreenNailItem = null; 908 } 909 mOrientationManager.removeListener(this); 910 mActivity.getGLRoot().setOrientationSource(null); 911 912 // Remove all pending messages. 913 mHandler.removeCallbacksAndMessages(null); 914 super.onDestroy(); 915 } 916 917 private class MyDetailsSource implements DetailsSource { 918 919 @Override 920 public MediaDetails getDetails() { 921 return mModel.getMediaItem(0).getDetails(); 922 } 923 924 @Override 925 public int size() { 926 return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1; 927 } 928 929 @Override 930 public int setIndex() { 931 return mModel.getCurrentIndex(); 932 } 933 } 934} 935