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