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.Activity;
     20 import android.content.Intent;
     21 import android.graphics.Bitmap;
     22 import android.os.Bundle;
     23 import android.os.Handler;
     24 import android.os.Message;
     25 import android.view.MotionEvent;
     26 
     27 import com.android.gallery3d.R;
     28 import com.android.gallery3d.common.Utils;
     29 import com.android.gallery3d.data.ContentListener;
     30 import com.android.gallery3d.data.MediaItem;
     31 import com.android.gallery3d.data.MediaObject;
     32 import com.android.gallery3d.data.MediaSet;
     33 import com.android.gallery3d.data.Path;
     34 import com.android.gallery3d.glrenderer.GLCanvas;
     35 import com.android.gallery3d.ui.GLView;
     36 import com.android.gallery3d.ui.SlideshowView;
     37 import com.android.gallery3d.ui.SynchronizedHandler;
     38 import com.android.gallery3d.util.Future;
     39 import com.android.gallery3d.util.FutureListener;
     40 
     41 import java.util.ArrayList;
     42 import java.util.Random;
     43 
     44 public class SlideshowPage extends ActivityState {
     45     private static final String TAG = "SlideshowPage";
     46 
     47     public static final String KEY_SET_PATH = "media-set-path";
     48     public static final String KEY_ITEM_PATH = "media-item-path";
     49     public static final String KEY_PHOTO_INDEX = "photo-index";
     50     public static final String KEY_RANDOM_ORDER = "random-order";
     51     public static final String KEY_REPEAT = "repeat";
     52     public static final String KEY_DREAM = "dream";
     53 
     54     private static final long SLIDESHOW_DELAY = 3000; // 3 seconds
     55 
     56     private static final int MSG_LOAD_NEXT_BITMAP = 1;
     57     private static final int MSG_SHOW_PENDING_BITMAP = 2;
     58 
     59     public static interface Model {
     60         public void pause();
     61 
     62         public void resume();
     63 
     64         public Future<Slide> nextSlide(FutureListener<Slide> listener);
     65     }
     66 
     67     public static class Slide {
     68         public Bitmap bitmap;
     69         public MediaItem item;
     70         public int index;
     71 
     72         public Slide(MediaItem item, int index, Bitmap bitmap) {
     73             this.bitmap = bitmap;
     74             this.item = item;
     75             this.index = index;
     76         }
     77     }
     78 
     79     private Handler mHandler;
     80     private Model mModel;
     81     private SlideshowView mSlideshowView;
     82 
     83     private Slide mPendingSlide = null;
     84     private boolean mIsActive = false;
     85     private final Intent mResultIntent = new Intent();
     86 
     87     @Override
     88     protected int getBackgroundColorId() {
     89         return R.color.slideshow_background;
     90     }
     91 
     92     private final GLView mRootPane = new GLView() {
     93         @Override
     94         protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
     95             mSlideshowView.layout(0, 0, right - left, bottom - top);
     96         }
     97 
     98         @Override
     99         protected boolean onTouch(MotionEvent event) {
    100             if (event.getAction() == MotionEvent.ACTION_UP) {
    101                 onBackPressed();
    102             }
    103             return true;
    104         }
    105 
    106         @Override
    107         protected void renderBackground(GLCanvas canvas) {
    108             canvas.clearBuffer(getBackgroundColor());
    109         }
    110     };
    111 
    112     @Override
    113     public void onCreate(Bundle data, Bundle restoreState) {
    114         super.onCreate(data, restoreState);
    115         mFlags |= (FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR);
    116         if (data.getBoolean(KEY_DREAM)) {
    117             // Dream screensaver only keeps screen on for plugged devices.
    118             mFlags |= FLAG_SCREEN_ON_WHEN_PLUGGED | FLAG_SHOW_WHEN_LOCKED;
    119         } else {
    120             // User-initiated slideshow would always keep screen on.
    121             mFlags |= FLAG_SCREEN_ON_ALWAYS;
    122         }
    123 
    124         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
    125             @Override
    126             public void handleMessage(Message message) {
    127                 switch (message.what) {
    128                     case MSG_SHOW_PENDING_BITMAP:
    129                         showPendingBitmap();
    130                         break;
    131                     case MSG_LOAD_NEXT_BITMAP:
    132                         loadNextBitmap();
    133                         break;
    134                     default: throw new AssertionError();
    135                 }
    136             }
    137         };
    138         initializeViews();
    139         initializeData(data);
    140     }
    141 
    142     private void loadNextBitmap() {
    143         mModel.nextSlide(new FutureListener<Slide>() {
    144             @Override
    145             public void onFutureDone(Future<Slide> future) {
    146                 mPendingSlide = future.get();
    147                 mHandler.sendEmptyMessage(MSG_SHOW_PENDING_BITMAP);
    148             }
    149         });
    150     }
    151 
    152     private void showPendingBitmap() {
    153         // mPendingBitmap could be null, if
    154         // 1.) there is no more items
    155         // 2.) mModel is paused
    156         Slide slide = mPendingSlide;
    157         if (slide == null) {
    158             if (mIsActive) {
    159                 mActivity.getStateManager().finishState(SlideshowPage.this);
    160             }
    161             return;
    162         }
    163 
    164         mSlideshowView.next(slide.bitmap, slide.item.getRotation());
    165 
    166         setStateResult(Activity.RESULT_OK, mResultIntent
    167                 .putExtra(KEY_ITEM_PATH, slide.item.getPath().toString())
    168                 .putExtra(KEY_PHOTO_INDEX, slide.index));
    169         mHandler.sendEmptyMessageDelayed(MSG_LOAD_NEXT_BITMAP, SLIDESHOW_DELAY);
    170     }
    171 
    172     @Override
    173     public void onPause() {
    174         super.onPause();
    175         mIsActive = false;
    176         mModel.pause();
    177         mSlideshowView.release();
    178 
    179         mHandler.removeMessages(MSG_LOAD_NEXT_BITMAP);
    180         mHandler.removeMessages(MSG_SHOW_PENDING_BITMAP);
    181     }
    182 
    183     @Override
    184     public void onResume() {
    185         super.onResume();
    186         mIsActive = true;
    187         mModel.resume();
    188 
    189         if (mPendingSlide != null) {
    190             showPendingBitmap();
    191         } else {
    192             loadNextBitmap();
    193         }
    194     }
    195 
    196     private void initializeData(Bundle data) {
    197         boolean random = data.getBoolean(KEY_RANDOM_ORDER, false);
    198 
    199         // We only want to show slideshow for images only, not videos.
    200         String mediaPath = data.getString(KEY_SET_PATH);
    201         mediaPath = FilterUtils.newFilterPath(mediaPath, FilterUtils.FILTER_IMAGE_ONLY);
    202         MediaSet mediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
    203 
    204         if (random) {
    205             boolean repeat = data.getBoolean(KEY_REPEAT);
    206             mModel = new SlideshowDataAdapter(mActivity,
    207                     new ShuffleSource(mediaSet, repeat), 0, null);
    208             setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, 0));
    209         } else {
    210             int index = data.getInt(KEY_PHOTO_INDEX);
    211             String itemPath = data.getString(KEY_ITEM_PATH);
    212             Path path = itemPath != null ? Path.fromString(itemPath) : null;
    213             boolean repeat = data.getBoolean(KEY_REPEAT);
    214             mModel = new SlideshowDataAdapter(mActivity, new SequentialSource(mediaSet, repeat),
    215                     index, path);
    216             setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, index));
    217         }
    218     }
    219 
    220     private void initializeViews() {
    221         mSlideshowView = new SlideshowView();
    222         mRootPane.addComponent(mSlideshowView);
    223         setContentPane(mRootPane);
    224     }
    225 
    226     private static MediaItem findMediaItem(MediaSet mediaSet, int index) {
    227         for (int i = 0, n = mediaSet.getSubMediaSetCount(); i < n; ++i) {
    228             MediaSet subset = mediaSet.getSubMediaSet(i);
    229             int count = subset.getTotalMediaItemCount();
    230             if (index < count) {
    231                 return findMediaItem(subset, index);
    232             }
    233             index -= count;
    234         }
    235         ArrayList<MediaItem> list = mediaSet.getMediaItem(index, 1);
    236         return list.isEmpty() ? null : list.get(0);
    237     }
    238 
    239     private static class ShuffleSource implements SlideshowDataAdapter.SlideshowSource {
    240         private static final int RETRY_COUNT = 5;
    241         private final MediaSet mMediaSet;
    242         private final Random mRandom = new Random();
    243         private int mOrder[] = new int[0];
    244         private final boolean mRepeat;
    245         private long mSourceVersion = MediaSet.INVALID_DATA_VERSION;
    246         private int mLastIndex = -1;
    247 
    248         public ShuffleSource(MediaSet mediaSet, boolean repeat) {
    249             mMediaSet = Utils.checkNotNull(mediaSet);
    250             mRepeat = repeat;
    251         }
    252 
    253         @Override
    254         public int findItemIndex(Path path, int hint) {
    255             return hint;
    256         }
    257 
    258         @Override
    259         public MediaItem getMediaItem(int index) {
    260             if (!mRepeat && index >= mOrder.length) return null;
    261             if (mOrder.length == 0) return null;
    262             mLastIndex = mOrder[index % mOrder.length];
    263             MediaItem item = findMediaItem(mMediaSet, mLastIndex);
    264             for (int i = 0; i < RETRY_COUNT && item == null; ++i) {
    265                 Log.w(TAG, "fail to find image: " + mLastIndex);
    266                 mLastIndex = mRandom.nextInt(mOrder.length);
    267                 item = findMediaItem(mMediaSet, mLastIndex);
    268             }
    269             return item;
    270         }
    271 
    272         @Override
    273         public long reload() {
    274             long version = mMediaSet.reload();
    275             if (version != mSourceVersion) {
    276                 mSourceVersion = version;
    277                 int count = mMediaSet.getTotalMediaItemCount();
    278                 if (count != mOrder.length) generateOrderArray(count);
    279             }
    280             return version;
    281         }
    282 
    283         private void generateOrderArray(int totalCount) {
    284             if (mOrder.length != totalCount) {
    285                 mOrder = new int[totalCount];
    286                 for (int i = 0; i < totalCount; ++i) {
    287                     mOrder[i] = i;
    288                 }
    289             }
    290             for (int i = totalCount - 1; i > 0; --i) {
    291                 Utils.swap(mOrder, i, mRandom.nextInt(i + 1));
    292             }
    293             if (mOrder[0] == mLastIndex && totalCount > 1) {
    294                 Utils.swap(mOrder, 0, mRandom.nextInt(totalCount - 1) + 1);
    295             }
    296         }
    297 
    298         @Override
    299         public void addContentListener(ContentListener listener) {
    300             mMediaSet.addContentListener(listener);
    301         }
    302 
    303         @Override
    304         public void removeContentListener(ContentListener listener) {
    305             mMediaSet.removeContentListener(listener);
    306         }
    307     }
    308 
    309     private static class SequentialSource implements SlideshowDataAdapter.SlideshowSource {
    310         private static final int DATA_SIZE = 32;
    311 
    312         private ArrayList<MediaItem> mData = new ArrayList<MediaItem>();
    313         private int mDataStart = 0;
    314         private long mDataVersion = MediaObject.INVALID_DATA_VERSION;
    315         private final MediaSet mMediaSet;
    316         private final boolean mRepeat;
    317 
    318         public SequentialSource(MediaSet mediaSet, boolean repeat) {
    319             mMediaSet = mediaSet;
    320             mRepeat = repeat;
    321         }
    322 
    323         @Override
    324         public int findItemIndex(Path path, int hint) {
    325             return mMediaSet.getIndexOfItem(path, hint);
    326         }
    327 
    328         @Override
    329         public MediaItem getMediaItem(int index) {
    330             int dataEnd = mDataStart + mData.size();
    331 
    332             if (mRepeat) {
    333                 int count = mMediaSet.getMediaItemCount();
    334                 if (count == 0) return null;
    335                 index = index % count;
    336             }
    337             if (index < mDataStart || index >= dataEnd) {
    338                 mData = mMediaSet.getMediaItem(index, DATA_SIZE);
    339                 mDataStart = index;
    340                 dataEnd = index + mData.size();
    341             }
    342 
    343             return (index < mDataStart || index >= dataEnd) ? null : mData.get(index - mDataStart);
    344         }
    345 
    346         @Override
    347         public long reload() {
    348             long version = mMediaSet.reload();
    349             if (version != mDataVersion) {
    350                 mDataVersion = version;
    351                 mData.clear();
    352             }
    353             return mDataVersion;
    354         }
    355 
    356         @Override
    357         public void addContentListener(ContentListener listener) {
    358             mMediaSet.addContentListener(listener);
    359         }
    360 
    361         @Override
    362         public void removeContentListener(ContentListener listener) {
    363             mMediaSet.removeContentListener(listener);
    364         }
    365     }
    366 }
    367