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