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