2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.gallery3d.app;
19 import android.annotation.TargetApi;
20 import android.app.ActionBar.OnMenuVisibilityListener;
21 import android.app.Activity;
22 import android.content.ActivityNotFoundException;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.graphics.Rect;
27 import android.net.Uri;
28 import android.nfc.NfcAdapter;
29 import android.os.Build;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.view.Menu;
34 import android.view.MenuInflater;
35 import android.view.MenuItem;
36 import android.view.View;
37 import android.view.WindowManager;
38 import android.widget.ShareActionProvider;
39 import android.widget.Toast;
41 import com.android.gallery3d.R;
42 import com.android.gallery3d.common.ApiHelper;
43 import com.android.gallery3d.common.Utils;
44 import com.android.gallery3d.data.DataManager;
45 import com.android.gallery3d.data.FilterDeleteSet;
46 import com.android.gallery3d.data.MediaDetails;
47 import com.android.gallery3d.data.MediaItem;
48 import com.android.gallery3d.data.MediaObject;
49 import com.android.gallery3d.data.MediaSet;
50 import com.android.gallery3d.data.MtpSource;
51 import com.android.gallery3d.data.Path;
52 import com.android.gallery3d.data.SnailAlbum;
53 import com.android.gallery3d.data.SnailItem;
54 import com.android.gallery3d.data.SnailSource;
55 import com.android.gallery3d.picasasource.PicasaSource;
56 import com.android.gallery3d.ui.DetailsHelper;
57 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
58 import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
59 import com.android.gallery3d.ui.GLCanvas;
60 import com.android.gallery3d.ui.GLRoot;
61 import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
62 import com.android.gallery3d.ui.GLView;
63 import com.android.gallery3d.ui.ImportCompleteListener;
64 import com.android.gallery3d.ui.MenuExecutor;
65 import com.android.gallery3d.ui.PhotoFallbackEffect;
66 import com.android.gallery3d.ui.PhotoView;
67 import com.android.gallery3d.ui.SelectionManager;
68 import com.android.gallery3d.ui.SynchronizedHandler;
69 import com.android.gallery3d.util.GalleryUtils;
70 import com.android.gallery3d.util.MediaSetUtils;
72 public class PhotoPage extends ActivityState implements
73 PhotoView.Listener, OrientationManager.Listener, AppBridge.Server {
74 private static final String TAG = "PhotoPage";
76 private static final int MSG_HIDE_BARS = 1;
77 private static final int MSG_LOCK_ORIENTATION = 2;
78 private static final int MSG_UNLOCK_ORIENTATION = 3;
79 private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
80 private static final int MSG_UPDATE_ACTION_BAR = 5;
81 private static final int MSG_UNFREEZE_GLROOT = 6;
82 private static final int MSG_WANT_BARS = 7;
84 private static final int HIDE_BARS_TIMEOUT = 3500;
85 private static final int UNFREEZE_GLROOT_TIMEOUT = 250;
87 private static final int REQUEST_SLIDESHOW = 1;
88 private static final int REQUEST_CROP = 2;
89 private static final int REQUEST_CROP_PICASA = 3;
90 private static final int REQUEST_EDIT = 4;
91 private static final int REQUEST_PLAY_VIDEO = 5;
93 public static final String KEY_MEDIA_SET_PATH = "media-set-path";
94 public static final String KEY_MEDIA_ITEM_PATH = "media-item-path";
95 public static final String KEY_INDEX_HINT = "index-hint";
96 public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect";
97 public static final String KEY_APP_BRIDGE = "app-bridge";
98 public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up";
100 public static final String KEY_RETURN_INDEX_HINT = "return-index-hint";
102 private GalleryApp mApplication;
103 private SelectionManager mSelectionManager;
105 private PhotoView mPhotoView;
106 private PhotoPage.Model mModel;
107 private DetailsHelper mDetailsHelper;
108 private boolean mShowDetails;
109 private Path mPendingSharePath;
111 // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
112 // E.g., viewing a photo in gmail attachment
113 private FilterDeleteSet mMediaSet;
116 private int mCurrentIndex = 0;
117 private Handler mHandler;
118 private boolean mShowBars = true;
119 private volatile boolean mActionBarAllowed = true;
120 private GalleryActionBar mActionBar;
121 private MyMenuVisibilityListener mMenuVisibilityListener;
122 private boolean mIsMenuVisible;
123 private MediaItem mCurrentPhoto = null;
124 private MenuExecutor mMenuExecutor;
125 private boolean mIsActive;
126 private ShareActionProvider mShareActionProvider;
127 private String mSetPathString;
128 // This is the original mSetPathString before adding the camera preview item.
129 private String mOriginalSetPathString;
130 private AppBridge mAppBridge;
131 private SnailItem mScreenNailItem;
132 private SnailAlbum mScreenNailSet;
133 private OrientationManager mOrientationManager;
134 private boolean mHasActivityResult;
135 private boolean mTreatBackAsUp;
137 // The item that is deleted (but it can still be undeleted before commiting)
138 private Path mDeletePath;
139 private boolean mDeleteIsFocus; // whether the deleted item was in focus
141 private NfcAdapter mNfcAdapter;
143 public static interface Model extends PhotoView.Model {
144 public void resume();
146 public boolean isEmpty();
147 public void setCurrentPhoto(Path path, int indexHint);
150 private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
152 public void onMenuVisibilityChanged(boolean isVisible) {
153 mIsMenuVisible = isVisible;
154 refreshHidingMessage();
158 private final GLView mRootPane = new GLView() {
161 protected void renderBackground(GLCanvas view) {
166 protected void onLayout(
167 boolean changed, int left, int top, int right, int bottom) {
168 mPhotoView.layout(0, 0, right - left, bottom - top);
170 mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom);
176 public void onCreate(Bundle data, Bundle restoreState) {
177 mActionBar = mActivity.getGalleryActionBar();
178 mSelectionManager = new SelectionManager(mActivity, false);
179 mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
181 mPhotoView = new PhotoView(mActivity);
182 mPhotoView.setListener(this);
183 mRootPane.addComponent(mPhotoView);
184 mApplication = (GalleryApp)((Activity) mActivity).getApplication();
185 mOrientationManager = mActivity.getOrientationManager();
186 mOrientationManager.addListener(this);
187 mActivity.getGLRoot().setOrientationSource(mOrientationManager);
189 mSetPathString = data.getString(KEY_MEDIA_SET_PATH);
190 mOriginalSetPathString = mSetPathString;
191 mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext());
192 Path itemPath = Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH));
193 mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false);
195 if (mSetPathString != null) {
196 mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE);
197 if (mAppBridge != null) {
198 mAppBridge.setServer(this);
199 mOrientationManager.lockOrientation();
201 // Get the ScreenNail from AppBridge and register it.
202 int id = SnailSource.newId();
203 Path screenNailSetPath = SnailSource.getSetPath(id);
204 Path screenNailItemPath = SnailSource.getItemPath(id);
205 mScreenNailSet = (SnailAlbum) mActivity.getDataManager()
206 .getMediaObject(screenNailSetPath);
207 mScreenNailItem = (SnailItem) mActivity.getDataManager()
208 .getMediaObject(screenNailItemPath);
209 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
211 // Combine the original MediaSet with the one for ScreenNail
213 mSetPathString = "/combo/item/{" + screenNailSetPath +
214 "," + mSetPathString + "}";
216 // Start from the screen nail.
217 itemPath = screenNailItemPath;
219 // Action bar should not be displayed when camera starts.
220 mFlags |= FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR;
224 MediaSet originalSet = mActivity.getDataManager()
225 .getMediaSet(mSetPathString);
226 mSelectionManager.setSourceMediaSet(originalSet);
227 mSetPathString = "/filter/delete/{" + mSetPathString + "}";
228 mMediaSet = (FilterDeleteSet) mActivity.getDataManager()
229 .getMediaSet(mSetPathString);
230 mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
231 if (mMediaSet == null) {
232 Log.w(TAG, "failed to restore " + mSetPathString);
234 PhotoDataAdapter pda = new PhotoDataAdapter(
235 mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex,
236 mAppBridge == null ? -1 : 0,
237 mAppBridge == null ? false : mAppBridge.isPanorama());
239 mPhotoView.setModel(mModel);
241 pda.setDataListener(new PhotoDataAdapter.DataListener() {
244 public void onPhotoChanged(int index, Path item) {
245 mCurrentIndex = index;
247 MediaItem photo = mModel.getMediaItem(0);
248 if (photo != null) updateCurrentPhoto(photo);
254 public void onLoadingFinished() {
255 if (!mModel.isEmpty()) {
256 MediaItem photo = mModel.getMediaItem(0);
257 if (photo != null) updateCurrentPhoto(photo);
258 } else if (mIsActive) {
259 // We only want to finish the PhotoPage if there is no
260 // deletion that the user can undo.
261 if (mMediaSet.getNumberOfDeletions() == 0) {
262 mActivity.getStateManager().finishState(
269 public void onLoadingStarted() {
273 // Get default media set by the URI
274 MediaItem mediaItem = (MediaItem)
275 mActivity.getDataManager().getMediaObject(itemPath);
276 mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
277 mPhotoView.setModel(mModel);
278 updateCurrentPhoto(mediaItem);
281 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
283 public void handleMessage(Message message) {
284 switch (message.what) {
285 case MSG_HIDE_BARS: {
289 case MSG_LOCK_ORIENTATION: {
290 mOrientationManager.lockOrientation();
293 case MSG_UNLOCK_ORIENTATION: {
294 mOrientationManager.unlockOrientation();
297 case MSG_ON_FULL_SCREEN_CHANGED: {
298 mAppBridge.onFullScreenChanged(message.arg1 == 1);
301 case MSG_UPDATE_ACTION_BAR: {
305 case MSG_WANT_BARS: {
309 case MSG_UNFREEZE_GLROOT: {
310 mActivity.getGLRoot().unfreeze();
313 default: throw new AssertionError(message.what);
318 // start the opening animation only if it's not restored.
319 if (restoreState == null) {
320 mPhotoView.setOpenAnimationRect((Rect) data.getParcelable(KEY_OPEN_ANIMATION_RECT));
324 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
325 private void setNfcBeamPushUris(Uri[] uris) {
326 if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
327 mNfcAdapter.setBeamPushUris(uris, (Activity)mActivity);
331 private void updateShareURI(Path path) {
332 if (mShareActionProvider != null) {
333 DataManager manager = mActivity.getDataManager();
334 int type = manager.getMediaType(path);
335 Intent intent = new Intent(Intent.ACTION_SEND);
336 intent.setType(MenuExecutor.getMimeType(type));
337 Uri uri = manager.getContentUri(path);
338 intent.putExtra(Intent.EXTRA_STREAM, uri);
339 mShareActionProvider.setShareIntent(intent);
340 setNfcBeamPushUris(new Uri[]{uri});
341 mPendingSharePath = null;
343 // This happens when ActionBar is not created yet.
344 mPendingSharePath = path;
348 private void updateCurrentPhoto(MediaItem photo) {
349 if (mCurrentPhoto == photo) return;
350 mCurrentPhoto = photo;
351 if (mCurrentPhoto == null) return;
352 updateMenuOperations();
355 mDetailsHelper.reloadDetails(mModel.getCurrentIndex());
357 if ((photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
358 updateShareURI(photo.getPath());
362 private void updateTitle() {
363 if (mCurrentPhoto == null) return;
364 boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean(
365 R.bool.show_action_bar_title);
366 if (showTitle && mCurrentPhoto.getName() != null)
367 mActionBar.setTitle(mCurrentPhoto.getName());
369 mActionBar.setTitle("");
372 private void updateMenuOperations() {
373 if (mMenu == null) return;
374 MenuItem item = mMenu.findItem(R.id.action_slideshow);
376 item.setVisible(canDoSlideShow());
378 if (mCurrentPhoto == null) return;
379 int supportedOperations = mCurrentPhoto.getSupportedOperations();
380 if (!GalleryUtils.isEditorAvailable((Context) mActivity, "image/*")) {
381 supportedOperations &= ~MediaObject.SUPPORT_EDIT;
384 MenuExecutor.updateMenuOperation(mMenu, supportedOperations);
387 private boolean canDoSlideShow() {
388 if (mMediaSet == null || mCurrentPhoto == null) {
391 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) {
394 if (MtpSource.isMtpPath(mOriginalSetPathString)) {
400 //////////////////////////////////////////////////////////////////////////
401 // Action Bar show/hide management
402 //////////////////////////////////////////////////////////////////////////
404 private void showBars() {
405 if (mShowBars) return;
407 mOrientationManager.unlockOrientation();
409 mActivity.getGLRoot().setLightsOutMode(false);
410 refreshHidingMessage();
413 private void hideBars() {
414 if (!mShowBars) return;
417 mActivity.getGLRoot().setLightsOutMode(true);
418 mHandler.removeMessages(MSG_HIDE_BARS);
421 private void refreshHidingMessage() {
422 mHandler.removeMessages(MSG_HIDE_BARS);
423 if (!mIsMenuVisible) {
424 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
428 private boolean canShowBars() {
429 // No bars if we are showing camera preview.
430 if (mAppBridge != null && mCurrentIndex == 0) return false;
431 // No bars if it's not allowed.
432 if (!mActionBarAllowed) return false;
437 private void wantBars() {
438 if (canShowBars()) showBars();
441 private void toggleBars() {
445 if (canShowBars()) showBars();
449 private void updateBars() {
450 if (!canShowBars()) {
456 public void onOrientationCompensationChanged() {
457 mActivity.getGLRoot().requestLayoutContentPane();
461 protected void onBackPressed() {
464 } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) {
465 // We are leaving this page. Set the result now.
467 if (mTreatBackAsUp) {
470 super.onBackPressed();
475 private void onUpPressed() {
476 if (mActivity.getStateManager().getStateCount() > 1) {
477 super.onBackPressed();
481 if (mOriginalSetPathString == null) return;
483 if (mAppBridge == null) {
484 // We're in view mode so set up the stacks on our own.
485 Bundle data = new Bundle(getData());
486 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
487 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
488 mActivity.getDataManager().getTopSetPath(
489 DataManager.INCLUDE_ALL));
490 mActivity.getStateManager().switchState(this, AlbumPage.class, data);
492 // Start the real gallery activity to view the camera roll.
493 Uri uri = Uri.parse("content://media/external/file?bucketId="
494 + MediaSetUtils.CAMERA_BUCKET_ID);
495 Intent intent = new Intent(Intent.ACTION_VIEW);
496 intent.setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE + "/image");
497 ((Activity) mActivity).startActivity(intent);
501 private void setResult() {
502 Intent result = null;
503 if (!mPhotoView.getFilmMode()) {
504 result = new Intent();
505 result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex);
507 setStateResult(Activity.RESULT_OK, result);
510 //////////////////////////////////////////////////////////////////////////
511 // AppBridge.Server interface
512 //////////////////////////////////////////////////////////////////////////
515 public void setCameraRelativeFrame(Rect frame) {
516 mPhotoView.setCameraRelativeFrame(frame);
520 public boolean switchWithCaptureAnimation(int offset) {
521 return mPhotoView.switchWithCaptureAnimation(offset);
525 public void setSwipingEnabled(boolean enabled) {
526 mPhotoView.setSwipingEnabled(enabled);
530 public void notifyScreenNailChanged() {
531 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
532 mScreenNailSet.notifyChange();
536 protected boolean onCreateActionBar(Menu menu) {
537 MenuInflater inflater = ((Activity) mActivity).getMenuInflater();
538 inflater.inflate(R.menu.photo, menu);
539 mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu);
540 if (mPendingSharePath != null) updateShareURI(mPendingSharePath);
542 updateMenuOperations();
547 private MenuExecutor.ProgressListener mConfirmDialogListener =
548 new MenuExecutor.ProgressListener() {
550 public void onProgressUpdate(int index) {}
553 public void onProgressComplete(int result) {}
556 public void onConfirmDialogShown() {
557 mHandler.removeMessages(MSG_HIDE_BARS);
561 public void onConfirmDialogDismissed(boolean confirmed) {
562 refreshHidingMessage();
567 protected boolean onItemSelected(MenuItem item) {
568 refreshHidingMessage();
569 MediaItem current = mModel.getMediaItem(0);
571 if (current == null) {
572 // item is not ready, ignore
576 int currentIndex = mModel.getCurrentIndex();
577 Path path = current.getPath();
579 DataManager manager = mActivity.getDataManager();
580 int action = item.getItemId();
581 String confirmMsg = null;
583 case android.R.id.home: {
587 case R.id.action_slideshow: {
588 Bundle data = new Bundle();
589 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
590 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString());
591 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
592 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
593 mActivity.getStateManager().startStateForResult(
594 SlideshowPage.class, REQUEST_SLIDESHOW, data);
597 case R.id.action_crop: {
598 Activity activity = (Activity) mActivity;
599 Intent intent = new Intent(CropImage.CROP_ACTION);
600 intent.setClass(activity, CropImage.class);
601 intent.setData(manager.getContentUri(path));
602 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
603 ? REQUEST_CROP_PICASA
607 case R.id.action_edit: {
608 Intent intent = new Intent(Intent.ACTION_EDIT)
609 .setData(manager.getContentUri(path))
610 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
611 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
615 case R.id.action_details: {
619 showDetails(currentIndex);
623 case R.id.action_delete:
624 confirmMsg = mActivity.getResources().getQuantityString(
625 R.plurals.delete_selection, 1);
626 case R.id.action_setas:
627 case R.id.action_rotate_ccw:
628 case R.id.action_rotate_cw:
629 case R.id.action_show_on_map:
630 mSelectionManager.deSelectAll();
631 mSelectionManager.toggle(path);
632 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
634 case R.id.action_import:
635 mSelectionManager.deSelectAll();
636 mSelectionManager.toggle(path);
637 mMenuExecutor.onMenuClicked(item, confirmMsg,
638 new ImportCompleteListener(mActivity));
645 private void hideDetails() {
646 mShowDetails = false;
647 mDetailsHelper.hide();
650 private void showDetails(int index) {
652 if (mDetailsHelper == null) {
653 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
654 mDetailsHelper.setCloseListener(new CloseListener() {
656 public void onClose() {
661 mDetailsHelper.reloadDetails(index);
662 mDetailsHelper.show();
665 ////////////////////////////////////////////////////////////////////////////
666 // Callbacks from PhotoView
667 ////////////////////////////////////////////////////////////////////////////
669 public void onSingleTapUp(int x, int y) {
670 if (mAppBridge != null) {
671 if (mAppBridge.onSingleTapUp(x, y)) return;
674 MediaItem item = mModel.getMediaItem(0);
675 if (item == null || item == mScreenNailItem) {
676 // item is not ready or it is camera preview, ignore
681 (item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0;
684 // determine if the point is at center (1/6) of the photo view.
685 // (The position of the "play" icon is at center (1/6) of the photo)
686 int w = mPhotoView.getWidth();
687 int h = mPhotoView.getHeight();
688 playVideo = (Math.abs(x - w / 2) * 12 <= w)
689 && (Math.abs(y - h / 2) * 12 <= h);
693 playVideo((Activity) mActivity, item.getPlayUri(), item.getName());
700 public void lockOrientation() {
701 mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION);
705 public void unlockOrientation() {
706 mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION);
710 public void onActionBarAllowed(boolean allowed) {
711 mActionBarAllowed = allowed;
712 mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
716 public void onActionBarWanted() {
717 mHandler.sendEmptyMessage(MSG_WANT_BARS);
721 public void onFullScreenChanged(boolean full) {
722 Message m = mHandler.obtainMessage(
723 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
727 // How we do delete/undo:
729 // When the user choose to delete a media item, we just tell the
730 // FilterDeleteSet to hide that item. If the user choose to undo it, we
731 // again tell FilterDeleteSet not to hide it. If the user choose to commit
732 // the deletion, we then actually delete the media item.
734 public void onDeleteImage(Path path, int offset) {
735 onCommitDeleteImage(); // commit the previous deletion
737 mDeleteIsFocus = (offset == 0);
738 mMediaSet.addDeletion(path, mCurrentIndex + offset);
742 public void onUndoDeleteImage() {
743 if (mDeletePath == null) return;
744 // If the deletion was done on the focused item, we want the model to
745 // focus on it when it is undeleted.
746 if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath);
747 mMediaSet.removeDeletion(mDeletePath);
752 public void onCommitDeleteImage() {
753 if (mDeletePath == null) return;
754 mSelectionManager.deSelectAll();
755 mSelectionManager.toggle(mDeletePath);
756 mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false);
760 public static void playVideo(Activity activity, Uri uri, String title) {
762 Intent intent = new Intent(Intent.ACTION_VIEW)
763 .setDataAndType(uri, "video/*")
764 .putExtra(Intent.EXTRA_TITLE, title)
765 .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true);
766 activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO);
767 } catch (ActivityNotFoundException e) {
768 Toast.makeText(activity, activity.getString(R.string.video_err),
769 Toast.LENGTH_SHORT).show();
773 private void setCurrentPhotoByIntent(Intent intent) {
774 if (intent == null) return;
775 Path path = mApplication.getDataManager()
776 .findPathByUri(intent.getData(), intent.getType());
778 mModel.setCurrentPhoto(path, mCurrentIndex);
783 protected void onStateResult(int requestCode, int resultCode, Intent data) {
784 mHasActivityResult = true;
785 switch (requestCode) {
787 setCurrentPhotoByIntent(data);
790 if (resultCode == Activity.RESULT_OK) {
791 setCurrentPhotoByIntent(data);
794 case REQUEST_CROP_PICASA: {
795 if (resultCode == Activity.RESULT_OK) {
796 Context context = mActivity.getAndroidContext();
797 String message = context.getString(R.string.crop_saved,
798 context.getString(R.string.folder_download));
799 Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
803 case REQUEST_SLIDESHOW: {
804 if (data == null) break;
805 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
806 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
808 mModel.setCurrentPhoto(Path.fromString(path), index);
814 private class PreparePhotoFallback implements OnGLIdleListener {
815 private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect();
816 private boolean mResultReady = false;
818 public synchronized PhotoFallbackEffect get() {
819 while (!mResultReady) {
820 Utils.waitWithoutInterrupt(this);
822 return mPhotoFallback;
826 public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
827 mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas);
828 synchronized (this) {
836 private void preparePhotoFallbackView() {
837 GLRoot root = mActivity.getGLRoot();
838 PreparePhotoFallback task = new PreparePhotoFallback();
839 root.unlockRenderThread();
840 PhotoFallbackEffect anim;
842 root.addOnGLIdleListener(task);
845 root.lockRenderThread();
847 mActivity.getTransitionStore().put(
848 AlbumPage.KEY_RESUME_ANIMATION, anim);
852 public void onPause() {
856 mActivity.getGLRoot().unfreeze();
857 mHandler.removeMessages(MSG_UNFREEZE_GLROOT);
858 if (isFinishing()) preparePhotoFallbackView();
860 DetailsHelper.pause();
863 mHandler.removeMessages(MSG_HIDE_BARS);
864 mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
866 onCommitDeleteImage();
867 mMenuExecutor.pause();
868 if (mMediaSet != null) mMediaSet.clearDeletion();
872 public void onCurrentImageUpdated() {
873 mActivity.getGLRoot().unfreeze();
877 protected void onResume() {
879 mActivity.getGLRoot().freeze();
881 setContentPane(mRootPane);
885 if (mMenuVisibilityListener == null) {
886 mMenuVisibilityListener = new MyMenuVisibilityListener();
888 mActionBar.setDisplayOptions(mSetPathString != null, true);
889 mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
891 if (mAppBridge != null && !mHasActivityResult) {
892 mPhotoView.resetToFirstPicture();
894 mHasActivityResult = false;
895 mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
899 protected void onDestroy() {
900 if (mAppBridge != null) {
901 mAppBridge.setServer(null);
902 mScreenNailItem.setScreenNail(null);
903 mAppBridge.detachScreenNail();
905 mScreenNailSet = null;
906 mScreenNailItem = null;
908 mOrientationManager.removeListener(this);
909 mActivity.getGLRoot().setOrientationSource(null);
911 // Remove all pending messages.
912 mHandler.removeCallbacksAndMessages(null);
916 private class MyDetailsSource implements DetailsSource {
920 public MediaDetails getDetails() {
921 return mModel.getMediaItem(0).getDetails();
926 return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
930 public int findIndex(int indexHint) {
936 public int getIndex() {