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