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