Home | History | Annotate | Download | only in app
      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 
     17 package com.android.gallery3d.app;
     18 
     19 import android.app.ActionBar;
     20 import android.app.ActionBar.OnMenuVisibilityListener;
     21 import android.app.Activity;
     22 import android.content.ActivityNotFoundException;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.net.Uri;
     26 import android.os.Bundle;
     27 import android.os.Handler;
     28 import android.os.Message;
     29 import android.view.Menu;
     30 import android.view.MenuInflater;
     31 import android.view.MenuItem;
     32 import android.view.View;
     33 import android.view.View.MeasureSpec;
     34 import android.view.WindowManager;
     35 import android.widget.ShareActionProvider;
     36 import android.widget.Toast;
     37 
     38 import com.android.gallery3d.R;
     39 import com.android.gallery3d.data.DataManager;
     40 import com.android.gallery3d.data.MediaDetails;
     41 import com.android.gallery3d.data.MediaItem;
     42 import com.android.gallery3d.data.MediaObject;
     43 import com.android.gallery3d.data.MediaSet;
     44 import com.android.gallery3d.data.MtpDevice;
     45 import com.android.gallery3d.data.Path;
     46 import com.android.gallery3d.picasasource.PicasaSource;
     47 import com.android.gallery3d.ui.DetailsHelper;
     48 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
     49 import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
     50 import com.android.gallery3d.ui.FilmStripView;
     51 import com.android.gallery3d.ui.GLCanvas;
     52 import com.android.gallery3d.ui.GLView;
     53 import com.android.gallery3d.ui.ImportCompleteListener;
     54 import com.android.gallery3d.ui.MenuExecutor;
     55 import com.android.gallery3d.ui.PhotoView;
     56 import com.android.gallery3d.ui.PositionRepository;
     57 import com.android.gallery3d.ui.PositionRepository.Position;
     58 import com.android.gallery3d.ui.SelectionManager;
     59 import com.android.gallery3d.ui.SynchronizedHandler;
     60 import com.android.gallery3d.ui.UserInteractionListener;
     61 import com.android.gallery3d.util.GalleryUtils;
     62 
     63 public class PhotoPage extends ActivityState
     64         implements PhotoView.PhotoTapListener, FilmStripView.Listener,
     65         UserInteractionListener {
     66     private static final String TAG = "PhotoPage";
     67 
     68     private static final int MSG_HIDE_BARS = 1;
     69 
     70     private static final int HIDE_BARS_TIMEOUT = 3500;
     71 
     72     private static final int REQUEST_SLIDESHOW = 1;
     73     private static final int REQUEST_CROP = 2;
     74     private static final int REQUEST_CROP_PICASA = 3;
     75 
     76     public static final String KEY_MEDIA_SET_PATH = "media-set-path";
     77     public static final String KEY_MEDIA_ITEM_PATH = "media-item-path";
     78     public static final String KEY_INDEX_HINT = "index-hint";
     79 
     80     private GalleryApp mApplication;
     81     private SelectionManager mSelectionManager;
     82 
     83     private PhotoView mPhotoView;
     84     private PhotoPage.Model mModel;
     85     private FilmStripView mFilmStripView;
     86     private DetailsHelper mDetailsHelper;
     87     private boolean mShowDetails;
     88     private Path mPendingSharePath;
     89 
     90     // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
     91     // E.g., viewing a photo in gmail attachment
     92     private MediaSet mMediaSet;
     93     private Menu mMenu;
     94 
     95     private final Intent mResultIntent = new Intent();
     96     private int mCurrentIndex = 0;
     97     private Handler mHandler;
     98     private boolean mShowBars = true;
     99     private ActionBar mActionBar;
    100     private MyMenuVisibilityListener mMenuVisibilityListener;
    101     private boolean mIsMenuVisible;
    102     private boolean mIsInteracting;
    103     private MediaItem mCurrentPhoto = null;
    104     private MenuExecutor mMenuExecutor;
    105     private boolean mIsActive;
    106     private ShareActionProvider mShareActionProvider;
    107 
    108     public static interface Model extends PhotoView.Model {
    109         public void resume();
    110         public void pause();
    111         public boolean isEmpty();
    112         public MediaItem getCurrentMediaItem();
    113         public int getCurrentIndex();
    114         public void setCurrentPhoto(Path path, int indexHint);
    115     }
    116 
    117     private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
    118         public void onMenuVisibilityChanged(boolean isVisible) {
    119             mIsMenuVisible = isVisible;
    120             refreshHidingMessage();
    121         }
    122     }
    123 
    124     private final GLView mRootPane = new GLView() {
    125 
    126         @Override
    127         protected void renderBackground(GLCanvas view) {
    128             view.clearBuffer();
    129         }
    130 
    131         @Override
    132         protected void onLayout(
    133                 boolean changed, int left, int top, int right, int bottom) {
    134             mPhotoView.layout(0, 0, right - left, bottom - top);
    135             PositionRepository.getInstance(mActivity).setOffset(0, 0);
    136             int filmStripHeight = 0;
    137             if (mFilmStripView != null) {
    138                 mFilmStripView.measure(
    139                         MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
    140                         MeasureSpec.UNSPECIFIED);
    141                 filmStripHeight = mFilmStripView.getMeasuredHeight();
    142                 mFilmStripView.layout(0, bottom - top - filmStripHeight,
    143                         right - left, bottom - top);
    144             }
    145             if (mShowDetails) {
    146                 mDetailsHelper.layout(left, GalleryActionBar.getHeight((Activity) mActivity),
    147                         right, bottom);
    148             }
    149         }
    150     };
    151 
    152     private void initFilmStripView() {
    153         Config.PhotoPage config = Config.PhotoPage.get((Context) mActivity);
    154         mFilmStripView = new FilmStripView(mActivity, mMediaSet,
    155                 config.filmstripTopMargin, config.filmstripMidMargin, config.filmstripBottomMargin,
    156                 config.filmstripContentSize, config.filmstripThumbSize, config.filmstripBarSize,
    157                 config.filmstripGripSize, config.filmstripGripWidth);
    158         mRootPane.addComponent(mFilmStripView);
    159         mFilmStripView.setListener(this);
    160         mFilmStripView.setUserInteractionListener(this);
    161         mFilmStripView.setFocusIndex(mCurrentIndex);
    162         mFilmStripView.setStartIndex(mCurrentIndex);
    163         mRootPane.requestLayout();
    164         if (mIsActive) mFilmStripView.resume();
    165         if (!mShowBars) mFilmStripView.setVisibility(GLView.INVISIBLE);
    166     }
    167 
    168     @Override
    169     public void onCreate(Bundle data, Bundle restoreState) {
    170         mActionBar = ((Activity) mActivity).getActionBar();
    171         mSelectionManager = new SelectionManager(mActivity, false);
    172         mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
    173 
    174         mPhotoView = new PhotoView(mActivity);
    175         mPhotoView.setPhotoTapListener(this);
    176         mRootPane.addComponent(mPhotoView);
    177         mApplication = (GalleryApp)((Activity) mActivity).getApplication();
    178 
    179         String setPathString = data.getString(KEY_MEDIA_SET_PATH);
    180         Path itemPath = Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH));
    181 
    182         if (setPathString != null) {
    183             mMediaSet = mActivity.getDataManager().getMediaSet(setPathString);
    184             mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
    185             mMediaSet = (MediaSet)
    186                     mActivity.getDataManager().getMediaObject(setPathString);
    187             if (mMediaSet == null) {
    188                 Log.w(TAG, "failed to restore " + setPathString);
    189             }
    190             PhotoDataAdapter pda = new PhotoDataAdapter(
    191                     mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex);
    192             mModel = pda;
    193             mPhotoView.setModel(mModel);
    194 
    195             mResultIntent.putExtra(KEY_INDEX_HINT, mCurrentIndex);
    196             setStateResult(Activity.RESULT_OK, mResultIntent);
    197 
    198             pda.setDataListener(new PhotoDataAdapter.DataListener() {
    199 
    200                 @Override
    201                 public void onPhotoChanged(int index, Path item) {
    202                     if (mFilmStripView != null) mFilmStripView.setFocusIndex(index);
    203                     mCurrentIndex = index;
    204                     mResultIntent.putExtra(KEY_INDEX_HINT, index);
    205                     if (item != null) {
    206                         mResultIntent.putExtra(KEY_MEDIA_ITEM_PATH, item.toString());
    207                         MediaItem photo = mModel.getCurrentMediaItem();
    208                         if (photo != null) updateCurrentPhoto(photo);
    209                     } else {
    210                         mResultIntent.removeExtra(KEY_MEDIA_ITEM_PATH);
    211                     }
    212                     setStateResult(Activity.RESULT_OK, mResultIntent);
    213                 }
    214 
    215                 @Override
    216                 public void onLoadingFinished() {
    217                     GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
    218                     if (!mModel.isEmpty()) {
    219                         MediaItem photo = mModel.getCurrentMediaItem();
    220                         if (photo != null) updateCurrentPhoto(photo);
    221                     } else if (mIsActive) {
    222                         mActivity.getStateManager().finishState(PhotoPage.this);
    223                     }
    224                 }
    225 
    226                 @Override
    227                 public void onLoadingStarted() {
    228                     GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
    229                 }
    230 
    231                 @Override
    232                 public void onPhotoAvailable(long version, boolean fullImage) {
    233                     if (mFilmStripView == null) initFilmStripView();
    234                 }
    235             });
    236         } else {
    237             // Get default media set by the URI
    238             MediaItem mediaItem = (MediaItem)
    239                     mActivity.getDataManager().getMediaObject(itemPath);
    240             mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
    241             mPhotoView.setModel(mModel);
    242             updateCurrentPhoto(mediaItem);
    243         }
    244 
    245         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
    246             @Override
    247             public void handleMessage(Message message) {
    248                 switch (message.what) {
    249                     case MSG_HIDE_BARS: {
    250                         hideBars();
    251                         break;
    252                     }
    253                     default: throw new AssertionError(message.what);
    254                 }
    255             }
    256         };
    257 
    258         // start the opening animation
    259         mPhotoView.setOpenedItem(itemPath);
    260     }
    261 
    262     private void updateShareURI(Path path) {
    263         if (mShareActionProvider != null) {
    264             DataManager manager = mActivity.getDataManager();
    265             int type = manager.getMediaType(path);
    266             Intent intent = new Intent(Intent.ACTION_SEND);
    267             intent.setType(MenuExecutor.getMimeType(type));
    268             intent.putExtra(Intent.EXTRA_STREAM, manager.getContentUri(path));
    269             mShareActionProvider.setShareIntent(intent);
    270             mPendingSharePath = null;
    271         } else {
    272             // This happens when ActionBar is not created yet.
    273             mPendingSharePath = path;
    274         }
    275     }
    276 
    277     private void setTitle(String title) {
    278         if (title == null) return;
    279         boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean(
    280                 R.bool.show_action_bar_title);
    281         if (showTitle)
    282             mActionBar.setTitle(title);
    283         else
    284             mActionBar.setTitle("");
    285     }
    286 
    287     private void updateCurrentPhoto(MediaItem photo) {
    288         if (mCurrentPhoto == photo) return;
    289         mCurrentPhoto = photo;
    290         if (mCurrentPhoto == null) return;
    291         updateMenuOperations();
    292         if (mShowDetails) {
    293             mDetailsHelper.reloadDetails(mModel.getCurrentIndex());
    294         }
    295         setTitle(photo.getName());
    296         mPhotoView.showVideoPlayIcon(
    297                 photo.getMediaType() == MediaObject.MEDIA_TYPE_VIDEO);
    298 
    299         updateShareURI(photo.getPath());
    300     }
    301 
    302     private void updateMenuOperations() {
    303         if (mMenu == null) return;
    304         MenuItem item = mMenu.findItem(R.id.action_slideshow);
    305         if (item != null) {
    306             item.setVisible(canDoSlideShow());
    307         }
    308         if (mCurrentPhoto == null) return;
    309         int supportedOperations = mCurrentPhoto.getSupportedOperations();
    310         if (!GalleryUtils.isEditorAvailable((Context) mActivity, "image/*")) {
    311             supportedOperations &= ~MediaObject.SUPPORT_EDIT;
    312         }
    313 
    314         MenuExecutor.updateMenuOperation(mMenu, supportedOperations);
    315     }
    316 
    317     private boolean canDoSlideShow() {
    318         if (mMediaSet == null || mCurrentPhoto == null) {
    319             return false;
    320         }
    321         if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) {
    322             return false;
    323         }
    324         if (mMediaSet instanceof MtpDevice) {
    325             return false;
    326         }
    327         return true;
    328     }
    329 
    330     private void showBars() {
    331         if (mShowBars) return;
    332         mShowBars = true;
    333         mActionBar.show();
    334         WindowManager.LayoutParams params = ((Activity) mActivity).getWindow().getAttributes();
    335         params.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
    336         ((Activity) mActivity).getWindow().setAttributes(params);
    337         if (mFilmStripView != null) {
    338             mFilmStripView.show();
    339         }
    340     }
    341 
    342     private void hideBars() {
    343         if (!mShowBars) return;
    344         mShowBars = false;
    345         mActionBar.hide();
    346         WindowManager.LayoutParams params = ((Activity) mActivity).getWindow().getAttributes();
    347         params.systemUiVisibility = View. SYSTEM_UI_FLAG_LOW_PROFILE;
    348         ((Activity) mActivity).getWindow().setAttributes(params);
    349         if (mFilmStripView != null) {
    350             mFilmStripView.hide();
    351         }
    352     }
    353 
    354     private void refreshHidingMessage() {
    355         mHandler.removeMessages(MSG_HIDE_BARS);
    356         if (!mIsMenuVisible && !mIsInteracting) {
    357             mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
    358         }
    359     }
    360 
    361     @Override
    362     public void onUserInteraction() {
    363         showBars();
    364         refreshHidingMessage();
    365     }
    366 
    367     public void onUserInteractionTap() {
    368         if (mShowBars) {
    369             hideBars();
    370             mHandler.removeMessages(MSG_HIDE_BARS);
    371         } else {
    372             showBars();
    373             refreshHidingMessage();
    374         }
    375     }
    376 
    377     @Override
    378     public void onUserInteractionBegin() {
    379         showBars();
    380         mIsInteracting = true;
    381         refreshHidingMessage();
    382     }
    383 
    384     @Override
    385     public void onUserInteractionEnd() {
    386         mIsInteracting = false;
    387 
    388         // This function could be called from GL thread (in SlotView.render)
    389         // and post to the main thread. So, it could be executed while the
    390         // activity is paused.
    391         if (mIsActive) refreshHidingMessage();
    392     }
    393 
    394     @Override
    395     protected void onBackPressed() {
    396         if (mShowDetails) {
    397             hideDetails();
    398         } else {
    399             PositionRepository repository = PositionRepository.getInstance(mActivity);
    400             repository.clear();
    401             if (mCurrentPhoto != null) {
    402                 Position position = new Position();
    403                 position.x = mRootPane.getWidth() / 2;
    404                 position.y = mRootPane.getHeight() / 2;
    405                 position.z = -1000;
    406                 repository.putPosition(
    407                         Long.valueOf(System.identityHashCode(mCurrentPhoto.getPath())),
    408                         position);
    409             }
    410             super.onBackPressed();
    411         }
    412     }
    413 
    414     @Override
    415     protected boolean onCreateActionBar(Menu menu) {
    416         MenuInflater inflater = ((Activity) mActivity).getMenuInflater();
    417         inflater.inflate(R.menu.photo, menu);
    418         mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu);
    419         if (mPendingSharePath != null) updateShareURI(mPendingSharePath);
    420         mMenu = menu;
    421         mShowBars = true;
    422         updateMenuOperations();
    423         return true;
    424     }
    425 
    426     @Override
    427     protected boolean onItemSelected(MenuItem item) {
    428         MediaItem current = mModel.getCurrentMediaItem();
    429 
    430         if (current == null) {
    431             // item is not ready, ignore
    432             return true;
    433         }
    434 
    435         int currentIndex = mModel.getCurrentIndex();
    436         Path path = current.getPath();
    437 
    438         DataManager manager = mActivity.getDataManager();
    439         int action = item.getItemId();
    440         switch (action) {
    441             case R.id.action_slideshow: {
    442                 Bundle data = new Bundle();
    443                 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
    444                 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString());
    445                 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
    446                 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
    447                 mActivity.getStateManager().startStateForResult(
    448                         SlideshowPage.class, REQUEST_SLIDESHOW, data);
    449                 return true;
    450             }
    451             case R.id.action_crop: {
    452                 Activity activity = (Activity) mActivity;
    453                 Intent intent = new Intent(CropImage.CROP_ACTION);
    454                 intent.setClass(activity, CropImage.class);
    455                 intent.setData(manager.getContentUri(path));
    456                 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
    457                         ? REQUEST_CROP_PICASA
    458                         : REQUEST_CROP);
    459                 return true;
    460             }
    461             case R.id.action_details: {
    462                 if (mShowDetails) {
    463                     hideDetails();
    464                 } else {
    465                     showDetails(currentIndex);
    466                 }
    467                 return true;
    468             }
    469             case R.id.action_setas:
    470             case R.id.action_confirm_delete:
    471             case R.id.action_rotate_ccw:
    472             case R.id.action_rotate_cw:
    473             case R.id.action_show_on_map:
    474             case R.id.action_edit:
    475                 mSelectionManager.deSelectAll();
    476                 mSelectionManager.toggle(path);
    477                 mMenuExecutor.onMenuClicked(item, null);
    478                 return true;
    479             case R.id.action_import:
    480                 mSelectionManager.deSelectAll();
    481                 mSelectionManager.toggle(path);
    482                 mMenuExecutor.onMenuClicked(item,
    483                         new ImportCompleteListener(mActivity));
    484                 return true;
    485             default :
    486                 return false;
    487         }
    488     }
    489 
    490     private void hideDetails() {
    491         mShowDetails = false;
    492         mDetailsHelper.hide();
    493     }
    494 
    495     private void showDetails(int index) {
    496         mShowDetails = true;
    497         if (mDetailsHelper == null) {
    498             mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
    499             mDetailsHelper.setCloseListener(new CloseListener() {
    500                 public void onClose() {
    501                     hideDetails();
    502                 }
    503             });
    504         }
    505         mDetailsHelper.reloadDetails(index);
    506         mDetailsHelper.show();
    507     }
    508 
    509     public void onSingleTapUp(int x, int y) {
    510         MediaItem item = mModel.getCurrentMediaItem();
    511         if (item == null) {
    512             // item is not ready, ignore
    513             return;
    514         }
    515 
    516         boolean playVideo =
    517                 (item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0;
    518 
    519         if (playVideo) {
    520             // determine if the point is at center (1/6) of the photo view.
    521             // (The position of the "play" icon is at center (1/6) of the photo)
    522             int w = mPhotoView.getWidth();
    523             int h = mPhotoView.getHeight();
    524             playVideo = (Math.abs(x - w / 2) * 12 <= w)
    525                 && (Math.abs(y - h / 2) * 12 <= h);
    526         }
    527 
    528         if (playVideo) {
    529             playVideo((Activity) mActivity, item.getPlayUri(), item.getName());
    530         } else {
    531             onUserInteractionTap();
    532         }
    533     }
    534 
    535     public static void playVideo(Activity activity, Uri uri, String title) {
    536         try {
    537             Intent intent = new Intent(Intent.ACTION_VIEW)
    538                     .setDataAndType(uri, "video/*");
    539             intent.putExtra(Intent.EXTRA_TITLE, title);
    540             activity.startActivity(intent);
    541         } catch (ActivityNotFoundException e) {
    542             Toast.makeText(activity, activity.getString(R.string.video_err),
    543                     Toast.LENGTH_SHORT).show();
    544         }
    545     }
    546 
    547     // Called by FileStripView.
    548     // Returns false if it cannot jump to the specified index at this time.
    549     public boolean onSlotSelected(int slotIndex) {
    550         return mPhotoView.jumpTo(slotIndex);
    551     }
    552 
    553     @Override
    554     protected void onStateResult(int requestCode, int resultCode, Intent data) {
    555         switch (requestCode) {
    556             case REQUEST_CROP:
    557                 if (resultCode == Activity.RESULT_OK) {
    558                     if (data == null) break;
    559                     Path path = mApplication
    560                             .getDataManager().findPathByUri(data.getData());
    561                     if (path != null) {
    562                         mModel.setCurrentPhoto(path, mCurrentIndex);
    563                     }
    564                 }
    565                 break;
    566             case REQUEST_CROP_PICASA: {
    567                 int message = resultCode == Activity.RESULT_OK
    568                         ? R.string.crop_saved
    569                         : R.string.crop_not_saved;
    570                 Toast.makeText(mActivity.getAndroidContext(),
    571                         message, Toast.LENGTH_SHORT).show();
    572                 break;
    573             }
    574             case REQUEST_SLIDESHOW: {
    575                 if (data == null) break;
    576                 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
    577                 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
    578                 if (path != null) {
    579                     mModel.setCurrentPhoto(Path.fromString(path), index);
    580                 }
    581             }
    582         }
    583     }
    584 
    585     @Override
    586     public void onPause() {
    587         super.onPause();
    588         mIsActive = false;
    589         if (mFilmStripView != null) {
    590             mFilmStripView.pause();
    591         }
    592         DetailsHelper.pause();
    593         mPhotoView.pause();
    594         mModel.pause();
    595         mHandler.removeMessages(MSG_HIDE_BARS);
    596         mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
    597         mMenuExecutor.pause();
    598     }
    599 
    600     @Override
    601     protected void onResume() {
    602         super.onResume();
    603         mIsActive = true;
    604         setContentPane(mRootPane);
    605         mModel.resume();
    606         mPhotoView.resume();
    607         if (mFilmStripView != null) {
    608             mFilmStripView.resume();
    609         }
    610         if (mMenuVisibilityListener == null) {
    611             mMenuVisibilityListener = new MyMenuVisibilityListener();
    612         }
    613         mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
    614         onUserInteraction();
    615     }
    616 
    617     private class MyDetailsSource implements DetailsSource {
    618         private int mIndex;
    619 
    620         @Override
    621         public MediaDetails getDetails() {
    622             return mModel.getCurrentMediaItem().getDetails();
    623         }
    624 
    625         @Override
    626         public int size() {
    627             return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
    628         }
    629 
    630         @Override
    631         public int findIndex(int indexHint) {
    632             mIndex = indexHint;
    633             return indexHint;
    634         }
    635 
    636         @Override
    637         public int getIndex() {
    638             return mIndex;
    639         }
    640     }
    641 }
    642