/*
* 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);
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);
}
}
}