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