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.ui.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                 | FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | FLAG_SHOW_WHEN_LOCKED);
    117         if (data.getBoolean(KEY_DREAM)) {
    118             // Dream screensaver only keeps screen on for plugged devices.
    119             mFlags |= FLAG_SCREEN_ON_WHEN_PLUGGED;
    120         } else {
    121             // User-initiated slideshow would always keep screen on.
    122             mFlags |= FLAG_SCREEN_ON_ALWAYS;
    123         }
    124 
    125         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
    126             @Override
    127             public void handleMessage(Message message) {
    128                 switch (message.what) {
    129                     case MSG_SHOW_PENDING_BITMAP:
    130                         showPendingBitmap();
    131                         break;
    132                     case MSG_LOAD_NEXT_BITMAP:
    133                         loadNextBitmap();
    134                         break;
    135                     default: throw new AssertionError();
    136                 }
    137             }
    138         };
    139         initializeViews();
    140         initializeData(data);
    141     }
    142 
    143     private void loadNextBitmap() {
    144         mModel.nextSlide(new FutureListener<Slide>() {
    145             @Override
    146             public void onFutureDone(Future<Slide> future) {
    147                 mPendingSlide = future.get();
    148                 mHandler.sendEmptyMessage(MSG_SHOW_PENDING_BITMAP);
    149             }
    150         });
    151     }
    152 
    153     private void showPendingBitmap() {
    154         // mPendingBitmap could be null, if
    155         // 1.) there is no more items
    156         // 2.) mModel is paused
    157         Slide slide = mPendingSlide;
    158         if (slide == null) {
    159             if (mIsActive) {
    160                 mActivity.getStateManager().finishState(SlideshowPage.this);
    161             }
    162             return;
    163         }
    164 
    165         mSlideshowView.next(slide.bitmap, slide.item.getRotation());
    166 
    167         setStateResult(Activity.RESULT_OK, mResultIntent
    168                 .putExtra(KEY_ITEM_PATH, slide.item.getPath().toString())
    169                 .putExtra(KEY_PHOTO_INDEX, slide.index));
    170         mHandler.sendEmptyMessageDelayed(MSG_LOAD_NEXT_BITMAP, SLIDESHOW_DELAY);
    171     }
    172 
    173     @Override
    174     public void onPause() {
    175         super.onPause();
    176         mIsActive = false;
    177         mModel.pause();
    178         mSlideshowView.release();
    179 
    180         mHandler.removeMessages(MSG_LOAD_NEXT_BITMAP);
    181         mHandler.removeMessages(MSG_SHOW_PENDING_BITMAP);
    182     }
    183 
    184     @Override
    185     public void onResume() {
    186         super.onResume();
    187         mIsActive = true;
    188         mModel.resume();
    189 
    190         if (mPendingSlide != null) {
    191             showPendingBitmap();
    192         } else {
    193             loadNextBitmap();
    194         }
    195     }
    196 
    197     private void initializeData(Bundle data) {
    198         boolean random = data.getBoolean(KEY_RANDOM_ORDER, false);
    199 
    200         // We only want to show slideshow for images only, not videos.
    201         String mediaPath = data.getString(KEY_SET_PATH);
    202         mediaPath = FilterUtils.newFilterPath(mediaPath, FilterUtils.FILTER_IMAGE_ONLY);
    203         MediaSet mediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
    204 
    205         if (random) {
    206             boolean repeat = data.getBoolean(KEY_REPEAT);
    207             mModel = new SlideshowDataAdapter(mActivity,
    208                     new ShuffleSource(mediaSet, repeat), 0, null);
    209             setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, 0));
    210         } else {
    211             int index = data.getInt(KEY_PHOTO_INDEX);
    212             String itemPath = data.getString(KEY_ITEM_PATH);
    213             Path path = itemPath != null ? Path.fromString(itemPath) : null;
    214             boolean repeat = data.getBoolean(KEY_REPEAT);
    215             mModel = new SlideshowDataAdapter(mActivity, new SequentialSource(mediaSet, repeat),
    216                     index, path);
    217             setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, index));
    218         }
    219     }
    220 
    221     private void initializeViews() {
    222         mSlideshowView = new SlideshowView();
    223         mRootPane.addComponent(mSlideshowView);
    224         setContentPane(mRootPane);
    225     }
    226 
    227     private static MediaItem findMediaItem(MediaSet mediaSet, int index) {
    228         for (int i = 0, n = mediaSet.getSubMediaSetCount(); i < n; ++i) {
    229             MediaSet subset = mediaSet.getSubMediaSet(i);
    230             int count = subset.getTotalMediaItemCount();
    231             if (index < count) {
    232                 return findMediaItem(subset, index);
    233             }
    234             index -= count;
    235         }
    236         ArrayList<MediaItem> list = mediaSet.getMediaItem(index, 1);
    237         return list.isEmpty() ? null : list.get(0);
    238     }
    239 
    240     private static class ShuffleSource implements SlideshowDataAdapter.SlideshowSource {
    241         private static final int RETRY_COUNT = 5;
    242         private final MediaSet mMediaSet;
    243         private final Random mRandom = new Random();
    244         private int mOrder[] = new int[0];
    245         private final boolean mRepeat;
    246         private long mSourceVersion = MediaSet.INVALID_DATA_VERSION;
    247         private int mLastIndex = -1;
    248 
    249         public ShuffleSource(MediaSet mediaSet, boolean repeat) {
    250             mMediaSet = Utils.checkNotNull(mediaSet);
    251             mRepeat = repeat;
    252         }
    253 
    254         @Override
    255         public int findItemIndex(Path path, int hint) {
    256             return hint;
    257         }
    258 
    259         @Override
    260         public MediaItem getMediaItem(int index) {
    261             if (!mRepeat && index >= mOrder.length) return null;
    262             if (mOrder.length == 0) return null;
    263             mLastIndex = mOrder[index % mOrder.length];
    264             MediaItem item = findMediaItem(mMediaSet, mLastIndex);
    265             for (int i = 0; i < RETRY_COUNT && item == null; ++i) {
    266                 Log.w(TAG, "fail to find image: " + mLastIndex);
    267                 mLastIndex = mRandom.nextInt(mOrder.length);
    268                 item = findMediaItem(mMediaSet, mLastIndex);
    269             }
    270             return item;
    271         }
    272 
    273         @Override
    274         public long reload() {
    275             long version = mMediaSet.reload();
    276             if (version != mSourceVersion) {
    277                 mSourceVersion = version;
    278                 int count = mMediaSet.getTotalMediaItemCount();
    279                 if (count != mOrder.length) generateOrderArray(count);
    280             }
    281             return version;
    282         }
    283 
    284         private void generateOrderArray(int totalCount) {
    285             if (mOrder.length != totalCount) {
    286                 mOrder = new int[totalCount];
    287                 for (int i = 0; i < totalCount; ++i) {
    288                     mOrder[i] = i;
    289                 }
    290             }
    291             for (int i = totalCount - 1; i > 0; --i) {
    292                 Utils.swap(mOrder, i, mRandom.nextInt(i + 1));
    293             }
    294             if (mOrder[0] == mLastIndex && totalCount > 1) {
    295                 Utils.swap(mOrder, 0, mRandom.nextInt(totalCount - 1) + 1);
    296             }
    297         }
    298 
    299         @Override
    300         public void addContentListener(ContentListener listener) {
    301             mMediaSet.addContentListener(listener);
    302         }
    303 
    304         @Override
    305         public void removeContentListener(ContentListener listener) {
    306             mMediaSet.removeContentListener(listener);
    307         }
    308     }
    309 
    310     private static class SequentialSource implements SlideshowDataAdapter.SlideshowSource {
    311         private static final int DATA_SIZE = 32;
    312 
    313         private ArrayList<MediaItem> mData = new ArrayList<MediaItem>();
    314         private int mDataStart = 0;
    315         private long mDataVersion = MediaObject.INVALID_DATA_VERSION;
    316         private final MediaSet mMediaSet;
    317         private final boolean mRepeat;
    318 
    319         public SequentialSource(MediaSet mediaSet, boolean repeat) {
    320             mMediaSet = mediaSet;
    321             mRepeat = repeat;
    322         }
    323 
    324         @Override
    325         public int findItemIndex(Path path, int hint) {
    326             return mMediaSet.getIndexOfItem(path, hint);
    327         }
    328 
    329         @Override
    330         public MediaItem getMediaItem(int index) {
    331             int dataEnd = mDataStart + mData.size();
    332 
    333             if (mRepeat) {
    334                 int count = mMediaSet.getMediaItemCount();
    335                 if (count == 0) return null;
    336                 index = index % count;
    337             }
    338             if (index < mDataStart || index >= dataEnd) {
    339                 mData = mMediaSet.getMediaItem(index, DATA_SIZE);
    340                 mDataStart = index;
    341                 dataEnd = index + mData.size();
    342             }
    343 
    344             return (index < mDataStart || index >= dataEnd) ? null : mData.get(index - mDataStart);
    345         }
    346 
    347         @Override
    348         public long reload() {
    349             long version = mMediaSet.reload();
    350             if (version != mDataVersion) {
    351                 mDataVersion = version;
    352                 mData.clear();
    353             }
    354             return mDataVersion;
    355         }
    356 
    357         @Override
    358         public void addContentListener(ContentListener listener) {
    359             mMediaSet.addContentListener(listener);
    360         }
    361 
    362         @Override
    363         public void removeContentListener(ContentListener listener) {
    364             mMediaSet.removeContentListener(listener);
    365         }
    366     }
    367 }
    368