summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorChris Wren <cwren@android.com>2012-09-05 17:35:10 -0400
committerChris Wren <cwren@android.com>2012-09-05 17:38:47 -0400
commit83fee9012b6d5c5940de5b96fe8d98653ba14c0d (patch)
treeefe4be787e7390528834a1d9e608cf66603808a0 /src/com
parentc1501041b64faa6c205a93baf403c4c87a0c1acf (diff)
downloadandroid_packages_screensavers_PhotoTable-83fee9012b6d5c5940de5b96fe8d98653ba14c0d.tar.gz
android_packages_screensavers_PhotoTable-83fee9012b6d5c5940de5b96fe8d98653ba14c0d.tar.bz2
android_packages_screensavers_PhotoTable-83fee9012b6d5c5940de5b96fe8d98653ba14c0d.zip
refactor photo sources and other cleanup.
Change-Id: I22fda6f1e443776133bc9e0e242d4a6081eb1ba3
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/dreams/phototable/LocalSource.java175
-rw-r--r--src/com/android/dreams/phototable/PhotoCarousel.java49
-rw-r--r--src/com/android/dreams/phototable/PhotoSource.java183
-rw-r--r--src/com/android/dreams/phototable/PhotoSourcePlexor.java81
-rw-r--r--src/com/android/dreams/phototable/PicasaSource.java194
-rw-r--r--src/com/android/dreams/phototable/StockSource.java82
-rw-r--r--src/com/android/dreams/phototable/Table.java63
7 files changed, 441 insertions, 386 deletions
diff --git a/src/com/android/dreams/phototable/LocalSource.java b/src/com/android/dreams/phototable/LocalSource.java
index 4b260db..8dbc079 100644
--- a/src/com/android/dreams/phototable/LocalSource.java
+++ b/src/com/android/dreams/phototable/LocalSource.java
@@ -15,60 +15,52 @@
*/
package com.android.dreams.phototable;
-import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
import android.provider.MediaStore;
-import android.util.Log;
import java.io.FileInputStream;
-import java.io.BufferedInputStream;
-import java.util.Collections;
+import java.io.InputStream;
+import java.util.Collection;
import java.util.LinkedList;
-import java.util.Random;
/**
- * Picks a random image from the local store.
+ * Loads images from the local store.
*/
-public class LocalSource {
+public class LocalSource extends PhotoSource {
private static final String TAG = "PhotoTable.LocalSource";
- static final boolean DEBUG = false;
- public static class ImageData {
- public String path;
- public int orientation;
- }
-
- private final ContentResolver mResolver;
- private final Context mContext;
- private final LinkedList<ImageData> mImageQueue;
- private final float mImageRatio;
- private final int mMaxQueueSize;
- private final Random mRNG;
private int mNextPosition;
+ public static final int TYPE = 2;
+
public LocalSource(Context context) {
- mContext = context;
- mResolver = mContext.getContentResolver();
+ super(context);
+ mSourceName = TAG;
mNextPosition = -1;
- mImageQueue = new LinkedList<ImageData>();
- mImageRatio = context.getResources().getInteger(R.integer.image_ratio) / 1000000f;
- mMaxQueueSize = context.getResources().getInteger(R.integer.image_queue_size);
- mRNG = new Random();
fillQueue();
}
- private void fillQueue() {
- log("filling queue");
- String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION};
+ @Override
+ protected Collection<ImageData> findImages(int howMany) {
+ log(TAG, "finding images");
+ LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
+
+ String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION,
+ MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME};
+ String[] selectionArgs = {}; // settings go here
+ String selection = "";
+ for (String arg : selectionArgs) {
+ if (selection.length() > 0) {
+ selection += " OR ";
+ }
+ selection += MediaStore.Images.Media.BUCKET_ID + " = '" + arg + "'";
+ }
Cursor cursor = mResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- projection, null, null, null);
+ projection, selection, null, null);
if (cursor != null) {
- if (cursor.getCount() > mMaxQueueSize && mNextPosition == -1) {
- mNextPosition = mRNG.nextInt() % (cursor.getCount() - mMaxQueueSize);
+ if (cursor.getCount() > howMany && mNextPosition == -1) {
+ mNextPosition = mRNG.nextInt() % (cursor.getCount() - howMany);
}
if (mNextPosition == -1) {
mNextPosition = 0;
@@ -77,14 +69,18 @@ public class LocalSource {
int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
int orientationIndex = cursor.getColumnIndex(MediaStore.Images.Media.ORIENTATION);
+ int bucketIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID);
+ int nameIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_DISPLAY_NAME);
+
if (dataIndex < 0) {
- log("can't find the DATA column!");
+ log(TAG, "can't find the DATA column!");
} else {
- while (mImageQueue.size() < mMaxQueueSize && !cursor.isAfterLast()) {
+ while (foundImages.size() < howMany && !cursor.isAfterLast()) {
ImageData data = new ImageData();
- data.path = cursor.getString(dataIndex);
+ data.type = TYPE;
+ data.url = cursor.getString(dataIndex);
data.orientation = cursor.getInt(orientationIndex);
- mImageQueue.offer(data);
+ foundImages.offer(data);
if (cursor.moveToNext()) {
mNextPosition++;
}
@@ -93,101 +89,24 @@ public class LocalSource {
mNextPosition = 0;
}
}
+
cursor.close();
}
- Collections.shuffle(mImageQueue);
- log("queue contains: " + mImageQueue.size() + " items.");
+ log(TAG, "found " + foundImages.size() + " items.");
+ return foundImages;
}
- public Bitmap next(BitmapFactory.Options options, int longSide, int shortSide) {
- log("decoding a local resource to " + longSide + ", " + shortSide);
- Bitmap image = null;
-
- if (mImageQueue.isEmpty()) {
- fillQueue();
- }
-
- if (!mImageQueue.isEmpty()) {
- ImageData data = mImageQueue.poll();
-
- FileInputStream fis = null;
- try {
- log("decoding:" + data.path);
- fis = new FileInputStream(data.path);
- options.inJustDecodeBounds = true;
- options.inSampleSize = 1;
- BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);
- int rawLongSide = Math.max(options.outWidth, options.outHeight);
- int rawShortSide = Math.min(options.outWidth, options.outHeight);
- log("I see bounds of " + rawLongSide + ", " + rawShortSide);
-
- float ratio = Math.max((float) longSide / (float) rawLongSide,
- (float) shortSide / (float) rawShortSide);
- while (ratio < 0.5) {
- options.inSampleSize *= 2;
- ratio *= 2;
- }
-
- log("decoding with inSampleSize " + options.inSampleSize);
- options.inJustDecodeBounds = false;
- image = BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);
- rawLongSide = Math.max(options.outWidth, options.outHeight);
- rawShortSide = Math.max(options.outWidth, options.outHeight);
- ratio = Math.max((float) longSide / (float) rawLongSide,
- (float) shortSide / (float) rawShortSide);
-
- if (ratio < 1.0f) {
- log("still too big, scaling down by " + ratio);
- options.outWidth = (int) (ratio * options.outWidth);
- options.outHeight = (int) (ratio * options.outHeight);
- image = Bitmap.createScaledBitmap(image,
- options.outWidth, options.outHeight,
- true);
- }
-
- if (data.orientation != 0) {
- log("rotated by " + data.orientation + ": fixing");
- if (data.orientation == 90 || data.orientation == 270) {
- int tmp = options.outWidth;
- options.outWidth = options.outHeight;
- options.outHeight = tmp;
- }
- Matrix matrix = new Matrix();
- matrix.setRotate(data.orientation,
- (float) image.getWidth() / 2,
- (float) image.getHeight() / 2);
- image = Bitmap.createBitmap(image, 0, 0,
- options.outHeight, options.outWidth,
- matrix, true);
- }
-
- log("returning bitmap sized to " + image.getWidth() + ", " + image.getHeight());
- } catch (Exception ex) {
- log(ex.toString());
- return null;
- } finally {
- try {
- if (fis != null) {
- fis.close();
- }
- } catch (Throwable t) {
- log("close fail: " + t.toString());
- }
- }
- } else {
- log("device has no local images.");
+ @Override
+ protected InputStream getStream(ImageData data) {
+ FileInputStream fis = null;
+ try {
+ log(TAG, "opening:" + data.url);
+ fis = new FileInputStream(data.url);
+ } catch (Exception ex) {
+ log(TAG, ex.toString());
+ fis = null;
}
- return image;
- }
-
- public void setSeed(long seed) {
- mRNG.setSeed(seed);
- }
-
- private void log(String message) {
- if (DEBUG) {
- Log.i(TAG, message);
- }
+ return (InputStream) fis;
}
}
diff --git a/src/com/android/dreams/phototable/PhotoCarousel.java b/src/com/android/dreams/phototable/PhotoCarousel.java
index d704a88..838af18 100644
--- a/src/com/android/dreams/phototable/PhotoCarousel.java
+++ b/src/com/android/dreams/phototable/PhotoCarousel.java
@@ -30,6 +30,8 @@ import android.view.ViewPropertyAnimator;
import android.widget.FrameLayout;
import android.widget.ImageView;
+import java.util.HashMap;
+
/**
* A FrameLayout that holds two photos, back to back.
*/
@@ -37,9 +39,7 @@ public class PhotoCarousel extends FrameLayout {
private static final String TAG = "PhotoCarousel";
private final Flipper mFlipper;
- private final PicasaSource mPicasaSource;
- private final LocalSource mLocalSource;
- private final StockSource mStockSource;
+ private final PhotoSourcePlexor mPhotoSource;
private final GestureDetector mGestureDetector;
private final View[] mPanel;
private final BitmapFactory.Options mOptions;
@@ -48,6 +48,7 @@ public class PhotoCarousel extends FrameLayout {
private boolean mOnce;
private int mLongSide;
private int mShortSide;
+ private final HashMap<View, Bitmap> mBitmapStore;
class Flipper implements Runnable {
@Override
@@ -63,9 +64,9 @@ public class PhotoCarousel extends FrameLayout {
mFlipDuration = resources.getInteger(R.integer.flip_duration);
mOptions = new BitmapFactory.Options();
mOptions.inTempStorage = new byte[32768];
- mPicasaSource = new PicasaSource(context);
- mLocalSource = new LocalSource(context);
- mStockSource = new StockSource(context);
+ mPhotoSource = new PhotoSourcePlexor(context);
+ mBitmapStore = new HashMap<View, Bitmap>();
+
mPanel = new View[2];
mFlipper = new Flipper();
mGestureDetector = new GestureDetector(context,
@@ -88,34 +89,34 @@ public class PhotoCarousel extends FrameLayout {
}
private class PhotoLoadTask extends AsyncTask<Void, Void, Bitmap> {
- private PhotoCarousel mCarousel;
+ private int mTries;
private ImageView mDestination;
- public PhotoLoadTask(PhotoCarousel carousel, View destination) {
- mCarousel = carousel;
+ public PhotoLoadTask(View destination) {
+ mTries = 0;
mDestination = (ImageView) destination;
}
+
@Override
public Bitmap doInBackground(Void... unused) {
- Bitmap decodedPhoto = null;
- decodedPhoto = mCarousel.mPicasaSource.next(mCarousel.mOptions,
- mCarousel.mLongSide, mCarousel.mShortSide);
- if (decodedPhoto == null) {
- decodedPhoto = mCarousel.mLocalSource.next(mCarousel.mOptions,
- mCarousel.mLongSide, mCarousel.mShortSide);
- }
- if (decodedPhoto == null) {
- decodedPhoto = mCarousel.mStockSource.next(mCarousel.mOptions,
- mCarousel.mLongSide, mCarousel.mShortSide);
- }
+ Bitmap decodedPhoto = mPhotoSource.next(PhotoCarousel.this.mOptions,
+ PhotoCarousel.this.mLongSide, PhotoCarousel.this.mShortSide);
return decodedPhoto;
}
@Override
public void onPostExecute(Bitmap photo) {
if (photo != null) {
+ Bitmap old = mBitmapStore.get(mDestination);
mDestination.setImageBitmap(photo);
- mCarousel.requestLayout();
+ mBitmapStore.put(mDestination, photo);
+ if (old != null) {
+ old.recycle();
+ }
+ PhotoCarousel.this.requestLayout();
+ } else if (mTries < 3) {
+ mTries++;
+ this.execute();
}
}
};
@@ -166,7 +167,7 @@ public class PhotoCarousel extends FrameLayout {
replaceAnim.withEndAction(new Runnable() {
@Override
public void run() {
- new PhotoLoadTask(PhotoCarousel.this, replaceView)
+ new PhotoLoadTask(replaceView)
.execute();
}
});
@@ -192,8 +193,8 @@ public class PhotoCarousel extends FrameLayout {
mPanel[0] = findViewById(R.id.front);
mPanel[1] = findViewById(R.id.back);
- new PhotoLoadTask(this, mPanel[0]).execute();
- new PhotoLoadTask(this, mPanel[1]).execute();
+ new PhotoLoadTask(mPanel[0]).execute();
+ new PhotoLoadTask(mPanel[1]).execute();
scheduleNext(mDropPeriod);
}
diff --git a/src/com/android/dreams/phototable/PhotoSource.java b/src/com/android/dreams/phototable/PhotoSource.java
new file mode 100644
index 0000000..2851c6c
--- /dev/null
+++ b/src/com/android/dreams/phototable/PhotoSource.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2012 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.dreams.phototable;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.BufferedInputStream;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.Random;
+
+/**
+ * Picks a random image from a source of photos.
+ */
+public abstract class PhotoSource {
+ private static final String TAG = "PhotoTable.PhotoSource";
+ private static final boolean DEBUG = false;
+
+ // This should be large enough for BitmapFactory to decode the header so
+ // that we can mark and reset the input stream to avoid duplicate network i/o
+ private static final int BUFFER_SIZE = 128 * 1024;
+
+ public static class ImageData {
+ public String id;
+ public String url;
+ public int orientation;
+ public int type;
+ }
+
+ private final Context mContext;
+ private final LinkedList<ImageData> mImageQueue;
+ private final int mMaxQueueSize;
+
+ protected final Resources mResources;
+ protected ContentResolver mResolver;
+ protected String mSourceName;
+ protected final Random mRNG;
+
+ public PhotoSource(Context context) {
+ mSourceName = TAG;
+ mContext = context;
+ mResolver = mContext.getContentResolver();
+ mResources = context.getResources();
+ mImageQueue = new LinkedList<ImageData>();
+ mMaxQueueSize = mResources.getInteger(R.integer.image_queue_size);
+ mRNG = new Random();
+ }
+
+ protected void fillQueue() {
+ log(TAG, "filling queue");
+ mImageQueue.addAll(findImages(mMaxQueueSize - mImageQueue.size()));
+ Collections.shuffle(mImageQueue);
+ log(TAG, "queue contains: " + mImageQueue.size() + " items.");
+ }
+
+ public Bitmap next(BitmapFactory.Options options, int longSide, int shortSide) {
+ log(TAG, "decoding a picasa resource to " + longSide + ", " + shortSide);
+ Bitmap image = null;
+
+ if (mImageQueue.isEmpty()) {
+ fillQueue();
+ }
+
+ if (!mImageQueue.isEmpty()) {
+ ImageData data = mImageQueue.poll();
+ InputStream is = null;
+ try {
+ is = getStream(data);
+ BufferedInputStream bis = new BufferedInputStream(is);
+ bis.mark(BUFFER_SIZE);
+
+ options.inJustDecodeBounds = true;
+ options.inSampleSize = 1;
+ image = BitmapFactory.decodeStream(new BufferedInputStream(bis), null, options);
+ int rawLongSide = Math.max(options.outWidth, options.outHeight);
+ int rawShortSide = Math.min(options.outWidth, options.outHeight);
+ log(TAG, "I see bounds of " + rawLongSide + ", " + rawShortSide);
+
+ if (rawLongSide != -1 && rawShortSide != -1) {
+ float ratio = Math.max((float) longSide / (float) rawLongSide,
+ (float) shortSide / (float) rawShortSide);
+ while (ratio < 0.5) {
+ options.inSampleSize *= 2;
+ ratio *= 2;
+ }
+
+ log(TAG, "decoding with inSampleSize " + options.inSampleSize);
+ bis.reset();
+ options.inJustDecodeBounds = false;
+ image = BitmapFactory.decodeStream(bis, null, options);
+ rawLongSide = Math.max(options.outWidth, options.outHeight);
+ rawShortSide = Math.max(options.outWidth, options.outHeight);
+ ratio = Math.max((float) longSide / (float) rawLongSide,
+ (float) shortSide / (float) rawShortSide);
+
+ if (ratio < 1.0f) {
+ log(TAG, "still too big, scaling down by " + ratio);
+ options.outWidth = (int) (ratio * options.outWidth);
+ options.outHeight = (int) (ratio * options.outHeight);
+ image = Bitmap.createScaledBitmap(image,
+ options.outWidth, options.outHeight,
+ true);
+ }
+
+ if (data.orientation != 0) {
+ log(TAG, "rotated by " + data.orientation + ": fixing");
+ if (data.orientation == 90 || data.orientation == 270) {
+ int tmp = options.outWidth;
+ options.outWidth = options.outHeight;
+ options.outHeight = tmp;
+ }
+ Matrix matrix = new Matrix();
+ matrix.setRotate(data.orientation,
+ (float) image.getWidth() / 2,
+ (float) image.getHeight() / 2);
+ image = Bitmap.createBitmap(image, 0, 0,
+ options.outHeight, options.outWidth,
+ matrix, true);
+ }
+
+ log(TAG, "returning bitmap " + image.getWidth() + ", " + image.getHeight());
+ } else {
+ log(TAG, "decoding failed with no error: " + options.mCancel);
+ }
+ } catch (FileNotFoundException fnf) {
+ log(TAG, "file not found: " + fnf);
+ } catch (IOException ioe) {
+ log(TAG, "i/o exception: " + ioe);
+ } finally {
+ try {
+ if (is != null) {
+ is.close();
+ }
+ } catch (Throwable t) {
+ log(TAG, "close fail: " + t.toString());
+ }
+ }
+ } else {
+ log(TAG, mSourceName + " has no images.");
+ }
+
+ return image;
+ }
+
+ public void setSeed(long seed) {
+ mRNG.setSeed(seed);
+ }
+
+ protected void log(String tag, String message) {
+ if (DEBUG) {
+ Log.i(tag, message);
+ }
+ }
+
+ protected abstract InputStream getStream(ImageData data);
+ protected abstract Collection<ImageData> findImages(int howMany);
+}
diff --git a/src/com/android/dreams/phototable/PhotoSourcePlexor.java b/src/com/android/dreams/phototable/PhotoSourcePlexor.java
new file mode 100644
index 0000000..4c4550e
--- /dev/null
+++ b/src/com/android/dreams/phototable/PhotoSourcePlexor.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2012 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.dreams.phototable;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.LinkedList;
+
+/**
+ * Loads images from a variety of sources.
+ */
+public class PhotoSourcePlexor extends PhotoSource {
+ private static final String TAG = "PhotoTable.PhotoSourcePlexor";
+
+ private final PhotoSource mPicasaSource;
+ private final PhotoSource mLocalSource;
+ private final PhotoSource mStockSource;
+
+ public PhotoSourcePlexor(Context context) {
+ super(context);
+ mSourceName = TAG;
+ mPicasaSource = new PicasaSource(context);
+ mLocalSource = new LocalSource(context);
+ mStockSource = new StockSource(context);
+ }
+
+ @Override
+ protected Collection<ImageData> findImages(int howMany) {
+ log(TAG, "finding images");
+ LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
+
+ foundImages.addAll(mPicasaSource.findImages(howMany));
+ log(TAG, "found " + foundImages.size() + " network images");
+
+ foundImages.addAll(mLocalSource.findImages(howMany));
+ log(TAG, "found " + foundImages.size() + " user images");
+
+ if (foundImages.isEmpty()) {
+ foundImages.addAll(mStockSource.findImages(howMany));
+ }
+ log(TAG, "found " + foundImages.size() + " images");
+
+ return foundImages;
+ }
+
+ @Override
+ protected InputStream getStream(ImageData data) {
+ switch (data.type) {
+ case PicasaSource.TYPE:
+ return mPicasaSource.getStream(data);
+
+ case LocalSource.TYPE:
+ return mLocalSource.getStream(data);
+
+ case StockSource.TYPE:
+ return mStockSource.getStream(data);
+
+ default:
+ return null;
+ }
+ }
+}
diff --git a/src/com/android/dreams/phototable/PicasaSource.java b/src/com/android/dreams/phototable/PicasaSource.java
index d049a54..061b66b 100644
--- a/src/com/android/dreams/phototable/PicasaSource.java
+++ b/src/com/android/dreams/phototable/PicasaSource.java
@@ -15,30 +15,21 @@
*/
package com.android.dreams.phototable;
-import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Matrix;
import android.net.Uri;
-import android.provider.MediaStore;
-import android.util.Log;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;
-import java.io.BufferedInputStream;
-import java.util.Collections;
+import java.util.Collection;
import java.util.LinkedList;
-import java.util.Random;
/**
- * Picks a random image from the local store.
+ * Loads images from Picasa.
*/
-public class PicasaSource {
+public class PicasaSource extends PhotoSource {
private static final String TAG = "PhotoTable.PicasaSource";
- private static final boolean DEBUG = false;
private static final String PICASA_ID = "_id";
private static final String PICASA_URL = "content_url";
@@ -49,38 +40,21 @@ public class PicasaSource {
private static final String PICASA_TYPE_KEY = "type";
private static final String PICASA_TYPE_THUMB_VALUE = "full";
- // This should be large enough for BitmapFactory to decode the header so
- // that we can mark and reset the input stream to avoid duplicate network i/o
- private static final int BUFFER_SIZE = 128 * 1024;
-
- public static class ImageData {
- public String id;
- public String url;
- public String bucketId;
- public int orientation;
- }
-
- private final ContentResolver mResolver;
- private final Context mContext;
- private final LinkedList<ImageData> mImageQueue;
- private final float mImageRatio;
- private final int mMaxQueueSize;
- private final Random mRNG;
private int mNextPosition;
+ public static final int TYPE = 3;
+
public PicasaSource(Context context) {
- mContext = context;
- mResolver = mContext.getContentResolver();
+ super(context);
+ mSourceName = TAG;
mNextPosition = -1;
- mImageQueue = new LinkedList<ImageData>();
- mImageRatio = context.getResources().getInteger(R.integer.image_ratio) / 1000000f;
- mMaxQueueSize = context.getResources().getInteger(R.integer.image_queue_size);
- mRNG = new Random();
fillQueue();
}
- private void fillQueue() {
- log("filling queue");
+ @Override
+ protected Collection<ImageData> findImages(int howMany) {
+ log(TAG, "finding images");
+ LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
String[] projection = {PICASA_ID, PICASA_URL, PICASA_ROTATION, PICASA_ALBUM_ID};
String[] selectionArgs = {}; // settings go here
String selection = "";
@@ -97,16 +71,14 @@ public class PicasaSource {
Cursor cursor = mResolver.query(picasaUriBuilder.build(),
projection, selection, null, null);
if (cursor != null) {
- if (cursor.getCount() > mMaxQueueSize && mNextPosition == -1) {
- log("getcount: " + cursor.getCount());
- log("mMaxQueueSize: " + mMaxQueueSize);
+ if (cursor.getCount() > howMany && mNextPosition == -1) {
mNextPosition =
- (int) Math.abs(mRNG.nextInt() % (cursor.getCount() - mMaxQueueSize));
+ (int) Math.abs(mRNG.nextInt() % (cursor.getCount() - howMany));
}
if (mNextPosition == -1) {
mNextPosition = 0;
}
- log("moving to position: " + mNextPosition);
+ log(TAG, "moving to position: " + mNextPosition);
cursor.moveToPosition(mNextPosition);
int idIndex = cursor.getColumnIndex(PICASA_ID);
@@ -115,22 +87,19 @@ public class PicasaSource {
int bucketIndex = cursor.getColumnIndex(PICASA_ALBUM_ID);
if (idIndex < 0) {
- log("can't find the ID column!");
+ log(TAG, "can't find the ID column!");
} else {
- while (mImageQueue.size() < mMaxQueueSize && !cursor.isAfterLast()) {
+ while (foundImages.size() < howMany && !cursor.isAfterLast()) {
if (idIndex >= 0) {
ImageData data = new ImageData();
+ data.type = TYPE;
data.id = cursor.getString(idIndex);
- if (bucketIndex >= 0) {
- data.bucketId = cursor.getString(bucketIndex);
- }
-
if (urlIndex >= 0) {
data.url = cursor.getString(urlIndex);
}
- mImageQueue.offer(data);
+ foundImages.offer(data);
}
if (cursor.moveToNext()) {
mNextPosition++;
@@ -143,115 +112,32 @@ public class PicasaSource {
cursor.close();
}
- Collections.shuffle(mImageQueue);
- log("queue contains: " + mImageQueue.size() + " items.");
+ log(TAG, "found " + foundImages.size() + " items.");
+ return foundImages;
}
- public Bitmap next(BitmapFactory.Options options, int longSide, int shortSide) {
- log("decoding a picasa resource to " + longSide + ", " + shortSide);
- Bitmap image = null;
-
- if (mImageQueue.isEmpty()) {
- fillQueue();
- }
-
-
- if (!mImageQueue.isEmpty()) {
- ImageData data = mImageQueue.poll();
- InputStream is = null;
- try {
- log("bucket is: " + data.bucketId);
-
- options.inJustDecodeBounds = false;
- Uri.Builder photoUriBuilder = new Uri.Builder()
- .scheme("content")
- .authority("com.google.android.gallery3d.GooglePhotoProvider")
- .appendPath("photos")
- .appendPath(data.id)
- .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_THUMB_VALUE);
- if (data.url != null) {
- photoUriBuilder.appendQueryParameter(PICASA_URL_KEY, data.url);
- }
- is = mResolver.openInputStream(photoUriBuilder.build());
- BufferedInputStream bis = new BufferedInputStream(is);
- bis.mark(BUFFER_SIZE);
-
- options.inJustDecodeBounds = true;
- options.inSampleSize = 1;
- BitmapFactory.decodeStream(new BufferedInputStream(bis), null, options);
- int rawLongSide = Math.max(options.outWidth, options.outHeight);
- int rawShortSide = Math.min(options.outWidth, options.outHeight);
- log("I see bounds of " + rawLongSide + ", " + rawShortSide);
-
- float ratio = Math.max((float) longSide / (float) rawLongSide,
- (float) shortSide / (float) rawShortSide);
- while (ratio < 0.5) {
- options.inSampleSize *= 2;
- ratio *= 2;
- }
-
- log("decoding with inSampleSize " + options.inSampleSize);
- bis.reset();
- options.inJustDecodeBounds = false;
- image = BitmapFactory.decodeStream(bis, null, options);
- rawLongSide = Math.max(options.outWidth, options.outHeight);
- rawShortSide = Math.max(options.outWidth, options.outHeight);
- ratio = Math.max((float) longSide / (float) rawLongSide,
- (float) shortSide / (float) rawShortSide);
-
- if (ratio < 1.0f) {
- log("still too big, scaling down by " + ratio);
- options.outWidth = (int) (ratio * options.outWidth);
- options.outHeight = (int) (ratio * options.outHeight);
- image = Bitmap.createScaledBitmap(image,
- options.outWidth, options.outHeight,
- true);
- }
-
- if (data.orientation != 0) {
- log("rotated by " + data.orientation + ": fixing");
- if (data.orientation == 90 || data.orientation == 270) {
- int tmp = options.outWidth;
- options.outWidth = options.outHeight;
- options.outHeight = tmp;
- }
- Matrix matrix = new Matrix();
- matrix.setRotate(data.orientation,
- (float) image.getWidth() / 2,
- (float) image.getHeight() / 2);
- image = Bitmap.createBitmap(image, 0, 0,
- options.outHeight, options.outWidth,
- matrix, true);
- }
-
- log("returning bitmap sized to " + image.getWidth() + ", " + image.getHeight());
- } catch (FileNotFoundException fnf) {
- log("file not found: " + fnf);
- } catch (IOException ioe) {
- log("i/o exception: " + ioe);
- } finally {
- try {
- if (is != null) {
- is.close();
- }
- } catch (Throwable t) {
- log("close fail: " + t.toString());
- }
+ @Override
+ protected InputStream getStream(ImageData data) {
+ InputStream is = null;
+ try {
+ Uri.Builder photoUriBuilder = new Uri.Builder()
+ .scheme("content")
+ .authority("com.google.android.gallery3d.GooglePhotoProvider")
+ .appendPath("photos")
+ .appendPath(data.id)
+ .appendQueryParameter(PICASA_TYPE_KEY, PICASA_TYPE_THUMB_VALUE);
+ if (data.url != null) {
+ photoUriBuilder.appendQueryParameter(PICASA_URL_KEY, data.url);
}
- } else {
- log("device has no picasa images.");
+ is = mResolver.openInputStream(photoUriBuilder.build());
+ } catch (FileNotFoundException fnf) {
+ log(TAG, "file not found: " + fnf);
+ is = null;
+ } catch (IOException ioe) {
+ log(TAG, "i/o exception: " + ioe);
+ is = null;
}
- return image;
- }
-
- public void setSeed(long seed) {
- mRNG.setSeed(seed);
- }
-
- private void log(String message) {
- if (DEBUG) {
- Log.i(TAG, message);
- }
+ return is;
}
}
diff --git a/src/com/android/dreams/phototable/StockSource.java b/src/com/android/dreams/phototable/StockSource.java
index 829ba86..63e834b 100644
--- a/src/com/android/dreams/phototable/StockSource.java
+++ b/src/com/android/dreams/phototable/StockSource.java
@@ -15,21 +15,18 @@
*/
package com.android.dreams.phototable;
-import android.content.ContentResolver;
import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.util.Log;
-import java.util.Random;
+import java.io.InputStream;
+import java.util.Collection;
+import java.util.LinkedList;
/**
* Picks a random image from the local store.
*/
-public class StockSource {
+public class StockSource extends PhotoSource {
private static final String TAG = "PhotoTable.StockSource";
- private static final boolean DEBUG = false;
private static final int[] PHOTOS = {R.drawable.photo_044_002,
R.drawable.photo_039_002,
R.drawable.photo_059_003,
@@ -43,54 +40,43 @@ public class StockSource {
R.drawable.photo_147_002,
R.drawable.photo_175_004
};
- private static Random sRNG = new Random();
- private final Context mContext;
- private final Resources mResources;
- public StockSource(Context context) {
- mContext = context;
- mResources = context.getResources();
- }
+ private final LinkedList<ImageData> mImageList;
+ private int mNextPosition;
- public Bitmap next(BitmapFactory.Options options, int longSide, int shortSide) {
- log("decoding a local resource to " + longSide + ", " + shortSide);
- int photo = PHOTOS[Math.abs(sRNG.nextInt() % PHOTOS.length)];
+ public static final int TYPE = 1;
- options.inJustDecodeBounds = true;
- options.inSampleSize = 1;
- BitmapFactory.decodeResource(mResources, photo, options);
- int rawLongSide = Math.max(options.outWidth, options.outHeight);
- int rawShortSide = Math.max(options.outWidth, options.outHeight);
- log("I see bounds of " + rawLongSide + ", " + rawShortSide);
- float ratio = Math.min((float) longSide / (float) rawLongSide,
- (float) shortSide / (float) rawShortSide);
- while (ratio < 0.5) {
- options.inSampleSize *= 2;
- ratio *= 2;
- }
- log("decoding with inSampleSize " + options.inSampleSize);
- options.inJustDecodeBounds = false;
- Bitmap bitmap = BitmapFactory.decodeResource(mResources, photo, options);
- rawLongSide = Math.max(options.outWidth, options.outHeight);
- rawShortSide = Math.max(options.outWidth, options.outHeight);
- ratio = Math.min((float) longSide / (float) rawLongSide,
- (float) shortSide / (float) rawShortSide);
+ public StockSource(Context context) {
+ super(context);
+ mSourceName = TAG;
+ mImageList = new LinkedList<ImageData>();
+ fillQueue();
+ }
- if (ratio < 1.0f) {
- log("still too big, scaling down by " + ratio);
- int photoWidth = (int) (ratio * options.outWidth);
- int photoHeight = (int) (ratio * options.outHeight);
- bitmap = Bitmap.createScaledBitmap(bitmap, photoWidth, photoHeight, true);
+ @Override
+ protected Collection<ImageData> findImages(int howMany) {
+ if (mImageList.isEmpty()) {
+ for (int i = 0; i < PHOTOS.length; i++) {
+ ImageData data = new ImageData();
+ data.type = TYPE;
+ data.id = Integer.toString(PHOTOS[i]);
+ mImageList.offer(data);
+ }
}
-
- log("returning bitmap sized to " + bitmap.getWidth() + ", " + bitmap.getHeight());
- return bitmap;
+ return mImageList;
}
- private static void log(String message) {
- if (DEBUG) {
- Log.i(TAG, message);
+ @Override
+ protected InputStream getStream(ImageData data) {
+ InputStream is = null;
+ try {
+ log(TAG, "opening:" + data.id);
+ is = mResources.openRawResource(Integer.valueOf(data.id));
+ } catch (Exception ex) {
+ log(TAG, ex.toString());
+ is = null;
}
- }
+ return is;
+ }
}
diff --git a/src/com/android/dreams/phototable/Table.java b/src/com/android/dreams/phototable/Table.java
index 8fc90bf..d673175 100644
--- a/src/com/android/dreams/phototable/Table.java
+++ b/src/com/android/dreams/phototable/Table.java
@@ -73,8 +73,7 @@ public class Table extends FrameLayout {
private final boolean mTapToExit;
private final int mTableCapacity;
private final int mInset;
- private final LocalSource mLocalSource;
- private final StockSource mStockSource;
+ private final PhotoSourcePlexor mPhotoSource;
private final Resources mResources;
private PhotoLaunchTask mPhotoLaunchTask;
private boolean mStarted;
@@ -104,8 +103,7 @@ public class Table extends FrameLayout {
mOnTable = new LinkedList<View>();
mOptions = new BitmapFactory.Options();
mOptions.inTempStorage = new byte[32768];
- mLocalSource = new LocalSource(getContext());
- mStockSource = new StockSource(getContext());
+ mPhotoSource = new PhotoSourcePlexor(getContext());
mLauncher = new Launcher(this);
mStarted = false;
}
@@ -210,42 +208,39 @@ public class Table extends FrameLayout {
return true;
}
- static class PhotoLaunchTask extends AsyncTask<Void, Void, View> {
- private Table mTable;
- public PhotoLaunchTask(Table table) {
- mTable = table;
+ private class PhotoLaunchTask extends AsyncTask<Void, Void, View> {
+ private int mTries;
+ public PhotoLaunchTask() {
+ mTries = 0;
}
+
@Override
public View doInBackground(Void... unused) {
log("load a new photo");
- LayoutInflater inflater = (LayoutInflater) mTable.getContext()
+ LayoutInflater inflater = (LayoutInflater) Table.this.getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View photo = inflater.inflate(R.layout.photo, null);
ImageView image = (ImageView) photo;
Drawable[] layers = new Drawable[2];
- Bitmap decodedPhoto = null;
- decodedPhoto = mTable.mLocalSource.next(mTable.mOptions,
- mTable.mLongSide, mTable.mShortSide);
- if (decodedPhoto == null) {
- decodedPhoto = mTable.mStockSource.next(mTable.mOptions,
- mTable.mLongSide, mTable.mShortSide);
- }
- int photoWidth = mTable.mOptions.outWidth;
- int photoHeight = mTable.mOptions.outHeight;
- if (mTable.mOptions.outWidth <= 0 || mTable.mOptions.outHeight <= 0) {
+ Bitmap decodedPhoto = Table.this.mPhotoSource.next(Table.this.mOptions,
+ Table.this.mLongSide, Table.this.mShortSide);
+ int photoWidth = Table.this.mOptions.outWidth;
+ int photoHeight = Table.this.mOptions.outHeight;
+ if (Table.this.mOptions.outWidth <= 0 || Table.this.mOptions.outHeight <= 0) {
photo = null;
} else {
- layers[0] = new BitmapDrawable(mTable.mResources, decodedPhoto);
- layers[1] = mTable.mResources.getDrawable(R.drawable.frame);
+ layers[0] = new BitmapDrawable(Table.this.mResources, decodedPhoto);
+ layers[1] = Table.this.mResources.getDrawable(R.drawable.frame);
LayerDrawable layerList = new LayerDrawable(layers);
- layerList.setLayerInset(0, mTable.mInset, mTable.mInset,
- mTable.mInset, mTable.mInset);
+ layerList.setLayerInset(0, Table.this.mInset, Table.this.mInset,
+ Table.this.mInset, Table.this.mInset);
image.setImageDrawable(layerList);
photo.setTag(R.id.photo_width, new Integer(photoWidth));
photo.setTag(R.id.photo_height, new Integer(photoHeight));
- photo.setOnTouchListener(new PhotoTouchListener(mTable.getContext(), mTable));
+ photo.setOnTouchListener(new PhotoTouchListener(Table.this.getContext(),
+ Table.this));
}
return photo;
@@ -254,19 +249,23 @@ public class Table extends FrameLayout {
@Override
public void onPostExecute(View photo) {
if (photo != null) {
- mTable.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
+ Table.this.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
- if (mTable.hasSelection()) {
- mTable.bringChildToFront(mTable.getSelected());
+ if (Table.this.hasSelection()) {
+ Table.this.bringChildToFront(Table.this.getSelected());
}
int width = ((Integer) photo.getTag(R.id.photo_width)).intValue();
int height = ((Integer) photo.getTag(R.id.photo_height)).intValue();
log("drop it");
- mTable.throwOnTable(photo);
- }
- if(mTable.mOnTable.size() < mTable.mTableCapacity) {
- mTable.scheduleNext(mTable.mFastDropPeriod);
+ Table.this.throwOnTable(photo);
+
+ if(Table.this.mOnTable.size() < Table.this.mTableCapacity) {
+ Table.this.scheduleNext(Table.this.mFastDropPeriod);
+ }
+ } else if (mTries < 3) {
+ mTries++;
+ this.execute();
}
}
};
@@ -282,7 +281,7 @@ public class Table extends FrameLayout {
log("inflate it");
if (mPhotoLaunchTask == null ||
mPhotoLaunchTask.getStatus() == AsyncTask.Status.FINISHED) {
- mPhotoLaunchTask = new PhotoLaunchTask(this);
+ mPhotoLaunchTask = new PhotoLaunchTask();
mPhotoLaunchTask.execute();
}
}