AlbumPage.java revision 48ba94ae713dbf57898cfa84ae69517da50cf7a0
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.app.Activity;
20import android.content.Context;
21import android.content.Intent;
22import android.graphics.Rect;
23import android.net.Uri;
24import android.os.Bundle;
25import android.os.Vibrator;
26import android.provider.MediaStore;
27import android.view.ActionMode;
28import android.view.Menu;
29import android.view.MenuInflater;
30import android.view.MenuItem;
31import android.widget.Toast;
32
33import com.android.gallery3d.R;
34import com.android.gallery3d.common.Utils;
35import com.android.gallery3d.data.DataManager;
36import com.android.gallery3d.data.MediaDetails;
37import com.android.gallery3d.data.MediaItem;
38import com.android.gallery3d.data.MediaObject;
39import com.android.gallery3d.data.MediaSet;
40import com.android.gallery3d.data.MtpDevice;
41import com.android.gallery3d.data.Path;
42import com.android.gallery3d.ui.ActionModeHandler;
43import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
44import com.android.gallery3d.ui.AlbumSlotRenderer;
45import com.android.gallery3d.ui.DetailsHelper;
46import com.android.gallery3d.ui.DetailsHelper.CloseListener;
47import com.android.gallery3d.ui.GLCanvas;
48import com.android.gallery3d.ui.GLRoot;
49import com.android.gallery3d.ui.GLView;
50import com.android.gallery3d.ui.RelativePosition;
51import com.android.gallery3d.ui.ScreenNailHolder;
52import com.android.gallery3d.ui.SelectionManager;
53import com.android.gallery3d.ui.SlotView;
54import com.android.gallery3d.util.Future;
55import com.android.gallery3d.util.GalleryUtils;
56
57public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,
58        SelectionManager.SelectionListener, MediaSet.SyncListener {
59    @SuppressWarnings("unused")
60    private static final String TAG = "AlbumPage";
61
62    public static final String KEY_MEDIA_PATH = "media-path";
63    public static final String KEY_PARENT_MEDIA_PATH = "parent-media-path";
64    public static final String KEY_SET_CENTER = "set-center";
65    public static final String KEY_AUTO_SELECT_ALL = "auto-select-all";
66    public static final String KEY_SHOW_CLUSTER_MENU = "cluster-menu";
67
68    private static final int REQUEST_SLIDESHOW = 1;
69    private static final int REQUEST_PHOTO = 2;
70    private static final int REQUEST_DO_ANIMATION = 3;
71
72    private static final int BIT_LOADING_RELOAD = 1;
73    private static final int BIT_LOADING_SYNC = 2;
74
75    private static final float USER_DISTANCE_METER = 0.3f;
76    private static final boolean TEST_CAMERA_PREVIEW = false;
77
78    private boolean mIsActive = false;
79    private AlbumSlotRenderer mAlbumView;
80    private Path mMediaSetPath;
81    private String mParentMediaSetString;
82    private SlotView mSlotView;
83
84    private AlbumDataLoader mAlbumDataAdapter;
85
86    protected SelectionManager mSelectionManager;
87    private Vibrator mVibrator;
88
89    private boolean mGetContent;
90    private boolean mShowClusterMenu;
91
92    private ActionMode mActionMode;
93    private ActionModeHandler mActionModeHandler;
94    private int mFocusIndex = 0;
95    private DetailsHelper mDetailsHelper;
96    private MyDetailsSource mDetailsSource;
97    private MediaSet mMediaSet;
98    private boolean mShowDetails;
99    private float mUserDistance; // in pixel
100
101    private Future<Integer> mSyncTask = null;
102
103    private int mLoadingBits = 0;
104    private boolean mInitialSynced = false;
105    private RelativePosition mOpenCenter = new RelativePosition();
106
107    private final GLView mRootPane = new GLView() {
108        private final float mMatrix[] = new float[16];
109
110        @Override
111        protected void renderBackground(GLCanvas view) {
112            view.clearBuffer();
113        }
114
115        @Override
116        protected void onLayout(
117                boolean changed, int left, int top, int right, int bottom) {
118
119            int slotViewTop = mActivity.getGalleryActionBar().getHeight();
120            int slotViewBottom = bottom - top;
121            int slotViewRight = right - left;
122
123            if (mShowDetails) {
124                mDetailsHelper.layout(left, slotViewTop, right, bottom);
125            } else {
126                mAlbumView.setHighlightItemPath(null);
127            }
128
129            // Set the mSlotView as a reference point to the open animation
130            mOpenCenter.setReferencePosition(0, slotViewTop);
131            mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
132            GalleryUtils.setViewPointMatrix(mMatrix,
133                    (right - left) / 2, (bottom - top) / 2, -mUserDistance);
134        }
135
136        @Override
137        protected void render(GLCanvas canvas) {
138            canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
139            canvas.multiplyMatrix(mMatrix, 0);
140            super.render(canvas);
141            canvas.restore();
142        }
143    };
144
145    @Override
146    protected void onBackPressed() {
147        if (mShowDetails) {
148            hideDetails();
149        } else if (mSelectionManager.inSelectionMode()) {
150            mSelectionManager.leaveSelectionMode();
151        } else {
152            // TODO: fix this regression
153            // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
154            super.onBackPressed();
155        }
156    }
157
158    private void onDown(int index) {
159        mAlbumView.setPressedIndex(index);
160    }
161
162    private void onUp() {
163        mAlbumView.setPressedIndex(-1);
164    }
165
166    private void onSingleTapUp(int slotIndex) {
167        if (!mIsActive) return;
168        MediaItem item = mAlbumDataAdapter.get(slotIndex);
169        if (item == null) {
170            Log.w(TAG, "item not ready yet, ignore the click");
171            return;
172        }
173        if (mShowDetails) {
174            mAlbumView.setHighlightItemPath(item.getPath());
175            mDetailsHelper.reloadDetails(slotIndex);
176        } else if (!mSelectionManager.inSelectionMode()) {
177            if (mGetContent) {
178                onGetContent(item);
179            } else {
180                // Get into the PhotoPage.
181                Bundle data = new Bundle();
182
183                // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
184                data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
185                data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT,
186                        getSlotRect(slotIndex));
187                data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
188                        mMediaSetPath.toString());
189                data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
190                        item.getPath().toString());
191                if (TEST_CAMERA_PREVIEW) {
192                    ScreenNailHolder holder = new CameraScreenNailHolder(mActivity);
193                    data.putParcelable(PhotoPage.KEY_SCREENNAIL_HOLDER, holder);
194                }
195                mActivity.getStateManager().startStateForResult(
196                        PhotoPage.class, REQUEST_PHOTO, data);
197            }
198        } else {
199            mSelectionManager.toggle(item.getPath());
200            mDetailsSource.findIndex(slotIndex);
201            mSlotView.invalidate();
202        }
203    }
204
205    private Rect getSlotRect(int slotIndex) {
206        // Get slot rectangle relative to this root pane.
207        Rect offset = new Rect();
208        mRootPane.getBoundsOf(mSlotView, offset);
209        Rect r = mSlotView.getSlotRect(slotIndex);
210        r.offset(offset.left - mSlotView.getScrollX(),
211                offset.top - mSlotView.getScrollY());
212        return r;
213    }
214
215    private void onGetContent(final MediaItem item) {
216        DataManager dm = mActivity.getDataManager();
217        Activity activity = (Activity) mActivity;
218        if (mData.getString(Gallery.EXTRA_CROP) != null) {
219            // TODO: Handle MtpImagew
220            Uri uri = dm.getContentUri(item.getPath());
221            Intent intent = new Intent(CropImage.ACTION_CROP, uri)
222                    .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
223                    .putExtras(getData());
224            if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
225                intent.putExtra(CropImage.KEY_RETURN_DATA, true);
226            }
227            activity.startActivity(intent);
228            activity.finish();
229        } else {
230            activity.setResult(Activity.RESULT_OK,
231                    new Intent(null, item.getContentUri()));
232            activity.finish();
233        }
234    }
235
236    public void onLongTap(int slotIndex) {
237        if (mGetContent) return;
238        if (mShowDetails) {
239            onSingleTapUp(slotIndex);
240        } else {
241            MediaItem item = mAlbumDataAdapter.get(slotIndex);
242            if (item == null) return;
243            mSelectionManager.setAutoLeaveSelectionMode(true);
244            mSelectionManager.toggle(item.getPath());
245            mDetailsSource.findIndex(slotIndex);
246            mSlotView.invalidate();
247        }
248    }
249
250    @Override
251    public void doCluster(int clusterType) {
252        String basePath = mMediaSet.getPath().toString();
253        String newPath = FilterUtils.newClusterPath(basePath, clusterType);
254        Bundle data = new Bundle(getData());
255        data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
256        if (mShowClusterMenu) {
257            Context context = mActivity.getAndroidContext();
258            data.putString(AlbumSetPage.KEY_SET_TITLE, mMediaSet.getName());
259            data.putString(AlbumSetPage.KEY_SET_SUBTITLE,
260                    GalleryActionBar.getClusterByTypeString(context, clusterType));
261        }
262
263        // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
264        mActivity.getStateManager().startStateForResult(
265                AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
266    }
267
268    @Override
269    protected void onCreate(Bundle data, Bundle restoreState) {
270        mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
271        initializeViews();
272        initializeData(data);
273        mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false);
274        mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false);
275        mDetailsSource = new MyDetailsSource();
276        Context context = mActivity.getAndroidContext();
277        mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
278
279        // Enable auto-select-all for mtp album
280        if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
281            mSelectionManager.selectAll();
282        }
283
284        // Don't show animation if it is restored
285        if (restoreState == null && data != null) {
286            int[] center = data.getIntArray(KEY_SET_CENTER);
287            if (center != null) {
288                mOpenCenter.setAbsolutePosition(center[0], center[1]);
289                mSlotView.startScatteringAnimation(mOpenCenter);
290            }
291        }
292    }
293
294    @Override
295    protected void onResume() {
296        super.onResume();
297        mIsActive = true;
298        setContentPane(mRootPane);
299
300        Path path = mMediaSet.getPath();
301        boolean enableHomeButton = (mActivity.getStateManager().getStateCount() > 1) |
302                mParentMediaSetString != null;
303        mActivity.getGalleryActionBar().setDisplayOptions(enableHomeButton, true);
304
305        // Set the reload bit here to prevent it exit this page in clearLoadingBit().
306        setLoadingBit(BIT_LOADING_RELOAD);
307        mAlbumDataAdapter.resume();
308
309        mAlbumView.resume();
310        mActionModeHandler.resume();
311        if (!mInitialSynced) {
312            setLoadingBit(BIT_LOADING_SYNC);
313            mSyncTask = mMediaSet.requestSync(this);
314        }
315    }
316
317    @Override
318    protected void onPause() {
319        super.onPause();
320        mIsActive = false;
321        mAlbumDataAdapter.pause();
322        mAlbumView.pause();
323        DetailsHelper.pause();
324
325        if (mSyncTask != null) {
326            mSyncTask.cancel();
327            mSyncTask = null;
328            clearLoadingBit(BIT_LOADING_SYNC);
329        }
330        mActionModeHandler.pause();
331        GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
332    }
333
334    @Override
335    protected void onDestroy() {
336        super.onDestroy();
337        if (mAlbumDataAdapter != null) {
338            mAlbumDataAdapter.setLoadingListener(null);
339        }
340    }
341
342    private void initializeViews() {
343        mSelectionManager = new SelectionManager(mActivity, false);
344        mSelectionManager.setSelectionListener(this);
345        Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity);
346        mSlotView = new SlotView(mActivity, config.slotViewSpec);
347        mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView, mSelectionManager);
348        mSlotView.setSlotRenderer(mAlbumView);
349        mRootPane.addComponent(mSlotView);
350        mSlotView.setListener(new SlotView.SimpleListener() {
351            @Override
352            public void onDown(int index) {
353                AlbumPage.this.onDown(index);
354            }
355
356            @Override
357            public void onUp() {
358                AlbumPage.this.onUp();
359            }
360
361            @Override
362            public void onSingleTapUp(int slotIndex) {
363                AlbumPage.this.onSingleTapUp(slotIndex);
364            }
365
366            @Override
367            public void onLongTap(int slotIndex) {
368                AlbumPage.this.onLongTap(slotIndex);
369            }
370        });
371        mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
372        mActionModeHandler.setActionModeListener(new ActionModeListener() {
373            public boolean onActionItemClicked(MenuItem item) {
374                return onItemSelected(item);
375            }
376        });
377    }
378
379    private void initializeData(Bundle data) {
380        mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH));
381        mParentMediaSetString = data.getString(KEY_PARENT_MEDIA_PATH);
382        mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath);
383        if (mMediaSet == null) {
384            Utils.fail("MediaSet is null. Path = %s", mMediaSetPath);
385        }
386        mSelectionManager.setSourceMediaSet(mMediaSet);
387        mAlbumDataAdapter = new AlbumDataLoader(mActivity, mMediaSet);
388        mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
389        mAlbumView.setModel(mAlbumDataAdapter);
390    }
391
392    private void showDetails() {
393        mShowDetails = true;
394        if (mDetailsHelper == null) {
395            mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
396            mDetailsHelper.setCloseListener(new CloseListener() {
397                public void onClose() {
398                    hideDetails();
399                }
400            });
401        }
402        mDetailsHelper.show();
403    }
404
405    private void hideDetails() {
406        mShowDetails = false;
407        mDetailsHelper.hide();
408        mAlbumView.setHighlightItemPath(null);
409        mSlotView.invalidate();
410    }
411
412    @Override
413    protected boolean onCreateActionBar(Menu menu) {
414        Activity activity = (Activity) mActivity;
415        GalleryActionBar actionBar = mActivity.getGalleryActionBar();
416        MenuInflater inflater = activity.getMenuInflater();
417
418        if (mGetContent) {
419            inflater.inflate(R.menu.pickup, menu);
420            int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS,
421                    DataManager.INCLUDE_IMAGE);
422
423            actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
424        } else {
425            inflater.inflate(R.menu.album, menu);
426            actionBar.setTitle(mMediaSet.getName());
427            if (mMediaSet instanceof MtpDevice) {
428                menu.findItem(R.id.action_slideshow).setVisible(false);
429            } else {
430                menu.findItem(R.id.action_slideshow).setVisible(true);
431            }
432
433            MenuItem groupBy = menu.findItem(R.id.action_group_by);
434            FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
435
436            if (groupBy != null) {
437                groupBy.setVisible(mShowClusterMenu);
438            }
439
440            actionBar.setTitle(mMediaSet.getName());
441        }
442        actionBar.setSubtitle(null);
443
444        return true;
445    }
446
447    @Override
448    protected boolean onItemSelected(MenuItem item) {
449        switch (item.getItemId()) {
450            case android.R.id.home: {
451                if (mActivity.getStateManager().getStateCount() > 1) {
452                    onBackPressed();
453                } else if (mParentMediaSetString != null) {
454                    Bundle data = new Bundle(getData());
455                    data.putString(AlbumSetPage.KEY_MEDIA_PATH, mParentMediaSetString);
456                    mActivity.getStateManager().switchState(this, AlbumSetPage.class, data);
457                }
458                return true;
459            }
460            case R.id.action_cancel:
461                mActivity.getStateManager().finishState(this);
462                return true;
463            case R.id.action_select:
464                mSelectionManager.setAutoLeaveSelectionMode(false);
465                mSelectionManager.enterSelectionMode();
466                return true;
467            case R.id.action_group_by: {
468                mActivity.getGalleryActionBar().showClusterDialog(this);
469                return true;
470            }
471            case R.id.action_slideshow: {
472                Bundle data = new Bundle();
473                data.putString(SlideshowPage.KEY_SET_PATH,
474                        mMediaSetPath.toString());
475                data.putBoolean(SlideshowPage.KEY_REPEAT, true);
476                mActivity.getStateManager().startStateForResult(
477                        SlideshowPage.class, REQUEST_SLIDESHOW, data);
478                return true;
479            }
480            case R.id.action_details: {
481                if (mShowDetails) {
482                    hideDetails();
483                } else {
484                    showDetails();
485                }
486                return true;
487            }
488            default:
489                return false;
490        }
491    }
492
493    @Override
494    protected void onStateResult(int request, int result, Intent data) {
495        switch (request) {
496            case REQUEST_SLIDESHOW: {
497                // data could be null, if there is no images in the album
498                if (data == null) return;
499                mFocusIndex = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
500                mSlotView.setCenterIndex(mFocusIndex);
501                break;
502            }
503            case REQUEST_PHOTO: {
504                if (data == null) return;
505                mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0);
506                mSlotView.setCenterIndex(mFocusIndex);
507                mSlotView.startRestoringAnimation(mFocusIndex);
508                break;
509            }
510            case REQUEST_DO_ANIMATION: {
511                mSlotView.startRisingAnimation();
512                break;
513            }
514        }
515    }
516
517    public void onSelectionModeChange(int mode) {
518        switch (mode) {
519            case SelectionManager.ENTER_SELECTION_MODE: {
520                mActionMode = mActionModeHandler.startActionMode();
521                mVibrator.vibrate(100);
522                break;
523            }
524            case SelectionManager.LEAVE_SELECTION_MODE: {
525                mActionMode.finish();
526                mRootPane.invalidate();
527                break;
528            }
529            case SelectionManager.SELECT_ALL_MODE: {
530                mActionModeHandler.updateSupportedOperation();
531                mRootPane.invalidate();
532                break;
533            }
534        }
535    }
536
537    public void onSelectionChange(Path path, boolean selected) {
538        Utils.assertTrue(mActionMode != null);
539        int count = mSelectionManager.getSelectedCount();
540        String format = mActivity.getResources().getQuantityString(
541                R.plurals.number_of_items_selected, count);
542        mActionModeHandler.setTitle(String.format(format, count));
543        mActionModeHandler.updateSupportedOperation(path, selected);
544    }
545
546    @Override
547    public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
548        Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
549                + resultCode);
550        ((Activity) mActivity).runOnUiThread(new Runnable() {
551            @Override
552            public void run() {
553                GLRoot root = mActivity.getGLRoot();
554                root.lockRenderThread();
555                try {
556                    if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
557                        mInitialSynced = true;
558                    }
559                    clearLoadingBit(BIT_LOADING_SYNC);
560                    if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
561                        Toast.makeText((Context) mActivity, R.string.sync_album_error,
562                                Toast.LENGTH_LONG).show();
563                    }
564                } finally {
565                    root.unlockRenderThread();
566                }
567            }
568        });
569    }
570
571    private void setLoadingBit(int loadTaskBit) {
572        if (mLoadingBits == 0 && mIsActive) {
573            GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
574        }
575        mLoadingBits |= loadTaskBit;
576    }
577
578    private void clearLoadingBit(int loadTaskBit) {
579        mLoadingBits &= ~loadTaskBit;
580        if (mLoadingBits == 0 && mIsActive) {
581            GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
582
583            if (mAlbumDataAdapter.size() == 0) {
584                Toast.makeText((Context) mActivity,
585                        R.string.empty_album, Toast.LENGTH_LONG).show();
586                mActivity.getStateManager().finishState(AlbumPage.this);
587            }
588        }
589    }
590
591    private class MyLoadingListener implements LoadingListener {
592        @Override
593        public void onLoadingStarted() {
594            setLoadingBit(BIT_LOADING_RELOAD);
595        }
596
597        @Override
598        public void onLoadingFinished() {
599            clearLoadingBit(BIT_LOADING_RELOAD);
600        }
601    }
602
603    private class MyDetailsSource implements DetailsHelper.DetailsSource {
604        private int mIndex;
605
606        public int size() {
607            return mAlbumDataAdapter.size();
608        }
609
610        public int getIndex() {
611            return mIndex;
612        }
613
614        // If requested index is out of active window, suggest a valid index.
615        // If there is no valid index available, return -1.
616        public int findIndex(int indexHint) {
617            if (mAlbumDataAdapter.isActive(indexHint)) {
618                mIndex = indexHint;
619            } else {
620                mIndex = mAlbumDataAdapter.getActiveStart();
621                if (!mAlbumDataAdapter.isActive(mIndex)) {
622                    return -1;
623                }
624            }
625            return mIndex;
626        }
627
628        public MediaDetails getDetails() {
629            MediaObject item = mAlbumDataAdapter.get(mIndex);
630            if (item != null) {
631                mAlbumView.setHighlightItemPath(item.getPath());
632                return item.getDetails();
633            } else {
634                return null;
635            }
636        }
637    }
638}
639