OSDN Git Service

am 21c2e665: Fix 6360834 Select All is shown in place of Deselect all option
[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             mSelectionManager.setSourceMediaSet(mMediaSet);
216             mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
217             if (mMediaSet == null) {
218                 Log.w(TAG, "failed to restore " + mSetPathString);
219             }
220             PhotoDataAdapter pda = new PhotoDataAdapter(
221                     mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex,
222                     mAppBridge == null ? -1 : 0,
223                     mAppBridge == null ? false : mAppBridge.isPanorama());
224             mModel = pda;
225             mPhotoView.setModel(mModel);
226
227             pda.setDataListener(new PhotoDataAdapter.DataListener() {
228
229                 @Override
230                 public void onPhotoChanged(int index, Path item) {
231                     mCurrentIndex = index;
232                     if (item != null) {
233                         MediaItem photo = mModel.getMediaItem(0);
234                         if (photo != null) updateCurrentPhoto(photo);
235                     }
236                     updateBars();
237                 }
238
239                 @Override
240                 public void onLoadingFinished() {
241                     if (!mModel.isEmpty()) {
242                         MediaItem photo = mModel.getMediaItem(0);
243                         if (photo != null) updateCurrentPhoto(photo);
244                     } else if (mIsActive) {
245                         mActivity.getStateManager().finishState(PhotoPage.this);
246                     }
247                 }
248
249                 @Override
250                 public void onLoadingStarted() {
251                 }
252             });
253         } else {
254             // Get default media set by the URI
255             MediaItem mediaItem = (MediaItem)
256                     mActivity.getDataManager().getMediaObject(itemPath);
257             mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
258             mPhotoView.setModel(mModel);
259             updateCurrentPhoto(mediaItem);
260         }
261
262         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
263             @Override
264             public void handleMessage(Message message) {
265                 switch (message.what) {
266                     case MSG_HIDE_BARS: {
267                         hideBars();
268                         break;
269                     }
270                     case MSG_LOCK_ORIENTATION: {
271                         mOrientationManager.lockOrientation();
272                         updateBars();
273                         break;
274                     }
275                     case MSG_UNLOCK_ORIENTATION: {
276                         mOrientationManager.unlockOrientation();
277                         updateBars();
278                         break;
279                     }
280                     case MSG_ON_FULL_SCREEN_CHANGED: {
281                         mAppBridge.onFullScreenChanged(message.arg1 == 1);
282                         break;
283                     }
284                     case MSG_UPDATE_ACTION_BAR: {
285                         updateBars();
286                         break;
287                     }
288                     case MSG_UNFREEZE_GLROOT: {
289                         mActivity.getGLRoot().unfreeze();
290                         break;
291                     }
292                     default: throw new AssertionError(message.what);
293                 }
294             }
295         };
296
297         // start the opening animation only if it's not restored.
298         if (restoreState == null) {
299             mPhotoView.setOpenAnimationRect((Rect) data.getParcelable(KEY_OPEN_ANIMATION_RECT));
300         }
301     }
302
303     private void updateShareURI(Path path) {
304         if (mShareActionProvider != null) {
305             DataManager manager = mActivity.getDataManager();
306             int type = manager.getMediaType(path);
307             Intent intent = new Intent(Intent.ACTION_SEND);
308             intent.setType(MenuExecutor.getMimeType(type));
309             intent.putExtra(Intent.EXTRA_STREAM, manager.getContentUri(path));
310             mShareActionProvider.setShareIntent(intent);
311             if (mNfcAdapter != null) {
312                 mNfcAdapter.setBeamPushUris(new Uri[]{manager.getContentUri(path)},
313                         (Activity)mActivity);
314             }
315             mPendingSharePath = null;
316         } else {
317             // This happens when ActionBar is not created yet.
318             mPendingSharePath = path;
319         }
320     }
321
322     private void updateCurrentPhoto(MediaItem photo) {
323         if (mCurrentPhoto == photo) return;
324         mCurrentPhoto = photo;
325         if (mCurrentPhoto == null) return;
326         updateMenuOperations();
327         updateTitle();
328         if (mShowDetails) {
329             mDetailsHelper.reloadDetails(mModel.getCurrentIndex());
330         }
331         if ((photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
332             updateShareURI(photo.getPath());
333         }
334     }
335
336     private void updateTitle() {
337         if (mCurrentPhoto == null) return;
338         boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean(
339                 R.bool.show_action_bar_title);
340         if (showTitle && mCurrentPhoto.getName() != null)
341             mActionBar.setTitle(mCurrentPhoto.getName());
342         else
343             mActionBar.setTitle("");
344     }
345
346     private void updateMenuOperations() {
347         if (mMenu == null) return;
348         MenuItem item = mMenu.findItem(R.id.action_slideshow);
349         if (item != null) {
350             item.setVisible(canDoSlideShow());
351         }
352         if (mCurrentPhoto == null) return;
353         int supportedOperations = mCurrentPhoto.getSupportedOperations();
354         if (!GalleryUtils.isEditorAvailable((Context) mActivity, "image/*")) {
355             supportedOperations &= ~MediaObject.SUPPORT_EDIT;
356         }
357
358         MenuExecutor.updateMenuOperation(mMenu, supportedOperations);
359     }
360
361     private boolean canDoSlideShow() {
362         if (mMediaSet == null || mCurrentPhoto == null) {
363             return false;
364         }
365         if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) {
366             return false;
367         }
368         if (mMediaSet instanceof MtpDevice) {
369             return false;
370         }
371         return true;
372     }
373
374     //////////////////////////////////////////////////////////////////////////
375     //  Action Bar show/hide management
376     //////////////////////////////////////////////////////////////////////////
377
378     private void showBars() {
379         if (mShowBars) return;
380         mShowBars = true;
381         mActionBar.show();
382         mActivity.getGLRoot().setLightsOutMode(false);
383         refreshHidingMessage();
384     }
385
386     private void hideBars() {
387         if (!mShowBars) return;
388         mShowBars = false;
389         mActionBar.hide();
390         mActivity.getGLRoot().setLightsOutMode(true);
391         mHandler.removeMessages(MSG_HIDE_BARS);
392     }
393
394     private void refreshHidingMessage() {
395         mHandler.removeMessages(MSG_HIDE_BARS);
396         if (!mIsMenuVisible) {
397             mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
398         }
399     }
400
401     private boolean canShowBars() {
402         // No bars if we are showing camera preview.
403         if (mAppBridge != null && mCurrentIndex == 0) return false;
404         // No bars if it's not allowed.
405         if (!mActionBarAllowed) return false;
406         // No bars if the orientation is locked.
407         if (mOrientationManager.isOrientationLocked()) return false;
408
409         return true;
410     }
411
412     private void toggleBars() {
413         mCanShowBars = canShowBars();
414         if (mShowBars) {
415             hideBars();
416         } else {
417             if (mCanShowBars) showBars();
418         }
419     }
420
421     private void updateBars() {
422         boolean v = canShowBars();
423         if (mCanShowBars == v) return;
424         mCanShowBars = v;
425
426         if (mCanShowBars) {
427             showBars();
428         } else {
429             hideBars();
430         }
431     }
432
433     @Override
434     public void onOrientationCompensationChanged() {
435         mActivity.getGLRoot().requestLayoutContentPane();
436     }
437
438     @Override
439     protected void onBackPressed() {
440         if (mShowDetails) {
441             hideDetails();
442         } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) {
443             // We are leaving this page. Set the result now.
444             setResult();
445             super.onBackPressed();
446         }
447     }
448
449     private void onUpPressed() {
450         if (mActivity.getStateManager().getStateCount() > 1) {
451             super.onBackPressed();
452             return;
453         }
454
455         if (mOriginalSetPathString == null) return;
456
457         if (mAppBridge == null) {
458             // We're in view mode so set up the stacks on our own.
459             Bundle data = new Bundle(getData());
460             data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
461             data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
462                     mActivity.getDataManager().getTopSetPath(
463                             DataManager.INCLUDE_ALL));
464             mActivity.getStateManager().switchState(this, AlbumPage.class, data);
465         } else {
466             // Start the real gallery activity to view the camera roll.
467             Uri uri = Uri.parse("content://media/external/file?bucketId="
468                     + MediaSetUtils.CAMERA_BUCKET_ID);
469             Intent intent = new Intent(Intent.ACTION_VIEW);
470             intent.setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE + "/image");
471             ((Activity) mActivity).startActivity(intent);
472         }
473     }
474
475     private void setResult() {
476         Intent result = null;
477         if (!mPhotoView.getFilmMode()) {
478             result = new Intent();
479             result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex);
480         }
481         setStateResult(Activity.RESULT_OK, result);
482     }
483
484     //////////////////////////////////////////////////////////////////////////
485     //  AppBridge.Server interface
486     //////////////////////////////////////////////////////////////////////////
487
488     @Override
489     public void setCameraRelativeFrame(Rect frame) {
490         mPhotoView.setCameraRelativeFrame(frame);
491     }
492
493     @Override
494     public boolean switchWithCaptureAnimation(int offset) {
495         return mPhotoView.switchWithCaptureAnimation(offset);
496     }
497
498     @Override
499     public void setSwipingEnabled(boolean enabled) {
500         mPhotoView.setSwipingEnabled(enabled);
501     }
502
503     @Override
504     public void notifyScreenNailChanged() {
505         mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
506         mScreenNailSet.notifyChange();
507     }
508
509     @Override
510     protected boolean onCreateActionBar(Menu menu) {
511         MenuInflater inflater = ((Activity) mActivity).getMenuInflater();
512         inflater.inflate(R.menu.photo, menu);
513         mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu);
514         if (mPendingSharePath != null) updateShareURI(mPendingSharePath);
515         mMenu = menu;
516         updateMenuOperations();
517         updateTitle();
518         return true;
519     }
520
521     private MenuExecutor.ProgressListener mConfirmDialogListener =
522             new MenuExecutor.ProgressListener() {
523         @Override
524         public void onProgressUpdate(int index) {}
525
526         @Override
527         public void onProgressComplete(int result) {}
528
529         @Override
530         public void onConfirmDialogShown() {
531             mHandler.removeMessages(MSG_HIDE_BARS);
532         }
533
534         @Override
535         public void onConfirmDialogDismissed(boolean confirmed) {
536             refreshHidingMessage();
537         }
538     };
539
540     @Override
541     protected boolean onItemSelected(MenuItem item) {
542         refreshHidingMessage();
543         MediaItem current = mModel.getMediaItem(0);
544
545         if (current == null) {
546             // item is not ready, ignore
547             return true;
548         }
549
550         int currentIndex = mModel.getCurrentIndex();
551         Path path = current.getPath();
552
553         DataManager manager = mActivity.getDataManager();
554         int action = item.getItemId();
555         String confirmMsg = null;
556         switch (action) {
557             case android.R.id.home: {
558                 onUpPressed();
559                 return true;
560             }
561             case R.id.action_slideshow: {
562                 Bundle data = new Bundle();
563                 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
564                 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString());
565                 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
566                 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
567                 mActivity.getStateManager().startStateForResult(
568                         SlideshowPage.class, REQUEST_SLIDESHOW, data);
569                 return true;
570             }
571             case R.id.action_crop: {
572                 Activity activity = (Activity) mActivity;
573                 Intent intent = new Intent(CropImage.CROP_ACTION);
574                 intent.setClass(activity, CropImage.class);
575                 intent.setData(manager.getContentUri(path));
576                 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
577                         ? REQUEST_CROP_PICASA
578                         : REQUEST_CROP);
579                 return true;
580             }
581             case R.id.action_edit: {
582                 Intent intent = new Intent(Intent.ACTION_EDIT)
583                         .setData(manager.getContentUri(path))
584                         .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
585                 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
586                         REQUEST_EDIT);
587                 return true;
588             }
589             case R.id.action_details: {
590                 if (mShowDetails) {
591                     hideDetails();
592                 } else {
593                     showDetails(currentIndex);
594                 }
595                 return true;
596             }
597             case R.id.action_delete:
598                 confirmMsg = mActivity.getResources().getQuantityString(
599                         R.plurals.delete_selection, 1);
600             case R.id.action_setas:
601             case R.id.action_rotate_ccw:
602             case R.id.action_rotate_cw:
603             case R.id.action_show_on_map:
604                 mSelectionManager.deSelectAll();
605                 mSelectionManager.toggle(path);
606                 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
607                 return true;
608             case R.id.action_import:
609                 mSelectionManager.deSelectAll();
610                 mSelectionManager.toggle(path);
611                 mMenuExecutor.onMenuClicked(item, confirmMsg,
612                         new ImportCompleteListener(mActivity));
613                 return true;
614             default :
615                 return false;
616         }
617     }
618
619     private void hideDetails() {
620         mShowDetails = false;
621         mDetailsHelper.hide();
622     }
623
624     private void showDetails(int index) {
625         mShowDetails = true;
626         if (mDetailsHelper == null) {
627             mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
628             mDetailsHelper.setCloseListener(new CloseListener() {
629                 @Override
630                 public void onClose() {
631                     hideDetails();
632                 }
633             });
634         }
635         mDetailsHelper.reloadDetails(index);
636         mDetailsHelper.show();
637     }
638
639     ////////////////////////////////////////////////////////////////////////////
640     //  Callbacks from PhotoView
641     ////////////////////////////////////////////////////////////////////////////
642     @Override
643     public void onSingleTapUp(int x, int y) {
644         if (mAppBridge != null) {
645             if (mAppBridge.onSingleTapUp(x, y)) return;
646         }
647
648         MediaItem item = mModel.getMediaItem(0);
649         if (item == null || item == mScreenNailItem) {
650             // item is not ready or it is camera preview, ignore
651             return;
652         }
653
654         boolean playVideo =
655                 (item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0;
656
657         if (playVideo) {
658             // determine if the point is at center (1/6) of the photo view.
659             // (The position of the "play" icon is at center (1/6) of the photo)
660             int w = mPhotoView.getWidth();
661             int h = mPhotoView.getHeight();
662             playVideo = (Math.abs(x - w / 2) * 12 <= w)
663                 && (Math.abs(y - h / 2) * 12 <= h);
664         }
665
666         if (playVideo) {
667             playVideo((Activity) mActivity, item.getPlayUri(), item.getName());
668         } else {
669             toggleBars();
670         }
671     }
672
673     @Override
674     public void lockOrientation() {
675         mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION);
676     }
677
678     @Override
679     public void unlockOrientation() {
680         mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION);
681     }
682
683     @Override
684     public void onActionBarAllowed(boolean allowed) {
685         mActionBarAllowed = allowed;
686         mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
687     }
688
689     @Override
690     public void onFullScreenChanged(boolean full) {
691         Message m = mHandler.obtainMessage(
692                 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
693         m.sendToTarget();
694     }
695
696     public static void playVideo(Activity activity, Uri uri, String title) {
697         try {
698             Intent intent = new Intent(Intent.ACTION_VIEW)
699                     .setDataAndType(uri, "video/*")
700                     .putExtra(Intent.EXTRA_TITLE, title)
701                     .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true);
702             activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO);
703         } catch (ActivityNotFoundException e) {
704             Toast.makeText(activity, activity.getString(R.string.video_err),
705                     Toast.LENGTH_SHORT).show();
706         }
707     }
708
709     private void setCurrentPhotoByIntent(Intent intent) {
710         if (intent == null) return;
711         Path path = mApplication.getDataManager()
712                 .findPathByUri(intent.getData(), intent.getType());
713         if (path != null) {
714             mModel.setCurrentPhoto(path, mCurrentIndex);
715         }
716     }
717
718     @Override
719     protected void onStateResult(int requestCode, int resultCode, Intent data) {
720         mHasActivityResult = true;
721         switch (requestCode) {
722             case REQUEST_EDIT:
723                 setCurrentPhotoByIntent(data);
724                 break;
725             case REQUEST_CROP:
726                 if (resultCode == Activity.RESULT_OK) {
727                     setCurrentPhotoByIntent(data);
728                 }
729                 break;
730             case REQUEST_CROP_PICASA: {
731                 if (resultCode == Activity.RESULT_OK) {
732                     Context context = mActivity.getAndroidContext();
733                     String message = context.getString(R.string.crop_saved,
734                             context.getString(R.string.folder_download));
735                     Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
736                 }
737                 break;
738             }
739             case REQUEST_SLIDESHOW: {
740                 if (data == null) break;
741                 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
742                 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
743                 if (path != null) {
744                     mModel.setCurrentPhoto(Path.fromString(path), index);
745                 }
746             }
747         }
748     }
749
750     private class PreparePhotoFallback implements OnGLIdleListener {
751         private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect();
752         private boolean mResultReady = false;
753
754         public synchronized PhotoFallbackEffect get() {
755             while (!mResultReady) {
756                 Utils.waitWithoutInterrupt(this);
757             }
758             return mPhotoFallback;
759         }
760
761         @Override
762         public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
763             mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas);
764             synchronized (this) {
765                 mResultReady = true;
766                 notifyAll();
767             }
768             return false;
769         }
770     }
771
772     private void preparePhotoFallbackView() {
773         GLRoot root = mActivity.getGLRoot();
774         PreparePhotoFallback task = new PreparePhotoFallback();
775         root.unlockRenderThread();
776         PhotoFallbackEffect anim;
777         try {
778             root.addOnGLIdleListener(task);
779             anim = task.get();
780         } finally {
781             root.lockRenderThread();
782         }
783         mActivity.getTransitionStore().put(
784                 AlbumPage.KEY_RESUME_ANIMATION, anim);
785     }
786
787     @Override
788     public void onPause() {
789         super.onPause();
790         mIsActive = false;
791
792         mActivity.getGLRoot().unfreeze();
793         mHandler.removeMessages(MSG_UNFREEZE_GLROOT);
794         if (isFinishing()) preparePhotoFallbackView();
795
796         DetailsHelper.pause();
797         mPhotoView.pause();
798         mModel.pause();
799         mHandler.removeMessages(MSG_HIDE_BARS);
800         mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
801
802         mMenuExecutor.pause();
803     }
804
805     @Override
806     public void onCurrentImageUpdated() {
807         mActivity.getGLRoot().unfreeze();
808     }
809
810     @Override
811     protected void onResume() {
812         super.onResume();
813         mActivity.getGLRoot().freeze();
814         mIsActive = true;
815         setContentPane(mRootPane);
816
817         mModel.resume();
818         mPhotoView.resume();
819         if (mMenuVisibilityListener == null) {
820             mMenuVisibilityListener = new MyMenuVisibilityListener();
821         }
822         mActionBar.setDisplayOptions(mSetPathString != null, true);
823         mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
824
825         if (mAppBridge != null && !mHasActivityResult) {
826             mPhotoView.resetToFirstPicture();
827         }
828         mHasActivityResult = false;
829         mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
830     }
831
832     @Override
833     protected void onDestroy() {
834         if (mAppBridge != null) {
835             mAppBridge.setServer(null);
836             mScreenNailItem.setScreenNail(null);
837             mAppBridge.detachScreenNail();
838             mAppBridge = null;
839             mScreenNailSet = null;
840             mScreenNailItem = null;
841         }
842         mOrientationManager.removeListener(this);
843         mActivity.getGLRoot().setOrientationSource(null);
844
845         // Remove all pending messages.
846         mHandler.removeCallbacksAndMessages(null);
847         super.onDestroy();
848     }
849
850     private class MyDetailsSource implements DetailsSource {
851         private int mIndex;
852
853         @Override
854         public MediaDetails getDetails() {
855             return mModel.getMediaItem(0).getDetails();
856         }
857
858         @Override
859         public int size() {
860             return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
861         }
862
863         @Override
864         public int findIndex(int indexHint) {
865             mIndex = indexHint;
866             return indexHint;
867         }
868
869         @Override
870         public int getIndex() {
871             return mIndex;
872         }
873     }
874 }