summaryrefslogtreecommitdiffstats
path: root/src/com/android/dreams/phototable/PhotoTable.java
diff options
context:
space:
mode:
authorChris Wren <cwren@android.com>2012-09-06 16:46:57 -0400
committerChris Wren <cwren@android.com>2012-09-07 17:41:42 -0400
commitd85f53c69dead1f1f6c0290b8104422143bc5166 (patch)
tree2a8d5cec293a0b55937a0a8296ed995f2af39bb8 /src/com/android/dreams/phototable/PhotoTable.java
parent83fee9012b6d5c5940de5b96fe8d98653ba14c0d (diff)
downloadandroid_packages_screensavers_PhotoTable-d85f53c69dead1f1f6c0290b8104422143bc5166.tar.gz
android_packages_screensavers_PhotoTable-d85f53c69dead1f1f6c0290b8104422143bc5166.tar.bz2
android_packages_screensavers_PhotoTable-d85f53c69dead1f1f6c0290b8104422143bc5166.zip
Add ability to select the albums to display.
Change-Id: I80ff33c4c880c445b79735d6483bc9337a89e392
Diffstat (limited to 'src/com/android/dreams/phototable/PhotoTable.java')
-rw-r--r--src/com/android/dreams/phototable/PhotoTable.java428
1 files changed, 417 insertions, 11 deletions
diff --git a/src/com/android/dreams/phototable/PhotoTable.java b/src/com/android/dreams/phototable/PhotoTable.java
index 49c4b92..7e6faea 100644
--- a/src/com/android/dreams/phototable/PhotoTable.java
+++ b/src/com/android/dreams/phototable/PhotoTable.java
@@ -16,25 +16,431 @@
package com.android.dreams.phototable;
import android.service.dreams.Dream;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.PointF;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.AsyncTask;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
+
+import java.util.LinkedList;
+import java.util.Random;
/**
- * Example interactive screen saver: flick photos onto a table.
+ * A surface where photos sit.
*/
-public class PhotoTable extends Dream {
+public class PhotoTable extends FrameLayout {
private static final String TAG = "PhotoTable";
- private Table mTable;
+ private static final boolean DEBUG = false;
+
+ class Launcher implements Runnable {
+ private final PhotoTable mTable;
+ public Launcher(PhotoTable table) {
+ mTable = table;
+ }
+
+ @Override
+ public void run() {
+ mTable.scheduleNext(mDropPeriod);
+ mTable.launch();
+ }
+ }
+
+ private static final long MAX_SELECTION_TIME = 10000L;
+ private static Random sRNG = new Random();
+
+ private final Launcher mLauncher;
+ private final LinkedList<View> mOnTable;
+ private final Dream mDream;
+ private final int mDropPeriod;
+ private final int mFastDropPeriod;
+ private final int mNowDropDelay;
+ private final float mImageRatio;
+ private final float mTableRatio;
+ private final float mImageRotationLimit;
+ private final boolean mTapToExit;
+ private final int mTableCapacity;
+ private final int mInset;
+ private final PhotoSourcePlexor mPhotoSource;
+ private final Resources mResources;
+ private PhotoLaunchTask mPhotoLaunchTask;
+ private boolean mStarted;
+ private boolean mIsLandscape;
+ private BitmapFactory.Options mOptions;
+ private int mLongSide;
+ private int mShortSide;
+ private int mWidth;
+ private int mHeight;
+ private View mSelected;
+ private long mSelectedTime;
+
+ public PhotoTable(Dream dream, AttributeSet as) {
+ super(dream, as);
+ mDream = dream;
+ mResources = getResources();
+ setBackground(mResources.getDrawable(R.drawable.table));
+ mInset = mResources.getDimensionPixelSize(R.dimen.photo_inset);
+ mDropPeriod = mResources.getInteger(R.integer.drop_period);
+ mFastDropPeriod = mResources.getInteger(R.integer.fast_drop);
+ mNowDropDelay = mResources.getInteger(R.integer.now_drop);
+ mImageRatio = mResources.getInteger(R.integer.image_ratio) / 1000000f;
+ mTableRatio = mResources.getInteger(R.integer.table_ratio) / 1000000f;
+ mImageRotationLimit = (float) mResources.getInteger(R.integer.max_image_rotation);
+ mTableCapacity = mResources.getInteger(R.integer.table_capacity);
+ mTapToExit = mResources.getBoolean(R.bool.enable_tap_to_exit);
+ mOnTable = new LinkedList<View>();
+ mOptions = new BitmapFactory.Options();
+ mOptions.inTempStorage = new byte[32768];
+ mPhotoSource = new PhotoSourcePlexor(getContext(),
+ getContext().getSharedPreferences(PhotoTableDreamSettings.PREFS_NAME, 0));
+ mLauncher = new Launcher(this);
+ mStarted = false;
+ }
+
+ public boolean hasSelection() {
+ return mSelected != null;
+ }
+
+ public View getSelected() {
+ return mSelected;
+ }
+
+ public void clearSelection() {
+ mSelected = null;
+ }
+
+ public void setSelection(View selected) {
+ assert(selected != null);
+ if (mSelected != null) {
+ dropOnTable(mSelected);
+ }
+ mSelected = selected;
+ mSelectedTime = System.currentTimeMillis();
+ bringChildToFront(selected);
+ pickUp(selected);
+ }
+
+ static float lerp(float a, float b, float f) {
+ return (b-a)*f + a;
+ }
+
+ static float randfrange(float a, float b) {
+ return lerp(a, b, sRNG.nextFloat());
+ }
+
+ static PointF randFromCurve(float t, PointF[] v) {
+ PointF p = new PointF();
+ if (v.length == 4 && t >= 0f && t <= 1f) {
+ float a = (float) Math.pow(1f-t, 3f);
+ float b = (float) Math.pow(1f-t, 2f) * t;
+ float c = (1f-t) * (float) Math.pow(t, 2f);
+ float d = (float) Math.pow(t, 3f);
+
+ p.x = a * v[0].x + 3 * b * v[1].x + 3 * c * v[2].x + d * v[3].x;
+ p.y = a * v[0].y + 3 * b * v[1].y + 3 * c * v[2].y + d * v[3].y;
+ }
+ return p;
+ }
+
+ private static PointF randInCenter(float i, float j, int width, int height) {
+ log("randInCenter (" + i + ", " + j + ", " + width + ", " + height + ")");
+ PointF p = new PointF();
+ p.x = 0.5f * width + 0.15f * width * i;
+ p.y = 0.5f * height + 0.15f * height * j;
+ log("randInCenter returning " + p.x + "," + p.y);
+ return p;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ if (hasSelection()) {
+ dropOnTable(getSelected());
+ clearSelection();
+ } else {
+ if (mTapToExit) {
+ mDream.finish();
+ }
+ }
+ return true;
+ }
+ return false;
+ }
@Override
- public void onStart() {
- super.onStart();
- setInteractive(true);
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ log("onLayout (" + left + ", " + top + ", " + right + ", " + bottom + ")");
+
+ mHeight = bottom - top;
+ mWidth = right - left;
+
+ mLongSide = (int) (mImageRatio * Math.max(mWidth, mHeight));
+ mShortSide = (int) (mImageRatio * Math.min(mWidth, mHeight));
+
+ boolean isLandscape = mWidth > mHeight;
+ if (mIsLandscape != isLandscape) {
+ for (View photo: mOnTable) {
+ if (photo == getSelected()) {
+ pickUp(photo);
+ } else {
+ dropOnTable(photo);
+ }
+ }
+ mIsLandscape = isLandscape;
+ }
+ start();
}
@Override
- public void onAttachedToWindow() {
- super.onAttachedToWindow();
- mTable = new Table(this, null);
- setContentView(mTable);
- lightsOut();
+ public boolean isOpaque() {
+ return true;
+ }
+
+ private class PhotoLaunchTask extends AsyncTask<Void, Void, View> {
+ @Override
+ public View doInBackground(Void... unused) {
+ log("load a new photo");
+ final PhotoTable table = PhotoTable.this;
+
+ LayoutInflater inflater = (LayoutInflater) table.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 = table.mPhotoSource.next(table.mOptions,
+ table.mLongSide, table.mShortSide);
+ int photoWidth = table.mOptions.outWidth;
+ int photoHeight = table.mOptions.outHeight;
+ if (table.mOptions.outWidth <= 0 || table.mOptions.outHeight <= 0) {
+ photo = null;
+ } else {
+ layers[0] = new BitmapDrawable(table.mResources, decodedPhoto);
+ layers[1] = table.mResources.getDrawable(R.drawable.frame);
+ LayerDrawable layerList = new LayerDrawable(layers);
+ layerList.setLayerInset(0, table.mInset, table.mInset,
+ table.mInset, table.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(table.getContext(),
+ table));
+ }
+
+ return photo;
+ }
+
+ @Override
+ public void onPostExecute(View photo) {
+ if (photo != null) {
+ final PhotoTable table = PhotoTable.this;
+
+ table.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+ if (table.hasSelection()) {
+ table.bringChildToFront(table.getSelected());
+ }
+ int width = ((Integer) photo.getTag(R.id.photo_width)).intValue();
+ int height = ((Integer) photo.getTag(R.id.photo_height)).intValue();
+
+ log("drop it");
+ table.throwOnTable(photo);
+
+ if(table.mOnTable.size() < table.mTableCapacity) {
+ table.scheduleNext(table.mFastDropPeriod);
+ }
+ }
+ }
+ };
+
+ public void launch() {
+ log("launching");
+ setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
+ if (hasSelection() &&
+ (System.currentTimeMillis() - mSelectedTime) > MAX_SELECTION_TIME) {
+ dropOnTable(getSelected());
+ clearSelection();
+ } else {
+ log("inflate it");
+ if (mPhotoLaunchTask == null ||
+ mPhotoLaunchTask.getStatus() == AsyncTask.Status.FINISHED) {
+ mPhotoLaunchTask = new PhotoLaunchTask();
+ mPhotoLaunchTask.execute();
+ }
+ }
+ }
+ public void fadeAway(final View photo, final boolean replace) {
+ // fade out of view
+ mOnTable.remove(photo);
+ photo.animate().cancel();
+ photo.animate()
+ .withLayer()
+ .alpha(0f)
+ .setDuration(1000)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ removeView(photo);
+ recycle(photo);
+ if (replace) {
+ scheduleNext(mNowDropDelay);
+ }
+ }
+ });
+ }
+
+ public void moveToBackOfQueue(View photo) {
+ // make this photo the last to be removed.
+ bringChildToFront(photo);
+ invalidate();
+ mOnTable.remove(photo);
+ mOnTable.offer(photo);
+ }
+
+ private void throwOnTable(final View photo) {
+ mOnTable.offer(photo);
+ log("start offscreen");
+ int width = ((Integer) photo.getTag(R.id.photo_width));
+ int height = ((Integer) photo.getTag(R.id.photo_height));
+ photo.setRotation(-100.0f);
+ photo.setX(-mLongSide);
+ photo.setY(-mLongSide);
+ dropOnTable(photo);
+ }
+
+ public void dropOnTable(final View photo) {
+ float angle = randfrange(-mImageRotationLimit, mImageRotationLimit);
+ PointF p = randInCenter((float) sRNG.nextGaussian(), (float) sRNG.nextGaussian(),
+ mWidth, mHeight);
+ float x = p.x;
+ float y = p.y;
+
+ log("drop it at " + x + ", " + y);
+
+ float x0 = photo.getX();
+ float y0 = photo.getY();
+ float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue();
+ float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue();
+
+ x -= mTableRatio * mLongSide / 2f;
+ y -= mTableRatio * mLongSide / 2f;
+ log("fixed offset is " + x + ", " + y);
+
+ float dx = x - x0;
+ float dy = y - y0;
+
+ float dist = (float) (Math.sqrt(dx * dx + dy * dy));
+ int duration = (int) (1000f * dist / 400f);
+ duration = Math.max(duration, 1000);
+
+ log("animate it");
+ // toss onto table
+ photo.animate()
+ .withLayer()
+ .scaleX(mTableRatio / mImageRatio)
+ .scaleY(mTableRatio / mImageRatio)
+ .rotation(angle)
+ .x(x)
+ .y(y)
+ .setDuration(duration)
+ .setInterpolator(new DecelerateInterpolator(3f))
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ while (mOnTable.size() > mTableCapacity) {
+ fadeAway(mOnTable.poll(), false);
+ }
+ }
+ });
+ }
+
+ /** wrap all orientations to the interval [-180, 180). */
+ private float wrapAngle(float angle) {
+ float result = angle + 180;
+ result = ((result % 360) + 360) % 360; // catch negative numbers
+ result -= 180;
+ return result;
+ }
+
+ private void pickUp(final View photo) {
+ float photoWidth = photo.getWidth();
+ float photoHeight = photo.getHeight();
+
+ float scale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth);
+
+ log("target it");
+ float x = (getWidth() - photoWidth) / 2f;
+ float y = (getHeight() - photoHeight) / 2f;
+
+ float x0 = photo.getX();
+ float y0 = photo.getY();
+ float dx = x - x0;
+ float dy = y - y0;
+
+ float dist = (float) (Math.sqrt(dx * dx + dy * dy));
+ int duration = (int) (1000f * dist / 1000f);
+ duration = Math.max(duration, 500);
+
+ photo.setRotation(wrapAngle(photo.getRotation()));
+
+ log("animate it");
+ // toss onto table
+ photo.animate()
+ .withLayer()
+ .rotation(0f)
+ .scaleX(scale)
+ .scaleY(scale)
+ .x(x)
+ .y(y)
+ .setDuration(duration)
+ .setInterpolator(new DecelerateInterpolator(2f))
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ log("endtimes: " + photo.getX());
+ }
+ });
+ }
+
+ private void recycle(View photo) {
+ ImageView image = (ImageView) photo;
+ LayerDrawable layers = (LayerDrawable) image.getDrawable();
+ BitmapDrawable bitmap = (BitmapDrawable) layers.getDrawable(0);
+ bitmap.getBitmap().recycle();
+ }
+
+ public void start() {
+ if (!mStarted) {
+ log("kick it");
+ mStarted = true;
+ scheduleNext(mDropPeriod);
+ launch();
+ }
+ }
+
+ public void scheduleNext(int delay) {
+ removeCallbacks(mLauncher);
+ postDelayed(mLauncher, delay);
+ }
+
+ private static void log(String message) {
+ if (DEBUG) {
+ Log.i(TAG, message);
+ }
}
}