summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorChris Wren <cwren@android.com>2012-10-12 15:32:46 -0400
committerChris Wren <cwren@android.com>2012-10-15 17:11:12 -0400
commit5006d4093ad1455ee98c157a71f57e9ea42b4dae (patch)
tree35da5d8ccdaefc11f8070e03d9c67ee6e7351e16 /src
parente8f4d55d6895e1f2bc298ef8978e1aef43ff57c5 (diff)
downloadandroid_packages_screensavers_PhotoTable-5006d4093ad1455ee98c157a71f57e9ea42b4dae.tar.gz
android_packages_screensavers_PhotoTable-5006d4093ad1455ee98c157a71f57e9ea42b4dae.tar.bz2
android_packages_screensavers_PhotoTable-5006d4093ad1455ee98c157a71f57e9ea42b4dae.zip
Better handling for network lag and large images.
Bug: 7339488 Change-Id: I3a26b30f766fc240e73e19c14a5ee14288bd4fb1
Diffstat (limited to 'src')
-rw-r--r--src/com/android/dreams/phototable/PhotoCarousel.java181
-rw-r--r--src/com/android/dreams/phototable/PhotoSource.java17
2 files changed, 125 insertions, 73 deletions
diff --git a/src/com/android/dreams/phototable/PhotoCarousel.java b/src/com/android/dreams/phototable/PhotoCarousel.java
index 0c957ab..495e73c 100644
--- a/src/com/android/dreams/phototable/PhotoCarousel.java
+++ b/src/com/android/dreams/phototable/PhotoCarousel.java
@@ -31,6 +31,8 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.ListIterator;
/**
* A FrameLayout that holds two photos, back to back.
@@ -49,18 +51,46 @@ public class PhotoCarousel extends FrameLayout {
private final BitmapFactory.Options mOptions;
private final int mFlipDuration;
private final int mDropPeriod;
- private boolean mOnce;
+ private final int mBitmapQueueLimit;
+ private final HashMap<View, Bitmap> mBitmapStore;
+ private final LinkedList<Bitmap> mBitmapQueue;
+ private final LinkedList<PhotoLoadTask> mBitmapLoaders;
+ private View mSpinner;
private int mOrientation;
private int mWidth;
private int mHeight;
private int mLongSide;
private int mShortSide;
- private final HashMap<View, Bitmap> mBitmapStore;
+ private long mLastFlipTime;
class Flipper implements Runnable {
@Override
public void run() {
- PhotoCarousel.this.flip(1f);
+ maybeLoadMore();
+
+ if (mBitmapQueue.isEmpty()) {
+ mSpinner.setVisibility(View.VISIBLE);
+ } else {
+ mSpinner.setVisibility(View.GONE);
+ }
+
+ long now = System.currentTimeMillis();
+ long elapsed = now - mLastFlipTime;
+
+ if (elapsed < mDropPeriod) {
+ scheduleNext((int) mDropPeriod - elapsed);
+ } else {
+ scheduleNext(mDropPeriod);
+ if (changePhoto() || canFlip()) {
+ flip(1f);
+ mLastFlipTime = now;
+ }
+ }
+ }
+
+ private void scheduleNext(long delay) {
+ removeCallbacks(mFlipper);
+ postDelayed(mFlipper, delay);
}
}
@@ -68,12 +98,15 @@ public class PhotoCarousel extends FrameLayout {
super(context, as);
final Resources resources = getResources();
mDropPeriod = resources.getInteger(R.integer.carousel_drop_period);
+ mBitmapQueueLimit = resources.getInteger(R.integer.num_images_to_preload);
mFlipDuration = resources.getInteger(R.integer.flip_duration);
mOptions = new BitmapFactory.Options();
mOptions.inTempStorage = new byte[32768];
mPhotoSource = new PhotoSourcePlexor(getContext(),
getContext().getSharedPreferences(FlipperDreamSettings.PREFS_NAME, 0));
mBitmapStore = new HashMap<View, Bitmap>();
+ mBitmapQueue = new LinkedList<Bitmap>();
+ mBitmapLoaders = new LinkedList<PhotoLoadTask>();
mPanel = new View[2];
mFlipper = new Flipper();
@@ -97,50 +130,78 @@ public class PhotoCarousel extends FrameLayout {
}
private class PhotoLoadTask extends AsyncTask<Void, Void, Bitmap> {
- private ImageView mDestination;
-
- public PhotoLoadTask(View destination) {
- mDestination = (ImageView) destination;
- }
-
@Override
public Bitmap doInBackground(Void... unused) {
- Bitmap decodedPhoto = mPhotoSource.next(PhotoCarousel.this.mOptions,
- PhotoCarousel.this.mLongSide, PhotoCarousel.this.mShortSide);
+ Bitmap decodedPhoto;
+ if (mLongSide == 0 || mShortSide == 0) {
+ return null;
+ }
+ decodedPhoto = mPhotoSource.next(mOptions, mLongSide, mShortSide);
return decodedPhoto;
}
@Override
public void onPostExecute(Bitmap photo) {
if (photo != null) {
- Bitmap old = mBitmapStore.get(mDestination);
- int width = PhotoCarousel.this.mOptions.outWidth;
- int height = PhotoCarousel.this.mOptions.outHeight;
- int orientation = (width > height ? LANDSCAPE : PORTRAIT);
-
- mDestination.setImageBitmap(photo);
- mDestination.setTag(R.id.photo_orientation, new Integer(orientation));
- mDestination.setTag(R.id.photo_width, new Integer(width));
- mDestination.setTag(R.id.photo_height, new Integer(height));
- PhotoCarousel.this.setScaleType(mDestination);
-
- mBitmapStore.put(mDestination, photo);
- if (old != null) {
- old.recycle();
- }
- PhotoCarousel.this.requestLayout();
- } else {
- postDelayed(new Runnable() {
- @Override
- public void run() {
- new PhotoLoadTask(mDestination)
- .execute();
- }
- }, 100);
+ mBitmapQueue.offer(photo);
}
+ mFlipper.run();
}
};
+ private void maybeLoadMore() {
+ if (!mBitmapLoaders.isEmpty()) {
+ for(ListIterator<PhotoLoadTask> i = mBitmapLoaders.listIterator(0);
+ i.hasNext();) {
+ PhotoLoadTask loader = i.next();
+ if (loader.getStatus() == AsyncTask.Status.FINISHED) {
+ i.remove();
+ }
+ }
+ }
+
+ if ((mBitmapLoaders.size() + mBitmapQueue.size()) < mBitmapQueueLimit) {
+ PhotoLoadTask task = new PhotoLoadTask();
+ mBitmapLoaders.offer(task);
+ task.execute();
+ }
+ }
+
+ private ImageView getBackface() {
+ return (ImageView) ((mPanel[0].getAlpha() < 0.5f) ? mPanel[0] : mPanel[1]);
+ }
+
+ private boolean canFlip() {
+ return mBitmapStore.containsKey(getBackface());
+ }
+
+ private boolean changePhoto() {
+ Bitmap photo = mBitmapQueue.poll();
+ if (photo != null) {
+ ImageView destination = getBackface();
+ Bitmap old = mBitmapStore.get(destination);
+ int width = mOptions.outWidth;
+ int height = mOptions.outHeight;
+ int orientation = (width > height ? LANDSCAPE : PORTRAIT);
+
+ destination.setImageBitmap(photo);
+ destination.setTag(R.id.photo_orientation, new Integer(orientation));
+ destination.setTag(R.id.photo_width, new Integer(width));
+ destination.setTag(R.id.photo_height, new Integer(height));
+ setScaleType(destination);
+
+ mBitmapStore.put(destination, photo);
+
+ if (old != null) {
+ old.recycle();
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
private void setScaleType(View photo) {
if (photo.getTag(R.id.photo_orientation) != null) {
int orientation = ((Integer) photo.getTag(R.id.photo_orientation)).intValue();
@@ -192,28 +253,24 @@ public class PhotoCarousel extends FrameLayout {
ViewPropertyAnimator backAnim = mPanel[1].animate()
.rotationY(backY)
.alpha(backA)
- .setDuration(mFlipDuration);
-
- int replaceIdx = 1;
- ViewPropertyAnimator replaceAnim = backAnim;
- if (frontA == 0f) {
- replaceAnim = frontAnim;
- replaceIdx = 0;
- }
-
- final View replaceView = mPanel[replaceIdx];
- replaceAnim.withEndAction(new Runnable() {
- @Override
- public void run() {
- new PhotoLoadTask(replaceView)
- .execute();
- }
- });
+ .setDuration(mFlipDuration)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ maybeLoadMore();
+ }
+ });
frontAnim.start();
backAnim.start();
+ }
- scheduleNext(mDropPeriod);
+ @Override
+ public void onAttachedToWindow() {
+ mPanel[0]= findViewById(R.id.front);
+ mPanel[1] = findViewById(R.id.back);
+ mSpinner = findViewById(R.id.spinner);
+ mFlipper.run();
}
@Override
@@ -222,20 +279,11 @@ public class PhotoCarousel extends FrameLayout {
mWidth = right - left;
mOrientation = (mWidth > mHeight ? LANDSCAPE : PORTRAIT);
+
+ boolean init = mLongSide == 0;
mLongSide = (int) Math.max(mWidth, mHeight);
mShortSide = (int) Math.min(mWidth, mHeight);
- if (!mOnce) {
- mOnce = true;
-
- mPanel[0] = findViewById(R.id.front);
- mPanel[1] = findViewById(R.id.back);
-
- new PhotoLoadTask(mPanel[0]).execute();
-
- scheduleNext(mDropPeriod);
- }
-
// reset scale types for new aspect ratio
setScaleType(mPanel[0]);
setScaleType(mPanel[1]);
@@ -249,11 +297,6 @@ public class PhotoCarousel extends FrameLayout {
return true;
}
- public void scheduleNext(int delay) {
- removeCallbacks(mFlipper);
- postDelayed(mFlipper, delay);
- }
-
private void log(String message) {
if (DEBUG) {
Log.i(TAG, message);
diff --git a/src/com/android/dreams/phototable/PhotoSource.java b/src/com/android/dreams/phototable/PhotoSource.java
index 62c0f4f..1fe6194 100644
--- a/src/com/android/dreams/phototable/PhotoSource.java
+++ b/src/com/android/dreams/phototable/PhotoSource.java
@@ -113,15 +113,19 @@ public abstract class PhotoSource {
public Bitmap next(BitmapFactory.Options options, int longSide, int shortSide) {
log(TAG, "decoding a picasa resource to " + longSide + ", " + shortSide);
Bitmap image = null;
+ ImageData imageData = null;
int tries = 0;
while (image == null && tries < mBadImageSkipLimit) {
- if (mImageQueue.isEmpty()) {
- fillQueue();
- }
+ synchronized(mImageQueue) {
+ if (mImageQueue.isEmpty()) {
+ fillQueue();
+ }
+ imageData = mImageQueue.poll();
+ }
if (!mImageQueue.isEmpty()) {
- image = load(mImageQueue.poll(), options, longSide, shortSide);
+ image = load(imageData, options, longSide, shortSide);
}
tries++;
@@ -211,10 +215,15 @@ public abstract class PhotoSource {
log(TAG, "Stream decoding failed with no error" +
(options.mCancel ? " due to cancelation." : "."));
}
+ } catch (OutOfMemoryError ome) {
+ log(TAG, "OUT OF MEMORY: " + ome);
+ image = null;
} catch (FileNotFoundException fnf) {
log(TAG, "file not found: " + fnf);
+ image = null;
} catch (IOException ioe) {
log(TAG, "i/o exception: " + ioe);
+ image = null;
} finally {
try {
if (is != null) {