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