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