• 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 com.android.gallery3d.R;
20 import com.android.gallery3d.data.DataManager;
21 import com.android.gallery3d.data.MediaDetails;
22 import com.android.gallery3d.data.MediaItem;
23 import com.android.gallery3d.data.MediaObject;
24 import com.android.gallery3d.data.MediaSet;
25 import com.android.gallery3d.data.MtpDevice;
26 import com.android.gallery3d.data.Path;
27 import com.android.gallery3d.picasasource.PicasaSource;
28 import com.android.gallery3d.ui.DetailsHelper;
29 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
30 import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
31 import com.android.gallery3d.ui.FilmStripView;
32 import com.android.gallery3d.ui.GLCanvas;
33 import com.android.gallery3d.ui.GLView;
34 import com.android.gallery3d.ui.ImportCompleteListener;
35 import com.android.gallery3d.ui.MenuExecutor;
36 import com.android.gallery3d.ui.PhotoView;
37 import com.android.gallery3d.ui.PositionRepository;
38 import com.android.gallery3d.ui.PositionRepository.Position;
39 import com.android.gallery3d.ui.SelectionManager;
40 import com.android.gallery3d.ui.SynchronizedHandler;
41 import com.android.gallery3d.ui.UserInteractionListener;
42 import com.android.gallery3d.util.GalleryUtils;
43 
44 import android.app.ActionBar;
45 import android.app.ActionBar.OnMenuVisibilityListener;
46 import android.app.Activity;
47 import android.content.ActivityNotFoundException;
48 import android.content.Context;
49 import android.content.Intent;
50 import android.net.Uri;
51 import android.os.Bundle;
52 import android.os.Handler;
53 import android.os.Message;
54 import android.view.Menu;
55 import android.view.MenuInflater;
56 import android.view.MenuItem;
57 import android.view.View;
58 import android.view.View.MeasureSpec;
59 import android.view.WindowManager;
60 import android.widget.ShareActionProvider;
61 import android.widget.Toast;
62 
63 public class PhotoPage extends ActivityState
64         implements PhotoView.PhotoTapListener, FilmStripView.Listener,
65         UserInteractionListener {
66     private static final String TAG = "PhotoPage";
67 
68     private static final int MSG_HIDE_BARS = 1;
69 
70     private static final int HIDE_BARS_TIMEOUT = 3500;
71 
72     private static final int REQUEST_SLIDESHOW = 1;
73     private static final int REQUEST_CROP = 2;
74     private static final int REQUEST_CROP_PICASA = 3;
75 
76     public static final String KEY_MEDIA_SET_PATH = "media-set-path";
77     public static final String KEY_MEDIA_ITEM_PATH = "media-item-path";
78     public static final String KEY_INDEX_HINT = "index-hint";
79 
80     private GalleryApp mApplication;
81     private SelectionManager mSelectionManager;
82 
83     private PhotoView mPhotoView;
84     private PhotoPage.Model mModel;
85     private FilmStripView mFilmStripView;
86     private DetailsHelper mDetailsHelper;
87     private boolean mShowDetails;
88     private Path mPendingSharePath;
89 
90     // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
91     // E.g., viewing a photo in gmail attachment
92     private MediaSet mMediaSet;
93     private Menu mMenu;
94 
95     private Intent mResultIntent = new Intent();
96     private int mCurrentIndex = 0;
97     private Handler mHandler;
98     private boolean mShowBars = true;
99     private ActionBar mActionBar;
100     private MyMenuVisibilityListener mMenuVisibilityListener;
101     private boolean mIsMenuVisible;
102     private boolean mIsInteracting;
103     private MediaItem mCurrentPhoto = null;
104     private MenuExecutor mMenuExecutor;
105     private boolean mIsActive;
106     private ShareActionProvider mShareActionProvider;
107 
108     public static interface Model extends PhotoView.Model {
resume()109         public void resume();
pause()110         public void pause();
isEmpty()111         public boolean isEmpty();
getCurrentMediaItem()112         public MediaItem getCurrentMediaItem();
getCurrentIndex()113         public int getCurrentIndex();
setCurrentPhoto(Path path, int indexHint)114         public void setCurrentPhoto(Path path, int indexHint);
115     }
116 
117     private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
onMenuVisibilityChanged(boolean isVisible)118         public void onMenuVisibilityChanged(boolean isVisible) {
119             mIsMenuVisible = isVisible;
120             refreshHidingMessage();
121         }
122     }
123 
124     private GLView mRootPane = new GLView() {
125 
126         @Override
127         protected void renderBackground(GLCanvas view) {
128             view.clearBuffer();
129         }
130 
131         @Override
132         protected void onLayout(
133                 boolean changed, int left, int top, int right, int bottom) {
134             mPhotoView.layout(0, 0, right - left, bottom - top);
135             PositionRepository.getInstance(mActivity).setOffset(0, 0);
136             int filmStripHeight = 0;
137             if (mFilmStripView != null) {
138                 mFilmStripView.measure(
139                         MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
140                         MeasureSpec.UNSPECIFIED);
141                 filmStripHeight = mFilmStripView.getMeasuredHeight();
142                 mFilmStripView.layout(0, bottom - top - filmStripHeight,
143                         right - left, bottom - top);
144             }
145             if (mShowDetails) {
146                 mDetailsHelper.layout(left, GalleryActionBar.getHeight((Activity) mActivity),
147                         right, bottom);
148             }
149         }
150     };
151 
initFilmStripView()152     private void initFilmStripView() {
153         Config.PhotoPage config = Config.PhotoPage.get((Context) mActivity);
154         mFilmStripView = new FilmStripView(mActivity, mMediaSet,
155                 config.filmstripTopMargin, config.filmstripMidMargin, config.filmstripBottomMargin,
156                 config.filmstripContentSize, config.filmstripThumbSize, config.filmstripBarSize,
157                 config.filmstripGripSize, config.filmstripGripWidth);
158         mRootPane.addComponent(mFilmStripView);
159         mFilmStripView.setListener(this);
160         mFilmStripView.setUserInteractionListener(this);
161         mFilmStripView.setFocusIndex(mCurrentIndex);
162         mFilmStripView.setStartIndex(mCurrentIndex);
163         mRootPane.requestLayout();
164         if (mIsActive) mFilmStripView.resume();
165         if (!mShowBars) mFilmStripView.setVisibility(GLView.INVISIBLE);
166     }
167 
168     @Override
onCreate(Bundle data, Bundle restoreState)169     public void onCreate(Bundle data, Bundle restoreState) {
170         mActionBar = ((Activity) mActivity).getActionBar();
171         mSelectionManager = new SelectionManager(mActivity, false);
172         mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
173 
174         mPhotoView = new PhotoView(mActivity);
175         mPhotoView.setPhotoTapListener(this);
176         mRootPane.addComponent(mPhotoView);
177         mApplication = (GalleryApp)((Activity) mActivity).getApplication();
178 
179         String setPathString = data.getString(KEY_MEDIA_SET_PATH);
180         Path itemPath = Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH));
181 
182         if (setPathString != null) {
183             mMediaSet = mActivity.getDataManager().getMediaSet(setPathString);
184             mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
185             mMediaSet = (MediaSet)
186                     mActivity.getDataManager().getMediaObject(setPathString);
187             if (mMediaSet == null) {
188                 Log.w(TAG, "failed to restore " + setPathString);
189             }
190             PhotoDataAdapter pda = new PhotoDataAdapter(
191                     mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex);
192             mModel = pda;
193             mPhotoView.setModel(mModel);
194 
195             mResultIntent.putExtra(KEY_INDEX_HINT, mCurrentIndex);
196             setStateResult(Activity.RESULT_OK, mResultIntent);
197 
198             pda.setDataListener(new PhotoDataAdapter.DataListener() {
199 
200                 @Override
201                 public void onPhotoChanged(int index, Path item) {
202                     if (mFilmStripView != null) mFilmStripView.setFocusIndex(index);
203                     mCurrentIndex = index;
204                     mResultIntent.putExtra(KEY_INDEX_HINT, index);
205                     if (item != null) {
206                         mResultIntent.putExtra(KEY_MEDIA_ITEM_PATH, item.toString());
207                         MediaItem photo = mModel.getCurrentMediaItem();
208                         if (photo != null) updateCurrentPhoto(photo);
209                     } else {
210                         mResultIntent.removeExtra(KEY_MEDIA_ITEM_PATH);
211                     }
212                     setStateResult(Activity.RESULT_OK, mResultIntent);
213                 }
214 
215                 @Override
216                 public void onLoadingFinished() {
217                     GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
218                     if (!mModel.isEmpty()) {
219                         MediaItem photo = mModel.getCurrentMediaItem();
220                         if (photo != null) updateCurrentPhoto(photo);
221                     } else if (mIsActive) {
222                         mActivity.getStateManager().finishState(PhotoPage.this);
223                     }
224                 }
225 
226                 @Override
227                 public void onLoadingStarted() {
228                     GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
229                 }
230 
231                 @Override
232                 public void onPhotoAvailable(long version, boolean fullImage) {
233                     if (mFilmStripView == null) initFilmStripView();
234                 }
235             });
236         } else {
237             // Get default media set by the URI
238             MediaItem mediaItem = (MediaItem)
239                     mActivity.getDataManager().getMediaObject(itemPath);
240             mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
241             mPhotoView.setModel(mModel);
242             updateCurrentPhoto(mediaItem);
243         }
244 
245         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
246             @Override
247             public void handleMessage(Message message) {
248                 switch (message.what) {
249                     case MSG_HIDE_BARS: {
250                         hideBars();
251                         break;
252                     }
253                     default: throw new AssertionError(message.what);
254                 }
255             }
256         };
257 
258         // start the opening animation
259         mPhotoView.setOpenedItem(itemPath);
260     }
261 
updateShareURI(Path path)262     private void updateShareURI(Path path) {
263         if (mShareActionProvider != null) {
264             DataManager manager = mActivity.getDataManager();
265             int type = manager.getMediaType(path);
266             Intent intent = new Intent(Intent.ACTION_SEND);
267             intent.setType(MenuExecutor.getMimeType(type));
268             intent.putExtra(Intent.EXTRA_STREAM, manager.getContentUri(path));
269             mShareActionProvider.setShareIntent(intent);
270             mPendingSharePath = null;
271         } else {
272             // This happens when ActionBar is not created yet.
273             mPendingSharePath = path;
274         }
275     }
276 
setTitle(String title)277     private void setTitle(String title) {
278         if (title == null) return;
279         boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean(
280                 R.bool.show_action_bar_title);
281         if (showTitle)
282             mActionBar.setTitle(title);
283         else
284             mActionBar.setTitle("");
285     }
286 
updateCurrentPhoto(MediaItem photo)287     private void updateCurrentPhoto(MediaItem photo) {
288         if (mCurrentPhoto == photo) return;
289         mCurrentPhoto = photo;
290         if (mCurrentPhoto == null) return;
291         updateMenuOperations();
292         if (mShowDetails) {
293             mDetailsHelper.reloadDetails(mModel.getCurrentIndex());
294         }
295         setTitle(photo.getName());
296         mPhotoView.showVideoPlayIcon(
297                 photo.getMediaType() == MediaObject.MEDIA_TYPE_VIDEO);
298 
299         updateShareURI(photo.getPath());
300     }
301 
updateMenuOperations()302     private void updateMenuOperations() {
303         if (mCurrentPhoto == null || mMenu == null) return;
304         int supportedOperations = mCurrentPhoto.getSupportedOperations();
305         if (!GalleryUtils.isEditorAvailable((Context) mActivity, "image/*")) {
306             supportedOperations &= ~MediaObject.SUPPORT_EDIT;
307         }
308         MenuExecutor.updateMenuOperation(mMenu, supportedOperations);
309     }
310 
showBars()311     private void showBars() {
312         if (mShowBars) return;
313         mShowBars = true;
314         mActionBar.show();
315         WindowManager.LayoutParams params = ((Activity) mActivity).getWindow().getAttributes();
316         params.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
317         ((Activity) mActivity).getWindow().setAttributes(params);
318         if (mFilmStripView != null) {
319             mFilmStripView.show();
320         }
321     }
322 
hideBars()323     private void hideBars() {
324         if (!mShowBars) return;
325         mShowBars = false;
326         mActionBar.hide();
327         WindowManager.LayoutParams params = ((Activity) mActivity).getWindow().getAttributes();
328         params.systemUiVisibility = View. SYSTEM_UI_FLAG_LOW_PROFILE;
329         ((Activity) mActivity).getWindow().setAttributes(params);
330         if (mFilmStripView != null) {
331             mFilmStripView.hide();
332         }
333     }
334 
refreshHidingMessage()335     private void refreshHidingMessage() {
336         mHandler.removeMessages(MSG_HIDE_BARS);
337         if (!mIsMenuVisible && !mIsInteracting) {
338             mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
339         }
340     }
341 
onUserInteraction()342     public void onUserInteraction() {
343         showBars();
344         refreshHidingMessage();
345     }
346 
onUserInteractionTap()347     public void onUserInteractionTap() {
348         if (mShowBars) {
349             hideBars();
350             mHandler.removeMessages(MSG_HIDE_BARS);
351         } else {
352             showBars();
353             refreshHidingMessage();
354         }
355     }
356 
onUserInteractionBegin()357     public void onUserInteractionBegin() {
358         showBars();
359         mIsInteracting = true;
360         refreshHidingMessage();
361     }
362 
onUserInteractionEnd()363     public void onUserInteractionEnd() {
364         mIsInteracting = false;
365         refreshHidingMessage();
366     }
367 
368     @Override
onBackPressed()369     protected void onBackPressed() {
370         if (mShowDetails) {
371             hideDetails();
372         } else {
373             PositionRepository repository = PositionRepository.getInstance(mActivity);
374             repository.clear();
375             if (mCurrentPhoto != null) {
376                 Position position = new Position();
377                 position.x = mRootPane.getWidth() / 2;
378                 position.y = mRootPane.getHeight() / 2;
379                 position.z = -1000;
380                 repository.putPosition(
381                         Long.valueOf(System.identityHashCode(mCurrentPhoto.getPath())),
382                         position);
383             }
384             super.onBackPressed();
385         }
386     }
387 
388     @Override
onCreateActionBar(Menu menu)389     protected boolean onCreateActionBar(Menu menu) {
390         MenuInflater inflater = ((Activity) mActivity).getMenuInflater();
391         inflater.inflate(R.menu.photo, menu);
392         menu.findItem(R.id.action_slideshow).setVisible(
393                 mMediaSet != null && !(mMediaSet instanceof MtpDevice));
394         mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu);
395         if (mPendingSharePath != null) updateShareURI(mPendingSharePath);
396         mMenu = menu;
397         mShowBars = true;
398         updateMenuOperations();
399         return true;
400     }
401 
402     @Override
onItemSelected(MenuItem item)403     protected boolean onItemSelected(MenuItem item) {
404         MediaItem current = mModel.getCurrentMediaItem();
405 
406         if (current == null) {
407             // item is not ready, ignore
408             return true;
409         }
410 
411         int currentIndex = mModel.getCurrentIndex();
412         Path path = current.getPath();
413 
414         DataManager manager = mActivity.getDataManager();
415         int action = item.getItemId();
416         switch (action) {
417             case R.id.action_slideshow: {
418                 Bundle data = new Bundle();
419                 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
420                 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
421                 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
422                 mActivity.getStateManager().startStateForResult(
423                         SlideshowPage.class, REQUEST_SLIDESHOW, data);
424                 return true;
425             }
426             case R.id.action_crop: {
427                 Activity activity = (Activity) mActivity;
428                 Intent intent = new Intent(CropImage.CROP_ACTION);
429                 intent.setClass(activity, CropImage.class);
430                 intent.setData(manager.getContentUri(path));
431                 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
432                         ? REQUEST_CROP_PICASA
433                         : REQUEST_CROP);
434                 return true;
435             }
436             case R.id.action_details: {
437                 if (mShowDetails) {
438                     hideDetails();
439                 } else {
440                     showDetails(currentIndex);
441                 }
442                 return true;
443             }
444             case R.id.action_setas:
445             case R.id.action_confirm_delete:
446             case R.id.action_rotate_ccw:
447             case R.id.action_rotate_cw:
448             case R.id.action_show_on_map:
449             case R.id.action_edit:
450                 mSelectionManager.deSelectAll();
451                 mSelectionManager.toggle(path);
452                 mMenuExecutor.onMenuClicked(item, null);
453                 return true;
454             case R.id.action_import:
455                 mSelectionManager.deSelectAll();
456                 mSelectionManager.toggle(path);
457                 mMenuExecutor.onMenuClicked(item,
458                         new ImportCompleteListener(mActivity));
459                 return true;
460             default :
461                 return false;
462         }
463     }
464 
hideDetails()465     private void hideDetails() {
466         mShowDetails = false;
467         mDetailsHelper.hide();
468     }
469 
showDetails(int index)470     private void showDetails(int index) {
471         mShowDetails = true;
472         if (mDetailsHelper == null) {
473             mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
474             mDetailsHelper.setCloseListener(new CloseListener() {
475                 public void onClose() {
476                     hideDetails();
477                 }
478             });
479         }
480         mDetailsHelper.reloadDetails(index);
481         mDetailsHelper.show();
482     }
483 
onSingleTapUp(int x, int y)484     public void onSingleTapUp(int x, int y) {
485         MediaItem item = mModel.getCurrentMediaItem();
486         if (item == null) {
487             // item is not ready, ignore
488             return;
489         }
490 
491         boolean playVideo =
492                 (item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0;
493 
494         if (playVideo) {
495             // determine if the point is at center (1/6) of the photo view.
496             // (The position of the "play" icon is at center (1/6) of the photo)
497             int w = mPhotoView.getWidth();
498             int h = mPhotoView.getHeight();
499             playVideo = (Math.abs(x - w / 2) * 12 <= w)
500                 && (Math.abs(y - h / 2) * 12 <= h);
501         }
502 
503         if (playVideo) {
504             playVideo((Activity) mActivity, item.getPlayUri(), item.getName());
505         } else {
506             onUserInteractionTap();
507         }
508     }
509 
playVideo(Activity activity, Uri uri, String title)510     public static void playVideo(Activity activity, Uri uri, String title) {
511         try {
512             Intent intent = new Intent(Intent.ACTION_VIEW)
513                     .setDataAndType(uri, "video/*");
514             intent.putExtra(Intent.EXTRA_TITLE, title);
515             activity.startActivity(intent);
516         } catch (ActivityNotFoundException e) {
517             Toast.makeText(activity, activity.getString(R.string.video_err),
518                     Toast.LENGTH_SHORT).show();
519         }
520     }
521 
522     // Called by FileStripView.
523     // Returns false if it cannot jump to the specified index at this time.
onSlotSelected(int slotIndex)524     public boolean onSlotSelected(int slotIndex) {
525         return mPhotoView.jumpTo(slotIndex);
526     }
527 
528     @Override
onStateResult(int requestCode, int resultCode, Intent data)529     protected void onStateResult(int requestCode, int resultCode, Intent data) {
530         switch (requestCode) {
531             case REQUEST_CROP:
532                 if (resultCode == Activity.RESULT_OK) {
533                     if (data == null) break;
534                     Path path = mApplication
535                             .getDataManager().findPathByUri(data.getData());
536                     if (path != null) {
537                         mModel.setCurrentPhoto(path, mCurrentIndex);
538                     }
539                 }
540                 break;
541             case REQUEST_CROP_PICASA: {
542                 int message = resultCode == Activity.RESULT_OK
543                         ? R.string.crop_saved
544                         : R.string.crop_not_saved;
545                 Toast.makeText(mActivity.getAndroidContext(),
546                         message, Toast.LENGTH_SHORT).show();
547                 break;
548             }
549             case REQUEST_SLIDESHOW: {
550                 if (data == null) break;
551                 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
552                 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
553                 if (path != null) {
554                     mModel.setCurrentPhoto(Path.fromString(path), index);
555                 }
556             }
557         }
558     }
559 
560     @Override
onPause()561     public void onPause() {
562         super.onPause();
563         mIsActive = false;
564         if (mFilmStripView != null) {
565             mFilmStripView.pause();
566         }
567         DetailsHelper.pause();
568         mPhotoView.pause();
569         mModel.pause();
570         mHandler.removeMessages(MSG_HIDE_BARS);
571         mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
572         mMenuExecutor.pause();
573     }
574 
575     @Override
onResume()576     protected void onResume() {
577         super.onResume();
578         mIsActive = true;
579         setContentPane(mRootPane);
580         mModel.resume();
581         mPhotoView.resume();
582         if (mFilmStripView != null) {
583             mFilmStripView.resume();
584         }
585         if (mMenuVisibilityListener == null) {
586             mMenuVisibilityListener = new MyMenuVisibilityListener();
587         }
588         mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
589         onUserInteraction();
590     }
591 
592     private class MyDetailsSource implements DetailsSource {
593         private int mIndex;
594 
595         @Override
getDetails()596         public MediaDetails getDetails() {
597             return mModel.getCurrentMediaItem().getDetails();
598         }
599 
600         @Override
size()601         public int size() {
602             return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
603         }
604 
605         @Override
findIndex(int indexHint)606         public int findIndex(int indexHint) {
607             mIndex = indexHint;
608             return indexHint;
609         }
610 
611         @Override
getIndex()612         public int getIndex() {
613             return mIndex;
614         }
615     }
616 }
617