| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.gallery3d.app; |
| |
| import android.app.Activity; |
| import android.content.Intent; |
| import android.graphics.Bitmap; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.view.MotionEvent; |
| |
| import com.android.gallery3d.R; |
| import com.android.gallery3d.common.Utils; |
| import com.android.gallery3d.data.ContentListener; |
| import com.android.gallery3d.data.MediaItem; |
| import com.android.gallery3d.data.MediaObject; |
| import com.android.gallery3d.data.MediaSet; |
| import com.android.gallery3d.data.Path; |
| import com.android.gallery3d.glrenderer.GLCanvas; |
| import com.android.gallery3d.ui.GLView; |
| import com.android.gallery3d.ui.SlideshowView; |
| import com.android.gallery3d.ui.SynchronizedHandler; |
| import com.android.gallery3d.util.Future; |
| import com.android.gallery3d.util.FutureListener; |
| |
| import java.util.ArrayList; |
| import java.util.Random; |
| |
| public class SlideshowPage extends ActivityState { |
| private static final String TAG = "SlideshowPage"; |
| |
| public static final String KEY_SET_PATH = "media-set-path"; |
| public static final String KEY_ITEM_PATH = "media-item-path"; |
| public static final String KEY_PHOTO_INDEX = "photo-index"; |
| public static final String KEY_RANDOM_ORDER = "random-order"; |
| public static final String KEY_REPEAT = "repeat"; |
| public static final String KEY_DREAM = "dream"; |
| |
| private static final long SLIDESHOW_DELAY = 3000; // 3 seconds |
| |
| private static final int MSG_LOAD_NEXT_BITMAP = 1; |
| private static final int MSG_SHOW_PENDING_BITMAP = 2; |
| |
| public static interface Model { |
| public void pause(); |
| |
| public void resume(); |
| |
| public Future<Slide> nextSlide(FutureListener<Slide> listener); |
| } |
| |
| public static class Slide { |
| public Bitmap bitmap; |
| public MediaItem item; |
| public int index; |
| |
| public Slide(MediaItem item, int index, Bitmap bitmap) { |
| this.bitmap = bitmap; |
| this.item = item; |
| this.index = index; |
| } |
| } |
| |
| private Handler mHandler; |
| private Model mModel; |
| private SlideshowView mSlideshowView; |
| |
| private Slide mPendingSlide = null; |
| private boolean mIsActive = false; |
| private final Intent mResultIntent = new Intent(); |
| |
| @Override |
| protected int getBackgroundColorId() { |
| return R.color.slideshow_background; |
| } |
| |
| private final GLView mRootPane = new GLView() { |
| @Override |
| protected void onLayout(boolean changed, int left, int top, int right, int bottom) { |
| mSlideshowView.layout(0, 0, right - left, bottom - top); |
| } |
| |
| @Override |
| protected boolean onTouch(MotionEvent event) { |
| if (event.getAction() == MotionEvent.ACTION_UP) { |
| onBackPressed(); |
| } |
| return true; |
| } |
| |
| @Override |
| protected void renderBackground(GLCanvas canvas) { |
| canvas.clearBuffer(getBackgroundColor()); |
| } |
| }; |
| |
| @Override |
| public void onCreate(Bundle data, Bundle restoreState) { |
| super.onCreate(data, restoreState); |
| mFlags |= (FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR |
| | FLAG_ALLOW_LOCK_WHILE_SCREEN_ON); |
| if (data.getBoolean(KEY_DREAM)) { |
| // Dream screensaver only keeps screen on for plugged devices. |
| mFlags |= FLAG_SCREEN_ON_WHEN_PLUGGED | FLAG_SHOW_WHEN_LOCKED; |
| } else { |
| // User-initiated slideshow would always keep screen on. |
| mFlags |= FLAG_SCREEN_ON_ALWAYS; |
| } |
| |
| mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { |
| @Override |
| public void handleMessage(Message message) { |
| switch (message.what) { |
| case MSG_SHOW_PENDING_BITMAP: |
| showPendingBitmap(); |
| break; |
| case MSG_LOAD_NEXT_BITMAP: |
| loadNextBitmap(); |
| break; |
| default: throw new AssertionError(); |
| } |
| } |
| }; |
| initializeViews(); |
| initializeData(data); |
| } |
| |
| private void loadNextBitmap() { |
| mModel.nextSlide(new FutureListener<Slide>() { |
| @Override |
| public void onFutureDone(Future<Slide> future) { |
| mPendingSlide = future.get(); |
| mHandler.sendEmptyMessage(MSG_SHOW_PENDING_BITMAP); |
| } |
| }); |
| } |
| |
| private void showPendingBitmap() { |
| // mPendingBitmap could be null, if |
| // 1.) there is no more items |
| // 2.) mModel is paused |
| Slide slide = mPendingSlide; |
| if (slide == null) { |
| if (mIsActive) { |
| mActivity.getStateManager().finishState(SlideshowPage.this); |
| } |
| return; |
| } |
| |
| mSlideshowView.next(slide.bitmap, slide.item.getRotation()); |
| |
| setStateResult(Activity.RESULT_OK, mResultIntent |
| .putExtra(KEY_ITEM_PATH, slide.item.getPath().toString()) |
| .putExtra(KEY_PHOTO_INDEX, slide.index)); |
| mHandler.sendEmptyMessageDelayed(MSG_LOAD_NEXT_BITMAP, SLIDESHOW_DELAY); |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| mIsActive = false; |
| mModel.pause(); |
| mSlideshowView.release(); |
| |
| mHandler.removeMessages(MSG_LOAD_NEXT_BITMAP); |
| mHandler.removeMessages(MSG_SHOW_PENDING_BITMAP); |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| mIsActive = true; |
| mModel.resume(); |
| |
| if (mPendingSlide != null) { |
| showPendingBitmap(); |
| } else { |
| loadNextBitmap(); |
| } |
| } |
| |
| private void initializeData(Bundle data) { |
| boolean random = data.getBoolean(KEY_RANDOM_ORDER, false); |
| |
| // We only want to show slideshow for images only, not videos. |
| String mediaPath = data.getString(KEY_SET_PATH); |
| mediaPath = FilterUtils.newFilterPath(mediaPath, FilterUtils.FILTER_IMAGE_ONLY); |
| MediaSet mediaSet = mActivity.getDataManager().getMediaSet(mediaPath); |
| |
| if (random) { |
| boolean repeat = data.getBoolean(KEY_REPEAT); |
| mModel = new SlideshowDataAdapter(mActivity, |
| new ShuffleSource(mediaSet, repeat), 0, null); |
| setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, 0)); |
| } else { |
| int index = data.getInt(KEY_PHOTO_INDEX); |
| String itemPath = data.getString(KEY_ITEM_PATH); |
| Path path = itemPath != null ? Path.fromString(itemPath) : null; |
| boolean repeat = data.getBoolean(KEY_REPEAT); |
| mModel = new SlideshowDataAdapter(mActivity, new SequentialSource(mediaSet, repeat), |
| index, path); |
| setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, index)); |
| } |
| } |
| |
| private void initializeViews() { |
| mSlideshowView = new SlideshowView(); |
| mRootPane.addComponent(mSlideshowView); |
| setContentPane(mRootPane); |
| } |
| |
| private static MediaItem findMediaItem(MediaSet mediaSet, int index) { |
| for (int i = 0, n = mediaSet.getSubMediaSetCount(); i < n; ++i) { |
| MediaSet subset = mediaSet.getSubMediaSet(i); |
| int count = subset.getTotalMediaItemCount(); |
| if (index < count) { |
| return findMediaItem(subset, index); |
| } |
| index -= count; |
| } |
| ArrayList<MediaItem> list = mediaSet.getMediaItem(index, 1); |
| return list.isEmpty() ? null : list.get(0); |
| } |
| |
| private static class ShuffleSource implements SlideshowDataAdapter.SlideshowSource { |
| private static final int RETRY_COUNT = 5; |
| private final MediaSet mMediaSet; |
| private final Random mRandom = new Random(); |
| private int mOrder[] = new int[0]; |
| private final boolean mRepeat; |
| private long mSourceVersion = MediaSet.INVALID_DATA_VERSION; |
| private int mLastIndex = -1; |
| |
| public ShuffleSource(MediaSet mediaSet, boolean repeat) { |
| mMediaSet = Utils.checkNotNull(mediaSet); |
| mRepeat = repeat; |
| } |
| |
| @Override |
| public int findItemIndex(Path path, int hint) { |
| return hint; |
| } |
| |
| @Override |
| public MediaItem getMediaItem(int index) { |
| if (!mRepeat && index >= mOrder.length) return null; |
| if (mOrder.length == 0) return null; |
| mLastIndex = mOrder[index % mOrder.length]; |
| MediaItem item = findMediaItem(mMediaSet, mLastIndex); |
| for (int i = 0; i < RETRY_COUNT && item == null; ++i) { |
| Log.w(TAG, "fail to find image: " + mLastIndex); |
| mLastIndex = mRandom.nextInt(mOrder.length); |
| item = findMediaItem(mMediaSet, mLastIndex); |
| } |
| return item; |
| } |
| |
| @Override |
| public long reload() { |
| long version = mMediaSet.reload(); |
| if (version != mSourceVersion) { |
| mSourceVersion = version; |
| int count = mMediaSet.getTotalMediaItemCount(); |
| if (count != mOrder.length) generateOrderArray(count); |
| } |
| return version; |
| } |
| |
| private void generateOrderArray(int totalCount) { |
| if (mOrder.length != totalCount) { |
| mOrder = new int[totalCount]; |
| for (int i = 0; i < totalCount; ++i) { |
| mOrder[i] = i; |
| } |
| } |
| for (int i = totalCount - 1; i > 0; --i) { |
| Utils.swap(mOrder, i, mRandom.nextInt(i + 1)); |
| } |
| if (mOrder[0] == mLastIndex && totalCount > 1) { |
| Utils.swap(mOrder, 0, mRandom.nextInt(totalCount - 1) + 1); |
| } |
| } |
| |
| @Override |
| public void addContentListener(ContentListener listener) { |
| mMediaSet.addContentListener(listener); |
| } |
| |
| @Override |
| public void removeContentListener(ContentListener listener) { |
| mMediaSet.removeContentListener(listener); |
| } |
| } |
| |
| private static class SequentialSource implements SlideshowDataAdapter.SlideshowSource { |
| private static final int DATA_SIZE = 32; |
| |
| private ArrayList<MediaItem> mData = new ArrayList<MediaItem>(); |
| private int mDataStart = 0; |
| private long mDataVersion = MediaObject.INVALID_DATA_VERSION; |
| private final MediaSet mMediaSet; |
| private final boolean mRepeat; |
| |
| public SequentialSource(MediaSet mediaSet, boolean repeat) { |
| mMediaSet = mediaSet; |
| mRepeat = repeat; |
| } |
| |
| @Override |
| public int findItemIndex(Path path, int hint) { |
| return mMediaSet.getIndexOfItem(path, hint); |
| } |
| |
| @Override |
| public MediaItem getMediaItem(int index) { |
| int dataEnd = mDataStart + mData.size(); |
| |
| if (mRepeat) { |
| int count = mMediaSet.getMediaItemCount(); |
| if (count == 0) return null; |
| index = index % count; |
| } |
| if (index < mDataStart || index >= dataEnd) { |
| mData = mMediaSet.getMediaItem(index, DATA_SIZE); |
| mDataStart = index; |
| dataEnd = index + mData.size(); |
| } |
| |
| return (index < mDataStart || index >= dataEnd) ? null : mData.get(index - mDataStart); |
| } |
| |
| @Override |
| public long reload() { |
| long version = mMediaSet.reload(); |
| if (version != mDataVersion) { |
| mDataVersion = version; |
| mData.clear(); |
| } |
| return mDataVersion; |
| } |
| |
| @Override |
| public void addContentListener(ContentListener listener) { |
| mMediaSet.addContentListener(listener); |
| } |
| |
| @Override |
| public void removeContentListener(ContentListener listener) { |
| mMediaSet.removeContentListener(listener); |
| } |
| } |
| } |