OSDN Git Service

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