AlbumSetPage.java revision b21b8e58a604f6c701245d84b141b5b87663192b
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.os.Bundle;
24import android.os.Handler;
25import android.os.Message;
26import android.os.Vibrator;
27import android.widget.Toast;
28
29import com.actionbarsherlock.view.Menu;
30import com.actionbarsherlock.view.MenuInflater;
31import com.actionbarsherlock.view.MenuItem;
32import com.android.gallery3d.R;
33import com.android.gallery3d.common.Utils;
34import com.android.gallery3d.data.DataManager;
35import com.android.gallery3d.data.MediaDetails;
36import com.android.gallery3d.data.MediaObject;
37import com.android.gallery3d.data.MediaSet;
38import com.android.gallery3d.data.Path;
39import com.android.gallery3d.picasasource.PicasaSource;
40import com.android.gallery3d.settings.GallerySettings;
41import com.android.gallery3d.ui.ActionModeHandler;
42import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
43import com.android.gallery3d.ui.AlbumSetSlotRenderer;
44import com.android.gallery3d.ui.DetailsHelper;
45import com.android.gallery3d.ui.DetailsHelper.CloseListener;
46import com.android.gallery3d.ui.FadeTexture;
47import com.android.gallery3d.ui.GLCanvas;
48import com.android.gallery3d.ui.GLRoot;
49import com.android.gallery3d.ui.GLView;
50import com.android.gallery3d.ui.SelectionManager;
51import com.android.gallery3d.ui.SlotView;
52import com.android.gallery3d.ui.SynchronizedHandler;
53import com.android.gallery3d.util.Future;
54import com.android.gallery3d.util.GalleryUtils;
55import com.android.gallery3d.util.HelpUtils;
56
57public class AlbumSetPage extends ActivityState implements
58        SelectionManager.SelectionListener, GalleryActionBar.ClusterRunner,
59        EyePosition.EyePositionListener, MediaSet.SyncListener {
60    @SuppressWarnings("unused")
61    private static final String TAG = "AlbumSetPage";
62
63    private static final int MSG_PICK_ALBUM = 1;
64
65    public static final String KEY_MEDIA_PATH = "media-path";
66    public static final String KEY_SET_TITLE = "set-title";
67    public static final String KEY_SET_SUBTITLE = "set-subtitle";
68    public static final String KEY_SELECTED_CLUSTER_TYPE = "selected-cluster";
69
70    private static final int DATA_CACHE_SIZE = 256;
71    private static final int REQUEST_DO_ANIMATION = 1;
72
73    private static final int BIT_LOADING_RELOAD = 1;
74    private static final int BIT_LOADING_SYNC = 2;
75
76    private boolean mIsActive = false;
77    private SlotView mSlotView;
78    private AlbumSetSlotRenderer mAlbumSetView;
79    private Config.AlbumSetPage mConfig;
80
81    private MediaSet mMediaSet;
82    private String mTitle;
83    private String mSubtitle;
84    private boolean mShowClusterMenu;
85    private GalleryActionBar mActionBar;
86    private int mSelectedAction;
87    private Vibrator mVibrator;
88
89    protected SelectionManager mSelectionManager;
90    private AlbumSetDataLoader mAlbumSetDataAdapter;
91
92    private boolean mGetContent;
93    private boolean mGetAlbum;
94    private ActionModeHandler mActionModeHandler;
95    private DetailsHelper mDetailsHelper;
96    private MyDetailsSource mDetailsSource;
97    private boolean mShowDetails;
98    private EyePosition mEyePosition;
99    private Handler mHandler;
100
101    // The eyes' position of the user, the origin is at the center of the
102    // device and the unit is in pixels.
103    private float mX;
104    private float mY;
105    private float mZ;
106
107    private Future<Integer> mSyncTask = null;
108
109    private int mLoadingBits = 0;
110    private boolean mInitialSynced = false;
111
112    @Override
113    protected int getBackgroundColorId() {
114        return R.color.albumset_background;
115    }
116
117    private final GLView mRootPane = new GLView() {
118        private final float mMatrix[] = new float[16];
119
120        @Override
121        protected void renderBackground(GLCanvas view) {
122            view.clearBuffer(getBackgroundColor());
123        }
124
125        @Override
126        protected void onLayout(
127                boolean changed, int left, int top, int right, int bottom) {
128            mEyePosition.resetPosition();
129
130            int slotViewTop = mActionBar.getHeight() + mConfig.paddingTop;
131            int slotViewBottom = bottom - top - mConfig.paddingBottom;
132            int slotViewRight = right - left;
133
134            if (mShowDetails) {
135                mDetailsHelper.layout(left, slotViewTop, right, bottom);
136            } else {
137                mAlbumSetView.setHighlightItemPath(null);
138            }
139
140            mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
141        }
142
143        @Override
144        protected void render(GLCanvas canvas) {
145            canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
146            GalleryUtils.setViewPointMatrix(mMatrix,
147                    getWidth() / 2 + mX, getHeight() / 2 + mY, mZ);
148            canvas.multiplyMatrix(mMatrix, 0);
149            super.render(canvas);
150            canvas.restore();
151        }
152    };
153
154    @Override
155    public void onEyePositionChanged(float x, float y, float z) {
156        mRootPane.lockRendering();
157        mX = x;
158        mY = y;
159        mZ = z;
160        mRootPane.unlockRendering();
161        mRootPane.invalidate();
162    }
163
164    @Override
165    public void onBackPressed() {
166        if (mShowDetails) {
167            hideDetails();
168        } else if (mSelectionManager.inSelectionMode()) {
169            mSelectionManager.leaveSelectionMode();
170        } else {
171            super.onBackPressed();
172        }
173    }
174
175    private void getSlotCenter(int slotIndex, int center[]) {
176        Rect offset = new Rect();
177        mRootPane.getBoundsOf(mSlotView, offset);
178        Rect r = mSlotView.getSlotRect(slotIndex);
179        int scrollX = mSlotView.getScrollX();
180        int scrollY = mSlotView.getScrollY();
181        center[0] = offset.left + (r.left + r.right) / 2 - scrollX;
182        center[1] = offset.top + (r.top + r.bottom) / 2 - scrollY;
183    }
184
185    public void onSingleTapUp(int slotIndex) {
186        if (!mIsActive) return;
187
188        if (mSelectionManager.inSelectionMode()) {
189            MediaSet targetSet = mAlbumSetDataAdapter.getMediaSet(slotIndex);
190            if (targetSet == null) return; // Content is dirty, we shall reload soon
191            mSelectionManager.toggle(targetSet.getPath());
192            mSlotView.invalidate();
193        } else {
194            // Show pressed-up animation for the single-tap.
195            mAlbumSetView.setPressedIndex(slotIndex);
196            mAlbumSetView.setPressedUp();
197            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PICK_ALBUM, slotIndex, 0),
198                    FadeTexture.DURATION);
199        }
200    }
201
202    private void pickAlbum(int slotIndex) {
203        if (!mIsActive) return;
204
205        MediaSet targetSet = mAlbumSetDataAdapter.getMediaSet(slotIndex);
206        if (targetSet == null) return; // Content is dirty, we shall reload soon
207        String mediaPath = targetSet.getPath().toString();
208
209        Bundle data = new Bundle(getData());
210        int[] center = new int[2];
211        getSlotCenter(slotIndex, center);
212        data.putIntArray(AlbumPage.KEY_SET_CENTER, center);
213        if (mGetAlbum && targetSet.isLeafAlbum()) {
214            Activity activity = mActivity;
215            Intent result = new Intent()
216                    .putExtra(AlbumPicker.KEY_ALBUM_PATH, targetSet.getPath().toString());
217            activity.setResult(Activity.RESULT_OK, result);
218            activity.finish();
219        } else if (targetSet.getSubMediaSetCount() > 0) {
220            data.putString(AlbumSetPage.KEY_MEDIA_PATH, mediaPath);
221            mActivity.getStateManager().startStateForResult(
222                    AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
223        } else {
224            if (!mGetContent && (targetSet.getSupportedOperations()
225                    & MediaObject.SUPPORT_IMPORT) != 0) {
226                data.putBoolean(AlbumPage.KEY_AUTO_SELECT_ALL, true);
227            }
228            data.putString(AlbumPage.KEY_MEDIA_PATH, mediaPath);
229            boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
230            // We only show cluster menu in the first AlbumPage in stack
231            data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum);
232            mActivity.getStateManager().startStateForResult(
233                    AlbumPage.class, REQUEST_DO_ANIMATION, data);
234        }
235    }
236
237    private void onDown(int index) {
238        mAlbumSetView.setPressedIndex(index);
239    }
240
241    private void onUp(boolean followedByLongPress) {
242        if (followedByLongPress) {
243            // Avoid showing press-up animations for long-press.
244            mAlbumSetView.setPressedIndex(-1);
245        } else {
246            mAlbumSetView.setPressedUp();
247        }
248    }
249
250    public void onLongTap(int slotIndex) {
251        if (mGetContent || mGetAlbum) return;
252        MediaSet set = mAlbumSetDataAdapter.getMediaSet(slotIndex);
253        if (set == null) return;
254        mSelectionManager.setAutoLeaveSelectionMode(true);
255        mSelectionManager.toggle(set.getPath());
256        mSlotView.invalidate();
257    }
258
259    @Override
260    public void doCluster(int clusterType) {
261        String basePath = mMediaSet.getPath().toString();
262        String newPath = FilterUtils.switchClusterPath(basePath, clusterType);
263        Bundle data = new Bundle(getData());
264        data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
265        data.putInt(KEY_SELECTED_CLUSTER_TYPE, clusterType);
266        mActivity.getStateManager().switchState(this, AlbumSetPage.class, data);
267    }
268
269    @Override
270    public void onCreate(Bundle data, Bundle restoreState) {
271        initializeViews();
272        initializeData(data);
273        Context context = mActivity.getAndroidContext();
274        mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false);
275        mGetAlbum = data.getBoolean(Gallery.KEY_GET_ALBUM, false);
276        mTitle = data.getString(AlbumSetPage.KEY_SET_TITLE);
277        mSubtitle = data.getString(AlbumSetPage.KEY_SET_SUBTITLE);
278        mEyePosition = new EyePosition(context, this);
279        mDetailsSource = new MyDetailsSource();
280        mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
281        mActionBar = mActivity.getGalleryActionBar();
282        mSelectedAction = data.getInt(AlbumSetPage.KEY_SELECTED_CLUSTER_TYPE,
283                FilterUtils.CLUSTER_BY_ALBUM);
284
285        mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
286            @Override
287            public void handleMessage(Message message) {
288                switch (message.what) {
289                    case MSG_PICK_ALBUM: {
290                        pickAlbum(message.arg1);
291                        break;
292                    }
293                    default: throw new AssertionError(message.what);
294                }
295            }
296        };
297    }
298
299    private void clearLoadingBit(int loadingBit) {
300        mLoadingBits &= ~loadingBit;
301        if (mLoadingBits == 0 && mIsActive) {
302            // Only show toast when there's no album and we are going to finish
303            // the page. Toast is redundant if we are going to stay on this page.
304            if ((mAlbumSetDataAdapter.size() == 0)) {
305                if (mActivity.getStateManager().getStateCount() > 1) {
306                    Toast.makeText(mActivity,
307                            R.string.empty_album, Toast.LENGTH_LONG).show();
308                    mActivity.getStateManager().finishState(this);
309                }
310            }
311        }
312    }
313
314    private void setLoadingBit(int loadingBit) {
315        mLoadingBits |= loadingBit;
316    }
317
318    @Override
319    public void onPause() {
320        super.onPause();
321        mIsActive = false;
322        mActionModeHandler.pause();
323        mAlbumSetDataAdapter.pause();
324        mAlbumSetView.pause();
325        mEyePosition.pause();
326        DetailsHelper.pause();
327        // Call disableClusterMenu to avoid receiving callback after paused.
328        // Don't hide menu here otherwise the list menu will disappear earlier than
329        // the action bar, which is janky and unwanted behavior.
330        mActionBar.disableClusterMenu(false);
331        if (mSyncTask != null) {
332            mSyncTask.cancel();
333            mSyncTask = null;
334            clearLoadingBit(BIT_LOADING_SYNC);
335        }
336    }
337
338    @Override
339    public void onResume() {
340        super.onResume();
341        mIsActive = true;
342        setContentPane(mRootPane);
343
344        // Set the reload bit here to prevent it exit this page in clearLoadingBit().
345        setLoadingBit(BIT_LOADING_RELOAD);
346        mAlbumSetDataAdapter.resume();
347
348        mAlbumSetView.resume();
349        mEyePosition.resume();
350        mActionModeHandler.resume();
351        if (mShowClusterMenu) {
352            mActionBar.enableClusterMenu(mSelectedAction, this);
353        }
354        if (!mInitialSynced) {
355            setLoadingBit(BIT_LOADING_SYNC);
356            mSyncTask = mMediaSet.requestSync(AlbumSetPage.this);
357        }
358    }
359
360    private void initializeData(Bundle data) {
361        String mediaPath = data.getString(AlbumSetPage.KEY_MEDIA_PATH);
362        mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
363        mSelectionManager.setSourceMediaSet(mMediaSet);
364        mAlbumSetDataAdapter = new AlbumSetDataLoader(
365                mActivity, mMediaSet, DATA_CACHE_SIZE);
366        mAlbumSetDataAdapter.setLoadingListener(new MyLoadingListener());
367        mAlbumSetView.setModel(mAlbumSetDataAdapter);
368    }
369
370    private void initializeViews() {
371        mSelectionManager = new SelectionManager(mActivity, true);
372        mSelectionManager.setSelectionListener(this);
373
374        mConfig = Config.AlbumSetPage.get(mActivity);
375        mSlotView = new SlotView(mActivity, mConfig.slotViewSpec);
376        mAlbumSetView = new AlbumSetSlotRenderer(
377                mActivity, mSelectionManager, mSlotView, mConfig.labelSpec,
378                mConfig.placeholderColor);
379        mSlotView.setSlotRenderer(mAlbumSetView);
380        mSlotView.setListener(new SlotView.SimpleListener() {
381            @Override
382            public void onDown(int index) {
383                AlbumSetPage.this.onDown(index);
384            }
385
386            @Override
387            public void onUp(boolean followedByLongPress) {
388                AlbumSetPage.this.onUp(followedByLongPress);
389            }
390
391            @Override
392            public void onSingleTapUp(int slotIndex) {
393                AlbumSetPage.this.onSingleTapUp(slotIndex);
394            }
395
396            @Override
397            public void onLongTap(int slotIndex) {
398                AlbumSetPage.this.onLongTap(slotIndex);
399            }
400        });
401
402        mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
403        mActionModeHandler.setActionModeListener(new ActionModeListener() {
404            @Override
405            public boolean onActionItemClicked(MenuItem item) {
406                return onItemSelected(item);
407            }
408        });
409        mRootPane.addComponent(mSlotView);
410    }
411
412    @Override
413    protected boolean onCreateActionBar(Menu menu) {
414        Activity activity = mActivity;
415        final boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
416        MenuInflater inflater = getSupportMenuInflater();
417
418        if (mGetContent) {
419            inflater.inflate(R.menu.pickup, menu);
420            int typeBits = mData.getInt(
421                    Gallery.KEY_TYPE_BITS, DataManager.INCLUDE_IMAGE);
422            mActionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
423        } else  if (mGetAlbum) {
424            inflater.inflate(R.menu.pickup, menu);
425            mActionBar.setTitle(R.string.select_album);
426        } else {
427            inflater.inflate(R.menu.albumset, menu);
428            mShowClusterMenu = !inAlbum;
429            boolean selectAlbums = !inAlbum &&
430                    mActionBar.getClusterTypeAction() == FilterUtils.CLUSTER_BY_ALBUM;
431            MenuItem selectItem = menu.findItem(R.id.action_select);
432            selectItem.setTitle(activity.getString(
433                    selectAlbums ? R.string.select_album : R.string.select_group));
434
435            MenuItem cameraItem = menu.findItem(R.id.action_camera);
436            cameraItem.setVisible(GalleryUtils.isCameraAvailable(activity));
437
438            FilterUtils.setupMenuItems(mActionBar, mMediaSet.getPath(), false);
439
440            Intent helpIntent = HelpUtils.getHelpIntent(activity, R.string.help_url_gallery_main);
441
442            MenuItem helpItem = menu.findItem(R.id.action_general_help);
443            helpItem.setVisible(helpIntent != null);
444            if (helpIntent != null) helpItem.setIntent(helpIntent);
445
446            mActionBar.setTitle(mTitle);
447            mActionBar.setSubtitle(mSubtitle);
448        }
449        return true;
450    }
451
452    @Override
453    protected boolean onItemSelected(MenuItem item) {
454        Activity activity = mActivity;
455        switch (item.getItemId()) {
456            case R.id.action_cancel:
457                activity.setResult(Activity.RESULT_CANCELED);
458                activity.finish();
459                return true;
460            case R.id.action_select:
461                mSelectionManager.setAutoLeaveSelectionMode(false);
462                mSelectionManager.enterSelectionMode();
463                return true;
464            case R.id.action_details:
465                if (mAlbumSetDataAdapter.size() != 0) {
466                    if (mShowDetails) {
467                        hideDetails();
468                    } else {
469                        showDetails();
470                    }
471                } else {
472                    Toast.makeText(activity,
473                            activity.getText(R.string.no_albums_alert),
474                            Toast.LENGTH_SHORT).show();
475                }
476                return true;
477            case R.id.action_camera: {
478                GalleryUtils.startCameraActivity(activity);
479                return true;
480            }
481            case R.id.action_manage_offline: {
482                Bundle data = new Bundle();
483                String mediaPath = mActivity.getDataManager().getTopSetPath(
484                    DataManager.INCLUDE_ALL);
485                data.putString(AlbumSetPage.KEY_MEDIA_PATH, mediaPath);
486                mActivity.getStateManager().startState(ManageCachePage.class, data);
487                return true;
488            }
489            case R.id.action_sync_picasa_albums: {
490                PicasaSource.requestSync(activity);
491                return true;
492            }
493            case R.id.action_settings: {
494                activity.startActivity(new Intent(activity, GallerySettings.class));
495                return true;
496            }
497            default:
498                return false;
499        }
500    }
501
502    @Override
503    protected void onStateResult(int requestCode, int resultCode, Intent data) {
504        switch (requestCode) {
505            case REQUEST_DO_ANIMATION: {
506                mSlotView.startRisingAnimation();
507            }
508        }
509    }
510
511    private String getSelectedString() {
512        int count = mSelectionManager.getSelectedCount();
513        int action = mActionBar.getClusterTypeAction();
514        int string = action == FilterUtils.CLUSTER_BY_ALBUM
515                ? R.plurals.number_of_albums_selected
516                : R.plurals.number_of_groups_selected;
517        String format = mActivity.getResources().getQuantityString(string, count);
518        return String.format(format, count);
519    }
520
521    @Override
522    public void onSelectionModeChange(int mode) {
523        switch (mode) {
524            case SelectionManager.ENTER_SELECTION_MODE: {
525                mActionBar.disableClusterMenu(true);
526                mActionModeHandler.startActionMode();
527                if (mHapticsEnabled) mVibrator.vibrate(100);
528                break;
529            }
530            case SelectionManager.LEAVE_SELECTION_MODE: {
531                mActionModeHandler.finishActionMode();
532                if (mShowClusterMenu) {
533                    mActionBar.enableClusterMenu(mSelectedAction, this);
534                }
535                mRootPane.invalidate();
536                break;
537            }
538            case SelectionManager.SELECT_ALL_MODE: {
539                mActionModeHandler.updateSupportedOperation();
540                mRootPane.invalidate();
541                break;
542            }
543        }
544    }
545
546    @Override
547    public void onSelectionChange(Path path, boolean selected) {
548        mActionModeHandler.setTitle(getSelectedString());
549        mActionModeHandler.updateSupportedOperation(path, selected);
550    }
551
552    private void hideDetails() {
553        mShowDetails = false;
554        mDetailsHelper.hide();
555        mAlbumSetView.setHighlightItemPath(null);
556        mSlotView.invalidate();
557    }
558
559    private void showDetails() {
560        mShowDetails = true;
561        if (mDetailsHelper == null) {
562            mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
563            mDetailsHelper.setCloseListener(new CloseListener() {
564                @Override
565                public void onClose() {
566                    hideDetails();
567                }
568            });
569        }
570        mDetailsHelper.show();
571    }
572
573    @Override
574    public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
575        if (resultCode == MediaSet.SYNC_RESULT_ERROR) {
576            Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
577                    + resultCode);
578        }
579        ((Activity) mActivity).runOnUiThread(new Runnable() {
580            @Override
581            public void run() {
582                GLRoot root = mActivity.getGLRoot();
583                root.lockRenderThread();
584                try {
585                    if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
586                        mInitialSynced = true;
587                    }
588                    clearLoadingBit(BIT_LOADING_SYNC);
589                    if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
590                        Log.w(TAG, "failed to load album set");
591                    }
592                } finally {
593                    root.unlockRenderThread();
594                }
595            }
596        });
597    }
598
599    private class MyLoadingListener implements LoadingListener {
600        @Override
601        public void onLoadingStarted() {
602            setLoadingBit(BIT_LOADING_RELOAD);
603        }
604
605        @Override
606        public void onLoadingFinished() {
607            clearLoadingBit(BIT_LOADING_RELOAD);
608        }
609    }
610
611    private class MyDetailsSource implements DetailsHelper.DetailsSource {
612        private int mIndex;
613
614        @Override
615        public int size() {
616            return mAlbumSetDataAdapter.size();
617        }
618
619        @Override
620        public int setIndex() {
621            Path id = mSelectionManager.getSelected(false).get(0);
622            mIndex = mAlbumSetDataAdapter.findSet(id);
623            return mIndex;
624        }
625
626        @Override
627        public MediaDetails getDetails() {
628            MediaObject item = mAlbumSetDataAdapter.getMediaSet(mIndex);
629            if (item != null) {
630                mAlbumSetView.setHighlightItemPath(item.getPath());
631                return item.getDetails();
632            } else {
633                return null;
634            }
635        }
636    }
637}
638