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