PhotoPage.java revision a9948ef755f665f1327ef80760df3858f1030454
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 android.annotation.TargetApi;
20import android.app.Activity;
21import android.content.ActivityNotFoundException;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.PackageManager;
26import android.graphics.Rect;
27import android.net.Uri;
28import android.nfc.NfcAdapter;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.Message;
32import android.view.View;
33import android.view.View.OnClickListener;
34import android.view.animation.AccelerateInterpolator;
35import android.widget.ImageView;
36import android.widget.RelativeLayout;
37import android.widget.Toast;
38
39import com.actionbarsherlock.app.ActionBar.OnMenuVisibilityListener;
40import com.actionbarsherlock.view.Menu;
41import com.actionbarsherlock.view.MenuItem;
42import com.android.gallery3d.R;
43import com.android.gallery3d.anim.FloatAnimation;
44import com.android.gallery3d.common.ApiHelper;
45import com.android.gallery3d.common.Utils;
46import com.android.gallery3d.data.DataManager;
47import com.android.gallery3d.data.FilterDeleteSet;
48import com.android.gallery3d.data.LocalImage;
49import com.android.gallery3d.data.MediaDetails;
50import com.android.gallery3d.data.MediaItem;
51import com.android.gallery3d.data.MediaObject;
52import com.android.gallery3d.data.MediaSet;
53import com.android.gallery3d.data.MtpSource;
54import com.android.gallery3d.data.Path;
55import com.android.gallery3d.data.SecureAlbum;
56import com.android.gallery3d.data.SecureSource;
57import com.android.gallery3d.data.SnailAlbum;
58import com.android.gallery3d.data.SnailItem;
59import com.android.gallery3d.data.SnailSource;
60import com.android.gallery3d.picasasource.PicasaSource;
61import com.android.gallery3d.ui.AnimationTime;
62import com.android.gallery3d.ui.BitmapScreenNail;
63import com.android.gallery3d.ui.DetailsHelper;
64import com.android.gallery3d.ui.DetailsHelper.CloseListener;
65import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
66import com.android.gallery3d.ui.GLCanvas;
67import com.android.gallery3d.ui.GLRoot;
68import com.android.gallery3d.ui.GLRoot.OnGLIdleListener;
69import com.android.gallery3d.ui.GLView;
70import com.android.gallery3d.ui.ImportCompleteListener;
71import com.android.gallery3d.ui.MenuExecutor;
72import com.android.gallery3d.ui.PhotoFallbackEffect;
73import com.android.gallery3d.ui.PhotoView;
74import com.android.gallery3d.ui.RawTexture;
75import com.android.gallery3d.ui.SelectionManager;
76import com.android.gallery3d.ui.SynchronizedHandler;
77import com.android.gallery3d.util.GalleryUtils;
78import com.android.gallery3d.util.LightCycleHelper;
79import com.android.gallery3d.util.MediaSetUtils;
80
81public class PhotoPage extends ActivityState implements
82        PhotoView.Listener, OrientationManager.Listener, AppBridge.Server {
83    private static final String TAG = "PhotoPage";
84
85    private static final int MSG_HIDE_BARS = 1;
86    private static final int MSG_LOCK_ORIENTATION = 2;
87    private static final int MSG_UNLOCK_ORIENTATION = 3;
88    private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
89    private static final int MSG_UPDATE_ACTION_BAR = 5;
90    private static final int MSG_UNFREEZE_GLROOT = 6;
91    private static final int MSG_WANT_BARS = 7;
92    private static final int MSG_REFRESH_GRID_BUTTON = 8;
93    private static final int MSG_REFRESH_EDIT_BUTTON = 9;
94
95    private static final int HIDE_BARS_TIMEOUT = 3500;
96    private static final int UNFREEZE_GLROOT_TIMEOUT = 250;
97
98    private static final int REQUEST_SLIDESHOW = 1;
99    private static final int REQUEST_CROP = 2;
100    private static final int REQUEST_CROP_PICASA = 3;
101    private static final int REQUEST_EDIT = 4;
102    private static final int REQUEST_PLAY_VIDEO = 5;
103    private static final int REQUEST_TRIM = 6;
104
105    public static final String KEY_MEDIA_SET_PATH = "media-set-path";
106    public static final String KEY_MEDIA_ITEM_PATH = "media-item-path";
107    public static final String KEY_INDEX_HINT = "index-hint";
108    public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect";
109    public static final String KEY_APP_BRIDGE = "app-bridge";
110    public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up";
111    public static final String KEY_START_IN_FILMSTRIP = "start-in-filmstrip";
112    public static final String KEY_RETURN_INDEX_HINT = "return-index-hint";
113
114    public static final String KEY_ALBUMPAGE_TRANSITION = "albumpage-transition";
115    public static final int MSG_ALBUMPAGE_NONE = 0;
116    public static final int MSG_ALBUMPAGE_STARTED = 1;
117    public static final int MSG_ALBUMPAGE_RESUMED = 2;
118    public static final int MSG_ALBUMPAGE_PICKED = 4;
119
120    public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit";
121
122    private GalleryApp mApplication;
123    private SelectionManager mSelectionManager;
124
125    private PhotoView mPhotoView;
126    private PhotoPage.Model mModel;
127    private DetailsHelper mDetailsHelper;
128    private boolean mShowDetails;
129
130    // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
131    // E.g., viewing a photo in gmail attachment
132    private FilterDeleteSet mMediaSet;
133
134    // The mediaset used by camera launched from secure lock screen.
135    private SecureAlbum mSecureAlbum;
136
137    private int mCurrentIndex = 0;
138    private Handler mHandler;
139    private boolean mShowBars = true;
140    private volatile boolean mActionBarAllowed = true;
141    private GalleryActionBar mActionBar;
142    private boolean mIsMenuVisible;
143    private MediaItem mCurrentPhoto = null;
144    private MenuExecutor mMenuExecutor;
145    private boolean mIsActive;
146    private String mSetPathString;
147    // This is the original mSetPathString before adding the camera preview item.
148    private String mOriginalSetPathString;
149    private AppBridge mAppBridge;
150    private SnailItem mScreenNailItem;
151    private SnailAlbum mScreenNailSet;
152    private OrientationManager mOrientationManager;
153    private boolean mHasActivityResult;
154    private boolean mTreatBackAsUp;
155    private boolean mStartInFilmstrip;
156    private boolean mStartedFromAlbumPage;
157
158    private RawTexture mFadeOutTexture;
159    private Rect mOpenAnimationRect;
160    public static final int ANIM_TIME_OPENING = 300;
161
162    // The item that is deleted (but it can still be undeleted before commiting)
163    private Path mDeletePath;
164    private boolean mDeleteIsFocus;  // whether the deleted item was in focus
165
166    private NfcAdapter mNfcAdapter;
167
168    private final MyMenuVisibilityListener mMenuVisibilityListener =
169            new MyMenuVisibilityListener();
170
171    public static interface Model extends PhotoView.Model {
172        public void resume();
173        public void pause();
174        public boolean isEmpty();
175        public void setCurrentPhoto(Path path, int indexHint);
176    }
177
178    private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
179        @Override
180        public void onMenuVisibilityChanged(boolean isVisible) {
181            mIsMenuVisible = isVisible;
182            refreshHidingMessage();
183        }
184    }
185
186    private static class BackgroundFadeOut extends FloatAnimation {
187        public BackgroundFadeOut() {
188            super(1f, 0f, ANIM_TIME_OPENING);
189            setInterpolator(new AccelerateInterpolator(2f));
190        }
191    }
192
193    private final FloatAnimation mBackgroundFade = new BackgroundFadeOut();
194
195    @Override
196    protected int getBackgroundColorId() {
197        return R.color.photo_background;
198    }
199
200    private final GLView mRootPane = new GLView() {
201        @Override
202        protected void renderBackground(GLCanvas view) {
203            if (mFadeOutTexture != null) {
204                if (mBackgroundFade.calculate(AnimationTime.get())) invalidate();
205                if (!mBackgroundFade.isActive()) {
206                    mFadeOutTexture = null;
207                    mOpenAnimationRect = null;
208                    BitmapScreenNail.enableDrawPlaceholder();
209                } else {
210                    float fadeAlpha = mBackgroundFade.get();
211                    if (fadeAlpha < 1f) {
212                        view.clearBuffer(getBackgroundColor());
213                        view.setAlpha(fadeAlpha);
214                    }
215                    mFadeOutTexture.draw(view, 0, 0);
216                    view.setAlpha(1f - fadeAlpha);
217                    return;
218                }
219            }
220            view.clearBuffer(getBackgroundColor());
221        }
222
223        @Override
224        protected void onLayout(
225                boolean changed, int left, int top, int right, int bottom) {
226            mPhotoView.layout(0, 0, right - left, bottom - top);
227            if (mShowDetails) {
228                mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom);
229            }
230        }
231    };
232
233    @Override
234    public void onCreate(Bundle data, Bundle restoreState) {
235        super.onCreate(data, restoreState);
236        mActionBar = mActivity.getGalleryActionBar();
237        mSelectionManager = new SelectionManager(mActivity, false);
238        mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
239
240        mPhotoView = new PhotoView(mActivity);
241        mPhotoView.setListener(this);
242        mRootPane.addComponent(mPhotoView);
243        mApplication = (GalleryApp) ((Activity) mActivity).getApplication();
244        mOrientationManager = mActivity.getOrientationManager();
245        mOrientationManager.addListener(this);
246        mActivity.getGLRoot().setOrientationSource(mOrientationManager);
247
248        mSetPathString = data.getString(KEY_MEDIA_SET_PATH);
249        mOriginalSetPathString = mSetPathString;
250        mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext());
251        String itemPathString = data.getString(KEY_MEDIA_ITEM_PATH);
252        Path itemPath = itemPathString != null ?
253                Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)) :
254                    null;
255        mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false);
256        mStartInFilmstrip =
257            data.getBoolean(KEY_START_IN_FILMSTRIP, false);
258        mStartedFromAlbumPage =
259                data.getInt(KEY_ALBUMPAGE_TRANSITION,
260                        MSG_ALBUMPAGE_NONE) == MSG_ALBUMPAGE_STARTED;
261        setGridButtonVisibility(!mStartedFromAlbumPage);
262        if (mSetPathString != null) {
263            mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE);
264            if (mAppBridge != null) {
265                mFlags |= FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR;
266                mShowBars = false;
267
268                mAppBridge.setServer(this);
269                mOrientationManager.lockOrientation();
270
271                // Get the ScreenNail from AppBridge and register it.
272                int id = SnailSource.newId();
273                Path screenNailSetPath = SnailSource.getSetPath(id);
274                Path screenNailItemPath = SnailSource.getItemPath(id);
275                mScreenNailSet = (SnailAlbum) mActivity.getDataManager()
276                        .getMediaObject(screenNailSetPath);
277                mScreenNailItem = (SnailItem) mActivity.getDataManager()
278                        .getMediaObject(screenNailItemPath);
279                mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
280
281                // Check if the path is a secure album.
282                if (SecureSource.isSecurePath(mSetPathString)) {
283                    mSecureAlbum = (SecureAlbum) mActivity.getDataManager()
284                            .getMediaSet(mSetPathString);
285                    // Set the flag to be on top of the lock screen.
286                    mFlags |= FLAG_SHOW_WHEN_LOCKED;
287                }
288
289                // Combine the original MediaSet with the one for ScreenNail
290                // from AppBridge.
291                mSetPathString = "/combo/item/{" + screenNailSetPath +
292                        "," + mSetPathString + "}";
293
294                // Start from the screen nail.
295                itemPath = screenNailItemPath;
296            }
297
298            MediaSet originalSet = mActivity.getDataManager()
299                    .getMediaSet(mSetPathString);
300            mSelectionManager.setSourceMediaSet(originalSet);
301            mSetPathString = "/filter/delete/{" + mSetPathString + "}";
302            mMediaSet = (FilterDeleteSet) mActivity.getDataManager()
303                    .getMediaSet(mSetPathString);
304            mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
305            if (mMediaSet == null) {
306                Log.w(TAG, "failed to restore " + mSetPathString);
307            }
308            if (itemPath == null) {
309               itemPath = mMediaSet.getMediaItem(mCurrentIndex, 1)
310                       .get(0).getPath();
311            }
312            PhotoDataAdapter pda = new PhotoDataAdapter(
313                    mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex,
314                    mAppBridge == null ? -1 : 0,
315                    mAppBridge == null ? false : mAppBridge.isPanorama(),
316                    mAppBridge == null ? false : mAppBridge.isStaticCamera());
317            mModel = pda;
318            mPhotoView.setModel(mModel);
319
320            pda.setDataListener(new PhotoDataAdapter.DataListener() {
321
322                @Override
323                public void onPhotoChanged(int index, Path item) {
324                    mCurrentIndex = index;
325                    if (item != null) {
326                        MediaItem photo = mModel.getMediaItem(0);
327                        if (photo != null) updateCurrentPhoto(photo);
328                    }
329                    updateBars();
330                }
331
332                @Override
333                public void onLoadingFinished() {
334                    if (!mModel.isEmpty()) {
335                        MediaItem photo = mModel.getMediaItem(0);
336                        if (photo != null) updateCurrentPhoto(photo);
337                    } else if (mIsActive) {
338                        // We only want to finish the PhotoPage if there is no
339                        // deletion that the user can undo.
340                        if (mMediaSet.getNumberOfDeletions() == 0) {
341                            mActivity.getStateManager().finishState(
342                                    PhotoPage.this);
343                        }
344                    }
345                }
346
347                @Override
348                public void onLoadingStarted() {
349                }
350            });
351        } else {
352            // Get default media set by the URI
353            MediaItem mediaItem = (MediaItem)
354                    mActivity.getDataManager().getMediaObject(itemPath);
355            mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
356            mPhotoView.setModel(mModel);
357            updateCurrentPhoto(mediaItem);
358        }
359
360        mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
361            @Override
362            public void handleMessage(Message message) {
363                switch (message.what) {
364                    case MSG_HIDE_BARS: {
365                        hideBars();
366                        break;
367                    }
368                    case MSG_REFRESH_GRID_BUTTON: {
369                        setGridButtonVisibility(mPhotoView.getFilmMode());
370                        break;
371                    }
372                    case MSG_REFRESH_EDIT_BUTTON: {
373                        refreshEditButton();
374                        break;
375                    }
376                    case MSG_LOCK_ORIENTATION: {
377                        mOrientationManager.lockOrientation();
378                        break;
379                    }
380                    case MSG_UNLOCK_ORIENTATION: {
381                        mOrientationManager.unlockOrientation();
382                        break;
383                    }
384                    case MSG_ON_FULL_SCREEN_CHANGED: {
385                        mAppBridge.onFullScreenChanged(message.arg1 == 1);
386                        break;
387                    }
388                    case MSG_UPDATE_ACTION_BAR: {
389                        updateBars();
390                        break;
391                    }
392                    case MSG_WANT_BARS: {
393                        wantBars();
394                        break;
395                    }
396                    case MSG_UNFREEZE_GLROOT: {
397                        mActivity.getGLRoot().unfreeze();
398                        break;
399                    }
400                    default: throw new AssertionError(message.what);
401                }
402            }
403        };
404
405        mPhotoView.setFilmMode(mStartInFilmstrip);
406        setupEditButton();
407    }
408
409    private ImageView mEditButton;
410    private void setupEditButton() {
411        RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity)
412                .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root);
413        if (galleryRoot == null) return;
414
415        mEditButton = new ImageView(mActivity);
416        mEditButton.setImageResource(R.drawable.photoeditor_artistic);
417        mEditButton.setOnClickListener(new OnClickListener() {
418            @Override
419            public void onClick(View arg0) {
420                launchPhotoEditor();
421            }
422        });
423        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
424                RelativeLayout.LayoutParams.WRAP_CONTENT,
425                RelativeLayout.LayoutParams.WRAP_CONTENT);
426        lp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
427        galleryRoot.addView(mEditButton, lp);
428        refreshEditButton();
429    }
430
431    private void cleanupEditButton() {
432        RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity)
433                .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root);
434        if (galleryRoot == null) return;
435        galleryRoot.removeView(mEditButton);
436        mEditButton = null;
437    }
438
439    private void refreshEditButton() {
440        if (mEditButton == null) return;
441        if(mShowBars) {
442            mEditButton.setVisibility(View.VISIBLE);
443        } else {
444            mEditButton.setVisibility(View.GONE);
445        }
446    }
447
448    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
449    private void setNfcBeamPushUris(Uri[] uris) {
450        if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) {
451            mNfcAdapter.setBeamPushUris(uris, mActivity);
452        }
453    }
454
455    private Intent createShareIntent(Path path) {
456        DataManager manager = mActivity.getDataManager();
457        int type = manager.getMediaType(path);
458        Intent intent = new Intent(Intent.ACTION_SEND);
459        intent.setType(MenuExecutor.getMimeType(type));
460        Uri uri = manager.getContentUri(path);
461        intent.putExtra(Intent.EXTRA_STREAM, uri);
462        return intent;
463
464    }
465
466    private void launchPhotoEditor() {
467        MediaItem current = mModel.getMediaItem(0);
468        if (current == null) return;
469
470        Intent intent = new Intent(ACTION_NEXTGEN_EDIT);
471        intent.setData(mActivity.getDataManager().getContentUri(current.getPath())).setFlags(
472                Intent.FLAG_GRANT_READ_URI_PERMISSION);
473        if (mActivity.getPackageManager()
474                .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
475            intent.setAction(Intent.ACTION_EDIT);
476        }
477        ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
478                REQUEST_EDIT);
479    }
480
481    private void updateShareURI(Path path) {
482        DataManager manager = mActivity.getDataManager();
483        Uri uri = manager.getContentUri(path);
484        mActionBar.setShareIntent(createShareIntent(path));
485        setNfcBeamPushUris(new Uri[]{uri});
486    }
487
488    private void updateCurrentPhoto(MediaItem photo) {
489        if (mCurrentPhoto == photo) return;
490        mCurrentPhoto = photo;
491        if (mCurrentPhoto == null) return;
492        updateMenuOperations();
493        updateTitle();
494        if (mShowDetails) {
495            mDetailsHelper.reloadDetails();
496        }
497        if ((mSecureAlbum == null)
498                && (photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
499            updateShareURI(photo.getPath());
500        }
501    }
502
503    private void updateTitle() {
504        if (mCurrentPhoto == null) return;
505        boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean(
506                R.bool.show_action_bar_title);
507        if (showTitle && mCurrentPhoto.getName() != null) {
508            mActionBar.setTitle(mCurrentPhoto.getName());
509        } else {
510            mActionBar.setTitle("");
511        }
512    }
513
514    private void updateMenuOperations() {
515        Menu menu = mActionBar.getMenu();
516
517        // it could be null if onCreateActionBar has not been called yet
518        if (menu == null) return;
519
520        setGridButtonVisibility(mPhotoView.getFilmMode());
521
522        MenuItem item = menu.findItem(R.id.action_slideshow);
523        item.setVisible((mSecureAlbum == null) && canDoSlideShow());
524        if (mCurrentPhoto == null) return;
525
526        int supportedOperations = mCurrentPhoto.getSupportedOperations();
527        if (mSecureAlbum != null) {
528            supportedOperations &= MediaObject.SUPPORT_DELETE;
529        } else if (!GalleryUtils.isEditorAvailable(mActivity, "image/*")) {
530            supportedOperations &= ~MediaObject.SUPPORT_EDIT;
531        }
532        MenuExecutor.updateMenuOperation(menu, supportedOperations);
533    }
534
535    private boolean canDoSlideShow() {
536        if (mMediaSet == null || mCurrentPhoto == null) {
537            return false;
538        }
539        if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) {
540            return false;
541        }
542        if (MtpSource.isMtpPath(mOriginalSetPathString)) {
543            return false;
544        }
545        return true;
546    }
547
548    //////////////////////////////////////////////////////////////////////////
549    //  Action Bar show/hide management
550    //////////////////////////////////////////////////////////////////////////
551
552    private void showBars() {
553        if (mShowBars) return;
554        mShowBars = true;
555        mOrientationManager.unlockOrientation();
556        mActionBar.show();
557        mActivity.getGLRoot().setLightsOutMode(false);
558        refreshHidingMessage();
559        refreshEditButton();
560    }
561
562    private void hideBars() {
563        if (!mShowBars) return;
564        mShowBars = false;
565        mActionBar.hide();
566        mActivity.getGLRoot().setLightsOutMode(true);
567        mHandler.removeMessages(MSG_HIDE_BARS);
568        refreshEditButton();
569    }
570
571    private void refreshHidingMessage() {
572        mHandler.removeMessages(MSG_HIDE_BARS);
573        if (!mIsMenuVisible && !mPhotoView.getFilmMode()) {
574            mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
575        }
576    }
577
578    private boolean canShowBars() {
579        // No bars if we are showing camera preview.
580        if (mAppBridge != null && mCurrentIndex == 0) return false;
581        // No bars if it's not allowed.
582        if (!mActionBarAllowed) return false;
583
584        return true;
585    }
586
587    private void wantBars() {
588        if (canShowBars()) showBars();
589    }
590
591    private void toggleBars() {
592        if (mShowBars) {
593            hideBars();
594        } else {
595            if (canShowBars()) showBars();
596        }
597    }
598
599    private void updateBars() {
600        if (!canShowBars()) {
601            hideBars();
602        }
603    }
604
605    @Override
606    public void onOrientationCompensationChanged() {
607        mActivity.getGLRoot().requestLayoutContentPane();
608    }
609
610    @Override
611    protected void onBackPressed() {
612        if (mShowDetails) {
613            hideDetails();
614        } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) {
615            // We are leaving this page. Set the result now.
616            setResult();
617            if (mStartInFilmstrip && !mPhotoView.getFilmMode()) {
618                mPhotoView.setFilmMode(true);
619            } else if (mTreatBackAsUp) {
620                onUpPressed();
621            } else {
622                super.onBackPressed();
623            }
624        }
625    }
626
627    private void onUpPressed() {
628        if (mStartInFilmstrip && !mPhotoView.getFilmMode()) {
629            mPhotoView.setFilmMode(true);
630            return;
631        }
632
633        if (mActivity.getStateManager().getStateCount() > 1) {
634            setResult();
635            super.onBackPressed();
636            return;
637        }
638
639        if (mOriginalSetPathString == null) return;
640
641        if (mAppBridge == null) {
642            // We're in view mode so set up the stacks on our own.
643            Bundle data = new Bundle(getData());
644            data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
645            data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
646                    mActivity.getDataManager().getTopSetPath(
647                            DataManager.INCLUDE_ALL));
648            mActivity.getStateManager().switchState(this, AlbumPage.class, data);
649        } else {
650            // Start the real gallery activity to view the camera roll.
651            Uri uri = Uri.parse("content://media/external/file?bucketId="
652                    + MediaSetUtils.CAMERA_BUCKET_ID);
653            Intent intent = new Intent(Intent.ACTION_VIEW);
654            intent.setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE + "/image");
655            ((Activity) mActivity).startActivity(intent);
656        }
657    }
658
659    private void setResult() {
660        Intent result = null;
661        result = new Intent();
662        result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex);
663        setStateResult(Activity.RESULT_OK, result);
664    }
665
666    //////////////////////////////////////////////////////////////////////////
667    //  AppBridge.Server interface
668    //////////////////////////////////////////////////////////////////////////
669
670    @Override
671    public void setCameraRelativeFrame(Rect frame) {
672        mPhotoView.setCameraRelativeFrame(frame);
673    }
674
675    @Override
676    public boolean switchWithCaptureAnimation(int offset) {
677        return mPhotoView.switchWithCaptureAnimation(offset);
678    }
679
680    @Override
681    public void setSwipingEnabled(boolean enabled) {
682        mPhotoView.setSwipingEnabled(enabled);
683    }
684
685    @Override
686    public void notifyScreenNailChanged() {
687        mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
688        mScreenNailSet.notifyChange();
689    }
690
691    @Override
692    public void addSecureAlbumItem(boolean isVideo, int id) {
693        mSecureAlbum.addMediaItem(isVideo, id);
694    }
695
696    @Override
697    protected boolean onCreateActionBar(Menu menu) {
698        mActionBar.createActionBarMenu(R.menu.photo, menu);
699        updateMenuOperations();
700        updateTitle();
701        return true;
702    }
703
704    private MenuExecutor.ProgressListener mConfirmDialogListener =
705            new MenuExecutor.ProgressListener() {
706        @Override
707        public void onProgressUpdate(int index) {}
708
709        @Override
710        public void onProgressComplete(int result) {}
711
712        @Override
713        public void onConfirmDialogShown() {
714            mHandler.removeMessages(MSG_HIDE_BARS);
715        }
716
717        @Override
718        public void onConfirmDialogDismissed(boolean confirmed) {
719            refreshHidingMessage();
720        }
721
722        @Override
723        public void onProgressStart() {}
724    };
725
726    @Override
727    protected boolean onItemSelected(MenuItem item) {
728        refreshHidingMessage();
729        MediaItem current = mModel.getMediaItem(0);
730
731        if (current == null) {
732            // item is not ready, ignore
733            return true;
734        }
735
736        int currentIndex = mModel.getCurrentIndex();
737        Path path = current.getPath();
738
739        DataManager manager = mActivity.getDataManager();
740        int action = item.getItemId();
741        String confirmMsg = null;
742        switch (action) {
743            case android.R.id.home: {
744                onUpPressed();
745                return true;
746            }
747            case R.id.action_grid: {
748                if (mStartedFromAlbumPage) {
749                    onUpPressed();
750                } else {
751                    preparePhotoFallbackView();
752                    Bundle data = new Bundle(getData());
753                    data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
754                    data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
755                            mActivity.getDataManager().getTopSetPath(
756                                    DataManager.INCLUDE_ALL));
757                    mActivity.getTransitionStore().put(
758                            KEY_RETURN_INDEX_HINT, mCurrentIndex);
759                    mActivity.getStateManager().startState(AlbumPage.class, data);
760                }
761                return true;
762            }
763            case R.id.action_slideshow: {
764                Bundle data = new Bundle();
765                data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
766                data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString());
767                data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
768                data.putBoolean(SlideshowPage.KEY_REPEAT, true);
769                mActivity.getStateManager().startStateForResult(
770                        SlideshowPage.class, REQUEST_SLIDESHOW, data);
771                return true;
772            }
773            case R.id.action_crop: {
774                Activity activity = mActivity;
775                Intent intent = new Intent(CropImage.CROP_ACTION);
776                intent.setClass(activity, CropImage.class);
777                intent.setData(manager.getContentUri(path));
778                activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
779                        ? REQUEST_CROP_PICASA
780                        : REQUEST_CROP);
781                return true;
782            }
783            case R.id.action_trim: {
784                Intent intent = new Intent(mActivity, TrimVideo.class);
785                intent.setData(manager.getContentUri(path));
786                mActivity.startActivityForResult(intent, REQUEST_TRIM);
787                return true;
788            }
789            case R.id.action_edit: {
790                launchPhotoEditor();
791                return true;
792            }
793            case R.id.action_details: {
794                if (mShowDetails) {
795                    hideDetails();
796                } else {
797                    showDetails();
798                }
799                return true;
800            }
801            case R.id.action_delete:
802                confirmMsg = mActivity.getResources().getQuantityString(
803                        R.plurals.delete_selection, 1);
804            case R.id.action_setas:
805            case R.id.action_rotate_ccw:
806            case R.id.action_rotate_cw:
807            case R.id.action_show_on_map:
808                mSelectionManager.deSelectAll();
809                mSelectionManager.toggle(path);
810                mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
811                return true;
812            case R.id.action_import:
813                mSelectionManager.deSelectAll();
814                mSelectionManager.toggle(path);
815                mMenuExecutor.onMenuClicked(item, confirmMsg,
816                        new ImportCompleteListener(mActivity));
817                return true;
818            case R.id.action_share:
819                Activity activity = mActivity;
820                Intent intent = createShareIntent(mCurrentPhoto.getPath());
821                activity.startActivity(Intent.createChooser(intent,
822                        activity.getString(R.string.share)));
823                return true;
824            default :
825                return false;
826        }
827    }
828
829    private void hideDetails() {
830        mShowDetails = false;
831        mDetailsHelper.hide();
832    }
833
834    private void showDetails() {
835        mShowDetails = true;
836        if (mDetailsHelper == null) {
837            mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
838            mDetailsHelper.setCloseListener(new CloseListener() {
839                @Override
840                public void onClose() {
841                    hideDetails();
842                }
843            });
844        }
845        mDetailsHelper.show();
846    }
847
848    ////////////////////////////////////////////////////////////////////////////
849    //  Callbacks from PhotoView
850    ////////////////////////////////////////////////////////////////////////////
851    @Override
852    public void onSingleTapUp(int x, int y) {
853        if (mAppBridge != null) {
854            if (mAppBridge.onSingleTapUp(x, y)) return;
855        }
856
857        MediaItem item = mModel.getMediaItem(0);
858        if (item == null || item == mScreenNailItem) {
859            // item is not ready or it is camera preview, ignore
860            return;
861        }
862
863        boolean playVideo = (mSecureAlbum == null) &&
864                ((item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0);
865        boolean viewPanorama = (mSecureAlbum == null) &&
866                (item.getSupportedOperations() & MediaItem.SUPPORT_VIEW_PANORAMA) != 0;
867
868        if (playVideo) {
869            // determine if the point is at center (1/6) of the photo view.
870            // (The position of the "play" icon is at center (1/6) of the photo)
871            int w = mPhotoView.getWidth();
872            int h = mPhotoView.getHeight();
873            playVideo = (Math.abs(x - w / 2) * 12 <= w)
874                && (Math.abs(y - h / 2) * 12 <= h);
875        }
876
877        if (playVideo) {
878            playVideo(mActivity, item.getPlayUri(), item.getName());
879        } else if (viewPanorama) {
880            LocalImage img = (LocalImage) item;
881            LightCycleHelper.viewPanorama(mActivity, img.getFilePath());
882        } else {
883            toggleBars();
884        }
885    }
886
887    @Override
888    public void lockOrientation() {
889        mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION);
890    }
891
892    @Override
893    public void unlockOrientation() {
894        mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION);
895    }
896
897    @Override
898    public void onActionBarAllowed(boolean allowed) {
899        mActionBarAllowed = allowed;
900        mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
901    }
902
903    @Override
904    public void onActionBarWanted() {
905        mHandler.sendEmptyMessage(MSG_WANT_BARS);
906    }
907
908    @Override
909    public void onFullScreenChanged(boolean full) {
910        Message m = mHandler.obtainMessage(
911                MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
912        m.sendToTarget();
913    }
914
915    // How we do delete/undo:
916    //
917    // When the user choose to delete a media item, we just tell the
918    // FilterDeleteSet to hide that item. If the user choose to undo it, we
919    // again tell FilterDeleteSet not to hide it. If the user choose to commit
920    // the deletion, we then actually delete the media item.
921    @Override
922    public void onDeleteImage(Path path, int offset) {
923        onCommitDeleteImage();  // commit the previous deletion
924        mDeletePath = path;
925        mDeleteIsFocus = (offset == 0);
926        mMediaSet.addDeletion(path, mCurrentIndex + offset);
927    }
928
929    @Override
930    public void onUndoDeleteImage() {
931        if (mDeletePath == null) return;
932        // If the deletion was done on the focused item, we want the model to
933        // focus on it when it is undeleted.
934        if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath);
935        mMediaSet.removeDeletion(mDeletePath);
936        mDeletePath = null;
937    }
938
939    @Override
940    public void onCommitDeleteImage() {
941        if (mDeletePath == null) return;
942        mSelectionManager.deSelectAll();
943        mSelectionManager.toggle(mDeletePath);
944        mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false);
945        mDeletePath = null;
946    }
947
948    public static void playVideo(Activity activity, Uri uri, String title) {
949        try {
950            Intent intent = new Intent(Intent.ACTION_VIEW)
951                    .setDataAndType(uri, "video/*")
952                    .putExtra(Intent.EXTRA_TITLE, title)
953                    .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true);
954            activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO);
955        } catch (ActivityNotFoundException e) {
956            Toast.makeText(activity, activity.getString(R.string.video_err),
957                    Toast.LENGTH_SHORT).show();
958        }
959    }
960
961    private void setCurrentPhotoByIntent(Intent intent) {
962        if (intent == null) return;
963        Path path = mApplication.getDataManager()
964                .findPathByUri(intent.getData(), intent.getType());
965        if (path != null) {
966            mModel.setCurrentPhoto(path, mCurrentIndex);
967        }
968    }
969
970    @Override
971    protected void onStateResult(int requestCode, int resultCode, Intent data) {
972        mHasActivityResult = true;
973        switch (requestCode) {
974            case REQUEST_EDIT:
975                setCurrentPhotoByIntent(data);
976                break;
977            case REQUEST_CROP:
978                if (resultCode == Activity.RESULT_OK) {
979                    setCurrentPhotoByIntent(data);
980                }
981                break;
982            case REQUEST_CROP_PICASA: {
983                if (resultCode == Activity.RESULT_OK) {
984                    Context context = mActivity.getAndroidContext();
985                    String message = context.getString(R.string.crop_saved,
986                            context.getString(R.string.folder_download));
987                    Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
988                }
989                break;
990            }
991            case REQUEST_SLIDESHOW: {
992                if (data == null) break;
993                String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
994                int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
995                if (path != null) {
996                    mModel.setCurrentPhoto(Path.fromString(path), index);
997                }
998            }
999        }
1000    }
1001
1002    @Override
1003    protected void clearStateResult() {
1004        mHasActivityResult = false;
1005    }
1006
1007    private class PreparePhotoFallback implements OnGLIdleListener {
1008        private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect();
1009        private boolean mResultReady = false;
1010
1011        public synchronized PhotoFallbackEffect get() {
1012            while (!mResultReady) {
1013                Utils.waitWithoutInterrupt(this);
1014            }
1015            return mPhotoFallback;
1016        }
1017
1018        @Override
1019        public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) {
1020            mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas);
1021            synchronized (this) {
1022                mResultReady = true;
1023                notifyAll();
1024            }
1025            return false;
1026        }
1027    }
1028
1029    private void preparePhotoFallbackView() {
1030        GLRoot root = mActivity.getGLRoot();
1031        PreparePhotoFallback task = new PreparePhotoFallback();
1032        root.unlockRenderThread();
1033        PhotoFallbackEffect anim;
1034        try {
1035            root.addOnGLIdleListener(task);
1036            anim = task.get();
1037        } finally {
1038            root.lockRenderThread();
1039        }
1040        mActivity.getTransitionStore().put(
1041                AlbumPage.KEY_RESUME_ANIMATION, anim);
1042    }
1043
1044    @Override
1045    public void onPause() {
1046        super.onPause();
1047        mIsActive = false;
1048
1049        mActivity.getGLRoot().unfreeze();
1050        mHandler.removeMessages(MSG_UNFREEZE_GLROOT);
1051        if (isFinishing()) preparePhotoFallbackView();
1052
1053        DetailsHelper.pause();
1054        mPhotoView.pause();
1055        mModel.pause();
1056        mHandler.removeMessages(MSG_HIDE_BARS);
1057        mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
1058
1059        onCommitDeleteImage();
1060        mMenuExecutor.pause();
1061        if (mMediaSet != null) mMediaSet.clearDeletion();
1062    }
1063
1064    @Override
1065    public void onCurrentImageUpdated() {
1066        mActivity.getGLRoot().unfreeze();
1067    }
1068
1069    private void setGridButtonVisibility(boolean enabled) {
1070        Menu menu = mActionBar.getMenu();
1071        if (menu == null) return;
1072        MenuItem item = menu.findItem(R.id.action_grid);
1073        if (item != null) item.setVisible(enabled);
1074    }
1075
1076    public void onFilmModeChanged(boolean enabled) {
1077        mHandler.sendEmptyMessage(MSG_REFRESH_GRID_BUTTON);
1078        mHandler.sendEmptyMessage(MSG_REFRESH_EDIT_BUTTON);
1079        if (enabled) {
1080            mHandler.removeMessages(MSG_HIDE_BARS);
1081        } else {
1082            refreshHidingMessage();
1083        }
1084    }
1085
1086    private void transitionFromAlbumPageIfNeeded() {
1087        TransitionStore transitions = mActivity.getTransitionStore();
1088
1089        int resumeIndex = transitions.get(KEY_INDEX_HINT, -1);
1090        if (resumeIndex >= 0) {
1091            mCurrentIndex = resumeIndex;
1092            mModel.setCurrentPhoto((Path)transitions.get(KEY_MEDIA_SET_PATH), mCurrentIndex);
1093            mPhotoView.switchToImage(mCurrentIndex);
1094        }
1095
1096        int albumPageTransition = transitions.get(
1097                KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE);
1098
1099        if(albumPageTransition != MSG_ALBUMPAGE_NONE) {
1100            mPhotoView.setFilmMode(mStartInFilmstrip
1101                    && albumPageTransition == MSG_ALBUMPAGE_RESUMED);
1102        }
1103
1104        mFadeOutTexture = transitions.get(AlbumPage.KEY_FADE_TEXTURE);
1105        if (mFadeOutTexture != null) {
1106            mBackgroundFade.start();
1107            BitmapScreenNail.disableDrawPlaceholder();
1108            mOpenAnimationRect =
1109                    albumPageTransition == MSG_ALBUMPAGE_NONE ?
1110                    (Rect) mData.getParcelable(KEY_OPEN_ANIMATION_RECT) :
1111                    (Rect) transitions.get(KEY_OPEN_ANIMATION_RECT);
1112            mPhotoView.setOpenAnimationRect(mOpenAnimationRect);
1113            mBackgroundFade.start();
1114        }
1115    }
1116
1117    @Override
1118    protected void onResume() {
1119        super.onResume();
1120
1121        transitionFromAlbumPageIfNeeded();
1122
1123        mActivity.getGLRoot().freeze();
1124        mIsActive = true;
1125        setContentPane(mRootPane);
1126
1127        mModel.resume();
1128        mPhotoView.resume();
1129        mActionBar.setDisplayOptions(
1130                ((mSecureAlbum == null) && (mSetPathString != null)), true);
1131        mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
1132
1133        if (mAppBridge != null && !mHasActivityResult) {
1134            mPhotoView.resetToFirstPicture();
1135        }
1136        mHasActivityResult = false;
1137        mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
1138    }
1139
1140    @Override
1141    protected void onDestroy() {
1142        if (mAppBridge != null) {
1143            mAppBridge.setServer(null);
1144            mScreenNailItem.setScreenNail(null);
1145            mAppBridge.detachScreenNail();
1146            mAppBridge = null;
1147            mScreenNailSet = null;
1148            mScreenNailItem = null;
1149        }
1150        mOrientationManager.removeListener(this);
1151        mActivity.getGLRoot().setOrientationSource(null);
1152        cleanupEditButton();
1153
1154        // Remove all pending messages.
1155        mHandler.removeCallbacksAndMessages(null);
1156        super.onDestroy();
1157    }
1158
1159    private class MyDetailsSource implements DetailsSource {
1160
1161        @Override
1162        public MediaDetails getDetails() {
1163            return mModel.getMediaItem(0).getDetails();
1164        }
1165
1166        @Override
1167        public int size() {
1168            return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
1169        }
1170
1171        @Override
1172        public int setIndex() {
1173            return mModel.getCurrentIndex();
1174        }
1175    }
1176}
1177