From 5006d4093ad1455ee98c157a71f57e9ea42b4dae Mon Sep 17 00:00:00 2001 From: Chris Wren Date: Fri, 12 Oct 2012 15:32:46 -0400 Subject: Better handling for network lag and large images. Bug: 7339488 Change-Id: I3a26b30f766fc240e73e19c14a5ee14288bd4fb1 --- .../android/dreams/phototable/PhotoCarousel.java | 181 +++++++++++++-------- src/com/android/dreams/phototable/PhotoSource.java | 17 +- 2 files changed, 125 insertions(+), 73 deletions(-) (limited to 'src') 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 mBitmapStore; + private final LinkedList mBitmapQueue; + private final LinkedList mBitmapLoaders; + private View mSpinner; private int mOrientation; private int mWidth; private int mHeight; private int mLongSide; private int mShortSide; - private final HashMap 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(); + mBitmapQueue = new LinkedList(); + mBitmapLoaders = new LinkedList(); mPanel = new View[2]; mFlipper = new Flipper(); @@ -97,50 +130,78 @@ public class PhotoCarousel extends FrameLayout { } private class PhotoLoadTask extends AsyncTask { - 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 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) { -- cgit v1.2.3