am 88d80f44: story mode for PhotoTable. also some cleanup and refactoring also fix stuck alphas
* commit '88d80f4471c900628e2cb6eef23029b99af48e09':
story mode for PhotoTable. also some cleanup and refactoring also fix stuck alphas
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 59a2236..c401ef7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.gallery3d.permission.PICASA_STORE" />
+ <uses-sdk android:minSdkVersion="17" android:targetSdkVersion="17"/>
<application
android:label="@string/app_name"
diff --git a/res/values/config.xml b/res/values/config.xml
index 1c9de4a..e20ac68 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -97,5 +97,8 @@
<!-- Parts per million gain applied to generalized touch gestures. -->
<integer name="generalized_touch_gain">2000000</integer>
+ <!-- Enable story mode. -->
+ <bool name="enable_story_mode">true</bool>
+
</resources>
diff --git a/src/com/android/dreams/phototable/AlbumDataAdapter.java b/src/com/android/dreams/phototable/AlbumDataAdapter.java
index 699fe14..570bbd7 100644
--- a/src/com/android/dreams/phototable/AlbumDataAdapter.java
+++ b/src/com/android/dreams/phototable/AlbumDataAdapter.java
@@ -17,7 +17,6 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.text.SpannableString;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,7 +29,6 @@
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
-import java.util.Set;
/**
* Settings panel for photo flipping dream.
diff --git a/src/com/android/dreams/phototable/AlbumSettings.java b/src/com/android/dreams/phototable/AlbumSettings.java
index 069948b..1ccd498 100644
--- a/src/com/android/dreams/phototable/AlbumSettings.java
+++ b/src/com/android/dreams/phototable/AlbumSettings.java
@@ -51,7 +51,6 @@
public boolean isAlbumEnabled(String albumId) {
synchronized (mEnabledAlbums) {
- boolean isEnabled = mEnabledAlbums.contains(albumId);
return mEnabledAlbums.contains(albumId);
}
}
diff --git a/src/com/android/dreams/phototable/CursorPhotoSource.java b/src/com/android/dreams/phototable/CursorPhotoSource.java
new file mode 100644
index 0000000..cb4ce6b
--- /dev/null
+++ b/src/com/android/dreams/phototable/CursorPhotoSource.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2013 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.content.SharedPreferences;
+import android.database.Cursor;
+
+/**
+ * Common implementation for sources that load images from a cursor.
+ */
+public abstract class CursorPhotoSource extends PhotoSource {
+
+ // An invalid cursor position to represent the uninitialized state.
+ protected static final int UNINITIALIZED = -1;
+ // An invalid cursor position to represent the error state.
+ protected static final int INVALID = -2;
+
+ public CursorPhotoSource(Context context, SharedPreferences settings) {
+ super(context, settings);
+ }
+
+ public CursorPhotoSource(Context context, SharedPreferences settings, PhotoSource fallback) {
+ super(context, settings, fallback);
+ }
+
+ @Override
+ protected ImageData naturalNext(ImageData current) {
+ if (current.cursor == null) {
+ openCursor(current);
+ findPosition(current);
+ }
+ current.cursor.moveToPosition(current.position);
+ current.cursor.moveToNext();
+ ImageData data = null;
+ if (!current.cursor.isAfterLast()) {
+ data = unpackImageData(current.cursor, null);
+ data.cursor = current.cursor;
+ data.position = current.cursor.getPosition();
+ }
+ return data;
+ }
+
+ @Override
+ protected ImageData naturalPrevious(ImageData current) {
+ if (current.cursor == null) {
+ openCursor(current);
+ findPosition(current);
+ }
+ current.cursor.moveToPosition(current.position);
+ current.cursor.moveToPrevious();
+ ImageData data = null;
+ if (!current.cursor.isBeforeFirst()) {
+ data = unpackImageData(current.cursor, null);
+ data.cursor = current.cursor;
+ data.position = current.cursor.getPosition();
+ }
+ return data;
+ }
+
+ protected abstract void openCursor(ImageData data);
+ protected abstract void findPosition(ImageData data);
+ protected abstract ImageData unpackImageData(Cursor cursor, ImageData data);
+}
+
diff --git a/src/com/android/dreams/phototable/DragGestureDetector.java b/src/com/android/dreams/phototable/DragGestureDetector.java
index 739163d..2153c48 100644
--- a/src/com/android/dreams/phototable/DragGestureDetector.java
+++ b/src/com/android/dreams/phototable/DragGestureDetector.java
@@ -17,19 +17,18 @@
import android.content.Context;
import android.content.res.Resources;
-import android.util.Log;
import android.view.MotionEvent;
/**
* Detect and dispatch edge events.
*/
public class DragGestureDetector {
+ @SuppressWarnings("unused")
private static final String TAG = "DragGestureDetector";
private final PhotoTable mTable;
private final float mTouchGain;
- private int mPointer;
private float[] mLast;
private float[] mCurrent;
private boolean mDrag;
@@ -66,7 +65,6 @@
int index = event.getActionIndex();
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
- mPointer = event.getPointerId(index);
computeAveragePosition(event, mLast);
mDrag = false;
break;
@@ -104,7 +102,7 @@
}
private void move(MotionEvent event, boolean drop) {
- mTable.move(mTable.getFocused(),
+ mTable.move(mTable.getFocus(),
mTouchGain * (mCurrent[0] - mLast[0]),
mTouchGain * (mCurrent[1] - mLast[1]),
drop);
diff --git a/src/com/android/dreams/phototable/EdgeSwipeDetector.java b/src/com/android/dreams/phototable/EdgeSwipeDetector.java
index 98ad6c6..e5ca23d 100644
--- a/src/com/android/dreams/phototable/EdgeSwipeDetector.java
+++ b/src/com/android/dreams/phototable/EdgeSwipeDetector.java
@@ -23,6 +23,7 @@
* Detect and dispatch edge events.
*/
public class EdgeSwipeDetector {
+ @SuppressWarnings("unused")
private static final String TAG = "EdgeSwipeDetector";
private float mEdgeSwipeGutter;
private float mEdgeSwipeThreshold;
@@ -61,7 +62,7 @@
* mEdgeSwipeThreshold;
if (event.getX() > enough) {
if (mTable.hasFocus()) {
- mTable.fling(mTable.getFocused());
+ mTable.fling(mTable.getFocus());
} else if (mTable.hasSelection()) {
mTable.clearSelection();
}
diff --git a/src/com/android/dreams/phototable/FlipperDream.java b/src/com/android/dreams/phototable/FlipperDream.java
index 36d8c7b..b70c8d4 100644
--- a/src/com/android/dreams/phototable/FlipperDream.java
+++ b/src/com/android/dreams/phototable/FlipperDream.java
@@ -15,7 +15,6 @@
*/
package com.android.dreams.phototable;
-import android.content.SharedPreferences;
import android.service.dreams.DreamService;
/**
diff --git a/src/com/android/dreams/phototable/FlipperDreamSettings.java b/src/com/android/dreams/phototable/FlipperDreamSettings.java
index 50e5f1e..87802c6 100644
--- a/src/com/android/dreams/phototable/FlipperDreamSettings.java
+++ b/src/com/android/dreams/phototable/FlipperDreamSettings.java
@@ -15,16 +15,15 @@
*/
package com.android.dreams.phototable;
+import android.app.ListActivity;
import android.content.SharedPreferences;
import android.database.DataSetObserver;
-import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.widget.ListAdapter;
import java.util.LinkedList;
@@ -32,6 +31,7 @@
* Settings panel for photo flipping dream.
*/
public class FlipperDreamSettings extends ListActivity {
+ @SuppressWarnings("unused")
private static final String TAG = "FlipperDreamSettings";
public static final String PREFS_NAME = FlipperDream.TAG;
diff --git a/src/com/android/dreams/phototable/KeyboardInterpreter.java b/src/com/android/dreams/phototable/KeyboardInterpreter.java
new file mode 100644
index 0000000..aa316cb
--- /dev/null
+++ b/src/com/android/dreams/phototable/KeyboardInterpreter.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2013 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.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+
+/**
+ * Keyboard event dispatcher for Photo Table.
+ */
+public class KeyboardInterpreter {
+ private static final String TAG = "DPadInterpreter";
+ private static final boolean DEBUG = false;
+
+ private final PhotoTable mTable;
+
+ public KeyboardInterpreter(PhotoTable table) {
+ mTable = table;
+ }
+
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ final View focus = mTable.getFocus();
+ boolean consumed = true;
+
+ if (mTable.hasSelection()) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ESCAPE:
+ mTable.setFocus(mTable.getSelection());
+ mTable.clearSelection();
+ break;
+ default:
+ if (DEBUG) Log.d(TAG, "dropped unexpected: " + keyCode);
+ consumed = false;
+ break;
+ }
+ } else {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_ENTER:
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (mTable.hasFocus()) {
+ mTable.setSelection(mTable.getFocus());
+ mTable.clearFocus();
+ } else {
+ mTable.setDefaultFocus();
+ }
+ break;
+
+ case KeyEvent.KEYCODE_DEL:
+ case KeyEvent.KEYCODE_X:
+ if (mTable.hasFocus()) {
+ mTable.fling(mTable.getFocus());
+ }
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_K:
+ mTable.moveFocus(focus, 0f);
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_L:
+ mTable.moveFocus(focus, 90f);
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ case KeyEvent.KEYCODE_J:
+ mTable.moveFocus(focus, 180f);
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_H:
+ mTable.moveFocus(focus, 270f);
+ break;
+
+ default:
+ if (DEBUG) Log.d(TAG, "dropped unexpected: " + keyCode);
+ consumed = false;
+ break;
+ }
+ }
+
+ return consumed;
+ }
+}
diff --git a/src/com/android/dreams/phototable/LocalSource.java b/src/com/android/dreams/phototable/LocalSource.java
index 4efc43b..cf2e0ec 100644
--- a/src/com/android/dreams/phototable/LocalSource.java
+++ b/src/com/android/dreams/phototable/LocalSource.java
@@ -30,7 +30,7 @@
/**
* Loads images from the local store.
*/
-public class LocalSource extends PhotoSource {
+public class LocalSource extends CursorPhotoSource {
private static final String TAG = "PhotoTable.LocalSource";
private final String mUnknownAlbumName;
@@ -113,6 +113,59 @@
}
@Override
+ protected void openCursor(ImageData data) {
+ log(TAG, "opening single album");
+
+ String[] projection = {MediaStore.Images.Media.DATA, MediaStore.Images.Media.ORIENTATION,
+ MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME};
+ String selection = MediaStore.Images.Media.BUCKET_ID + " = '" + data.albumId + "'";
+
+ data.cursor = mResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ projection, selection, null, null);
+ }
+
+ @Override
+ protected void findPosition(ImageData data) {
+ if (data.position == -1) {
+ if (data.cursor == null) {
+ openCursor(data);
+ }
+ if (data.cursor != null) {
+ int dataIndex = data.cursor.getColumnIndex(MediaStore.Images.Media.DATA);
+ data.cursor.moveToPosition(-1);
+ while (data.position == -1 && data.cursor.moveToNext()) {
+ String url = data.cursor.getString(dataIndex);
+ if (url != null && url.equals(data.url)) {
+ data.position = data.cursor.getPosition();
+ }
+ }
+ if (data.position == -1) {
+ // oops! The image isn't in this album. How did we get here?
+ data.position = INVALID;
+ }
+ }
+ }
+ }
+
+ @Override
+ protected ImageData unpackImageData(Cursor cursor, ImageData data) {
+ if (data == null) {
+ data = new ImageData();
+ }
+ 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);
+
+ data.url = cursor.getString(dataIndex);
+ data.albumId = cursor.getString(bucketIndex);
+ data.position = UNINITIALIZED;
+ data.cursor = null;
+ data.orientation = cursor.getInt(orientationIndex);
+
+ return data;
+ }
+
+ @Override
protected Collection<ImageData> findImages(int howMany) {
log(TAG, "finding images");
LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
@@ -138,23 +191,18 @@
Cursor cursor = mResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, selection, null, null);
if (cursor != null) {
+ int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
+
if (cursor.getCount() > howMany && mLastPosition == INVALID) {
mLastPosition = pickRandomStart(cursor.getCount(), howMany);
}
cursor.moveToPosition(mLastPosition);
- 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(TAG, "can't find the DATA column!");
} else {
while (foundImages.size() < howMany && cursor.moveToNext()) {
- ImageData data = new ImageData();
- data.url = cursor.getString(dataIndex);
- data.orientation = cursor.getInt(orientationIndex);
+ ImageData data = unpackImageData(cursor, null);
foundImages.offer(data);
mLastPosition = cursor.getPosition();
}
@@ -186,3 +234,4 @@
return (InputStream) fis;
}
}
+
diff --git a/src/com/android/dreams/phototable/PhotoCarousel.java b/src/com/android/dreams/phototable/PhotoCarousel.java
index 536d76b..9e77d06 100644
--- a/src/com/android/dreams/phototable/PhotoCarousel.java
+++ b/src/com/android/dreams/phototable/PhotoCarousel.java
@@ -20,7 +20,6 @@
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
-import android.service.dreams.DreamService;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
@@ -191,9 +190,9 @@
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));
+ destination.setTag(R.id.photo_orientation, Integer.valueOf(orientation));
+ destination.setTag(R.id.photo_width, Integer.valueOf(width));
+ destination.setTag(R.id.photo_height, Integer.valueOf(height));
setScaleType(destination);
mBitmapStore.put(destination, photo);
@@ -249,8 +248,8 @@
frontA = 1f - frontA;
backA = 1f - backA;
- // Don't rotate
- frontY = backY = 0f;
+ // Don't rotate
+ frontY = backY = 0f;
ViewPropertyAnimator frontAnim = mPanel[0].animate()
.rotationY(frontY)
@@ -286,7 +285,6 @@
mOrientation = (mWidth > mHeight ? LANDSCAPE : PORTRAIT);
- boolean init = mLongSide == 0;
mLongSide = (int) Math.max(mWidth, mHeight);
mShortSide = (int) Math.min(mWidth, mHeight);
diff --git a/src/com/android/dreams/phototable/PhotoSource.java b/src/com/android/dreams/phototable/PhotoSource.java
index 32d41c7..d9d4ab9 100644
--- a/src/com/android/dreams/phototable/PhotoSource.java
+++ b/src/com/android/dreams/phototable/PhotoSource.java
@@ -23,16 +23,15 @@
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.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.Random;
@@ -43,9 +42,6 @@
private static final String TAG = "PhotoTable.PhotoSource";
private static final boolean DEBUG = false;
- // An invalid cursor position to represent the uninitialized state.
- protected static final int INVALID = -2;
-
// 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;
@@ -55,9 +51,19 @@
public String url;
public int orientation;
+ protected String albumId;
+ protected Cursor cursor;
+ protected int position;
+
InputStream getStream(int longSide) {
return PhotoSource.this.getStream(this, longSide);
}
+ ImageData naturalNext() {
+ return PhotoSource.this.naturalNext(this);
+ }
+ ImageData naturalPrevious() {
+ return PhotoSource.this.naturalPrevious(this);
+ }
}
public class AlbumData {
@@ -79,6 +85,7 @@
private final float mMaxCropRatio;
private final int mBadImageSkipLimit;
private final PhotoSource mFallbackSource;
+ private final HashMap<Bitmap, ImageData> mImageMap;
protected final Context mContext;
protected final Resources mResources;
@@ -102,6 +109,7 @@
mMaxQueueSize = mResources.getInteger(R.integer.image_queue_size);
mMaxCropRatio = mResources.getInteger(R.integer.max_crop_ratio) / 1000000f;
mBadImageSkipLimit = mResources.getInteger(R.integer.bad_image_skip_limit);
+ mImageMap = new HashMap<Bitmap, ImageData>();
mRNG = new Random();
mFallbackSource = fallbackSource;
}
@@ -124,11 +132,11 @@
if (mImageQueue.isEmpty()) {
fillQueue();
}
-
imageData = mImageQueue.poll();
}
if (imageData != null) {
image = load(imageData, options, longSide, shortSide);
+ mImageMap.put(image, imageData);
imageData = null;
}
@@ -259,7 +267,38 @@
}
}
+ public Bitmap naturalNext(Bitmap current, BitmapFactory.Options options,
+ int longSide, int shortSide) {
+ Bitmap image = null;
+ ImageData data = mImageMap.get(current);
+ if (data != null) {
+ ImageData next = data.naturalNext();
+ if (next != null) {
+ image = load(next, options, longSide, shortSide);
+ mImageMap.put(image, next);
+ }
+ }
+ return image;
+ }
+
+ public Bitmap naturalPrevious(Bitmap current, BitmapFactory.Options options,
+ int longSide, int shortSide) {
+ Bitmap image = null;
+ ImageData data = mImageMap.get(current);
+ if (current != null) {
+ ImageData prev = data.naturalPrevious();
+ if (prev != null) {
+ image = load(prev, options, longSide, shortSide);
+ mImageMap.put(image, prev);
+ }
+ }
+ return image;
+ }
+
protected abstract InputStream getStream(ImageData data, int longSide);
protected abstract Collection<ImageData> findImages(int howMany);
+ protected abstract ImageData naturalNext(ImageData current);
+ protected abstract ImageData naturalPrevious(ImageData current);
+
public abstract Collection<AlbumData> findAlbums();
}
diff --git a/src/com/android/dreams/phototable/PhotoSourcePlexor.java b/src/com/android/dreams/phototable/PhotoSourcePlexor.java
index 147f16e..93fdc9e 100644
--- a/src/com/android/dreams/phototable/PhotoSourcePlexor.java
+++ b/src/com/android/dreams/phototable/PhotoSourcePlexor.java
@@ -17,12 +17,8 @@
import android.content.Context;
import android.content.SharedPreferences;
-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;
@@ -34,7 +30,6 @@
private final PhotoSource mPicasaSource;
private final PhotoSource mLocalSource;
- private SharedPreferences mSettings;
public PhotoSourcePlexor(Context context, SharedPreferences settings) {
super(context, settings);
@@ -75,4 +70,14 @@
protected InputStream getStream(ImageData data, int longSide) {
return data.getStream(longSide);
}
+
+ @Override
+ protected ImageData naturalNext(ImageData current) {
+ return current.naturalNext();
+ }
+
+ @Override
+ protected ImageData naturalPrevious(ImageData current) {
+ return current.naturalPrevious();
+ }
}
diff --git a/src/com/android/dreams/phototable/PhotoTable.java b/src/com/android/dreams/phototable/PhotoTable.java
index 4cf278c..7d2f6b6 100644
--- a/src/com/android/dreams/phototable/PhotoTable.java
+++ b/src/com/android/dreams/phototable/PhotoTable.java
@@ -15,13 +15,10 @@
*/
package com.android.dreams.phototable;
-import android.service.dreams.DreamService;
import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
-import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.Rect;
@@ -29,6 +26,7 @@
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.AsyncTask;
+import android.service.dreams.DreamService;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -39,8 +37,9 @@
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageView;
+
+import java.util.Formatter;
import java.util.LinkedList;
import java.util.Random;
@@ -68,8 +67,8 @@
private static final int MAX_SELECTION_TIME = 10000;
private static final int MAX_FOCUS_TIME = 5000;
- private static final float EDGE_SWIPE_GUTTER = 0.05f;
- private static final float EDGE_SWIPE_THRESHOLD = 0.25f;
+ private static final int NEXT = 1;
+ private static final int PREV = 0;
private static Random sRNG = new Random();
private final Launcher mLauncher;
@@ -91,20 +90,23 @@
private final Resources mResources;
private final Interpolator mThrowInterpolator;
private final Interpolator mDropInterpolator;
- final private EdgeSwipeDetector mEdgeSwipeDetector;
- final private DragGestureDetector mDragGestureDetector;
+ private final DragGestureDetector mDragGestureDetector;
+ private final EdgeSwipeDetector mEdgeSwipeDetector;
+ private final KeyboardInterpreter mKeyboardInterpreter;
+ private final boolean mStoryModeEnabled;
private DreamService mDream;
private PhotoLaunchTask mPhotoLaunchTask;
+ private LoadNaturalSiblingTask mLoadOnDeckTasks[];
private boolean mStarted;
private boolean mIsLandscape;
private int mLongSide;
private int mShortSide;
private int mWidth;
private int mHeight;
- private View mSelected;
- private long mSelectedTime;
- private View mFocused;
- private long mFocusedTime;
+ private View mSelection;
+ private View mOnDeck[];
+ private long mSelectionTime;
+ private View mFocus;
private int mHighlightColor;
public PhotoTable(Context context, AttributeSet as) {
@@ -122,6 +124,7 @@
mTableCapacity = mResources.getInteger(R.integer.table_capacity);
mRedealCount = mResources.getInteger(R.integer.redeal_count);
mTapToExit = mResources.getBoolean(R.bool.enable_tap_to_exit);
+ mStoryModeEnabled = mResources.getBoolean(R.bool.enable_story_mode);
mHighlightColor = mResources.getColor(R.color.highlight_color);
mThrowInterpolator = new SoftLandingInterpolator(
mResources.getInteger(R.integer.soft_landing_time) / 1000000f,
@@ -133,8 +136,11 @@
getContext().getSharedPreferences(PhotoTableDreamSettings.PREFS_NAME, 0));
mLauncher = new Launcher();
mFocusReaper = new FocusReaper();
- mEdgeSwipeDetector = new EdgeSwipeDetector(context, this);
mDragGestureDetector = new DragGestureDetector(context, this);
+ mEdgeSwipeDetector = new EdgeSwipeDetector(context, this);
+ mKeyboardInterpreter = new KeyboardInterpreter(this);
+ mLoadOnDeckTasks = new LoadNaturalSiblingTask[2];
+ mOnDeck = new View[2];
mStarted = false;
}
@@ -144,49 +150,107 @@
}
public boolean hasSelection() {
- return mSelected != null;
+ return mSelection != null;
}
- public View getSelected() {
- return mSelected;
+ public View getSelection() {
+ return mSelection;
}
public void clearSelection() {
if (hasSelection()) {
- dropOnTable(getSelected());
+ dropOnTable(getSelection());
}
- mSelected = null;
+ for (int slot = 0; slot < mOnDeck.length; slot++) {
+ if (mOnDeck[slot] != null) {
+ fadeAway(mOnDeck[slot], false);
+ mOnDeck[slot] = null;
+ }
+ }
+ mSelection = null;
}
public void setSelection(View selected) {
- assert(selected != null);
- clearSelection();
- mSelected = selected;
- mSelectedTime = System.currentTimeMillis();
- moveToTopOfPile(selected);
- pickUp(selected);
+ if (selected != null) {
+ clearSelection();
+ mSelection = selected;
+ promoteSelection();
+ }
+ }
+
+ public void selectNext() {
+ if (mStoryModeEnabled) {
+ log("selectNext");
+ if (hasSelection() && mOnDeck[NEXT] != null) {
+ placeOnDeck(mSelection, PREV);
+ mSelection = mOnDeck[NEXT];
+ mOnDeck[NEXT] = null;
+ promoteSelection();
+ }
+ } else {
+ clearSelection();
+ }
+ }
+
+ public void selectPrevious() {
+ if (mStoryModeEnabled) {
+ log("selectPrevious");
+ if (hasSelection() && mOnDeck[PREV] != null) {
+ placeOnDeck(mSelection, NEXT);
+ mSelection = mOnDeck[PREV];
+ mOnDeck[PREV] = null;
+ promoteSelection();
+ }
+ } else {
+ clearSelection();
+ }
+ }
+
+ private void promoteSelection() {
+ if (hasSelection()) {
+ mSelectionTime = System.currentTimeMillis();
+ mSelection.animate().cancel();
+ mSelection.setAlpha(1f);
+ moveToTopOfPile(mSelection);
+ pickUp(mSelection);
+ if (mStoryModeEnabled) {
+ for (int slot = 0; slot < mOnDeck.length; slot++) {
+ if (mLoadOnDeckTasks[slot] != null &&
+ mLoadOnDeckTasks[slot].getStatus() != AsyncTask.Status.FINISHED) {
+ mLoadOnDeckTasks[slot].cancel(true);
+ }
+ if (mOnDeck[slot] == null) {
+ mLoadOnDeckTasks[slot] = new LoadNaturalSiblingTask(slot);
+ mLoadOnDeckTasks[slot].execute(mSelection);
+ }
+ }
+ }
+ }
}
public boolean hasFocus() {
- return mFocused != null;
+ return mFocus != null;
}
- public View getFocused() {
- return mFocused;
+ public View getFocus() {
+ return mFocus;
}
public void clearFocus() {
if (hasFocus()) {
- setHighlight(getFocused(), false);
+ setHighlight(getFocus(), false);
}
- mFocused = null;
+ mFocus = null;
+ }
+
+ public void setDefaultFocus() {
+ setFocus(mOnTable.getLast());
}
public void setFocus(View focus) {
assert(focus != null);
clearFocus();
- mFocused = focus;
- mFocusedTime = System.currentTimeMillis();
+ mFocus = focus;
moveToTopOfPile(focus);
setHighlight(focus, true);
scheduleFocusReaper(MAX_FOCUS_TIME);
@@ -214,17 +278,8 @@
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;
- }
-
private static PointF randMultiDrop(int n, float i, float j, int width, int height) {
- log("randMultiDrop (" + n + "," + i + ", " + j + ", " + width + ", " + height + ")");
+ log("randMultiDrop (%d, %f, %f, %d, %d)", n, i, j, width, height);
final float[] cx = {0.3f, 0.3f, 0.5f, 0.7f, 0.7f};
final float[] cy = {0.3f, 0.7f, 0.5f, 0.3f, 0.7f};
n = Math.abs(n);
@@ -233,7 +288,7 @@
PointF p = new PointF();
p.x = x * width + 0.05f * width * i;
p.y = y * height + 0.05f * height * j;
- log("randInCenter returning " + p.x + "," + p.y);
+ log("randInCenter returning %f, %f", p.x, p.y);
return p;
}
@@ -241,10 +296,6 @@
return a[0] * b[1] - a[1] * b[0];
}
- private double dot(double[] a, double[] b) {
- return a[0] * b[0] + a[1] * b[1];
- }
-
private double norm(double[] a) {
return Math.hypot(a[0], a[1]);
}
@@ -296,74 +347,12 @@
setFocus(bestFocus);
}
}
- return getFocused();
+ return getFocus();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
- final View focus = getFocused();
- boolean consumed = true;
-
- if (hasSelection()) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_ENTER:
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_ESCAPE:
- setFocus(getSelected());
- clearSelection();
- break;
- default:
- log("dropped unexpected: " + keyCode);
- consumed = false;
- break;
- }
- } else {
- switch (keyCode) {
- case KeyEvent.KEYCODE_ENTER:
- case KeyEvent.KEYCODE_DPAD_CENTER:
- if (hasFocus()) {
- setSelection(getFocused());
- clearFocus();
- } else {
- setFocus(mOnTable.getLast());
- }
- break;
-
- case KeyEvent.KEYCODE_DEL:
- case KeyEvent.KEYCODE_X:
- if (hasFocus()) {
- fling(getFocused());
- }
- break;
-
- case KeyEvent.KEYCODE_DPAD_UP:
- case KeyEvent.KEYCODE_K:
- moveFocus(focus, 0f);
- break;
-
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- case KeyEvent.KEYCODE_L:
- moveFocus(focus, 90f);
- break;
-
- case KeyEvent.KEYCODE_DPAD_DOWN:
- case KeyEvent.KEYCODE_J:
- moveFocus(focus, 180f);
- break;
-
- case KeyEvent.KEYCODE_DPAD_LEFT:
- case KeyEvent.KEYCODE_H:
- moveFocus(focus, 270f);
- break;
-
- default:
- log("dropped unexpected: " + keyCode);
- consumed = false;
- break;
- }
- }
-
- return consumed;
+ return mKeyboardInterpreter.onKeyDown(keyCode, event);
}
@Override
@@ -389,7 +378,7 @@
@Override
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 + ")");
+ log("onLayout (%d, %d, %d, %d)", left, top, right, bottom);
mHeight = bottom - top;
mWidth = right - left;
@@ -400,12 +389,18 @@
boolean isLandscape = mWidth > mHeight;
if (mIsLandscape != isLandscape) {
for (View photo: mOnTable) {
- if (photo == getSelected()) {
- pickUp(photo);
- } else {
+ if (photo != getSelection()) {
dropOnTable(photo);
}
}
+ if (hasSelection()) {
+ pickUp(getSelection());
+ for (int slot = 0; slot < mOnDeck.length; slot++) {
+ if (mOnDeck[slot] != null) {
+ placeOnDeck(mOnDeck[slot], slot);
+ }
+ }
+ }
mIsLandscape = isLandscape;
}
start();
@@ -416,6 +411,86 @@
return true;
}
+ /** Put a nice border on the bitmap. */
+ private static View applyFrame(final PhotoTable table, final BitmapFactory.Options options,
+ final Bitmap decodedPhoto) {
+ 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];
+ int photoWidth = options.outWidth;
+ int photoHeight = options.outHeight;
+ if (decodedPhoto == null || options.outWidth <= 0 || options.outHeight <= 0) {
+ photo = null;
+ } else {
+ decodedPhoto.setHasMipMap(true);
+ 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, Integer.valueOf(photoWidth));
+ photo.setTag(R.id.photo_height, Integer.valueOf(photoHeight));
+
+ photo.setOnTouchListener(new PhotoTouchListener(table.getContext(),
+ table));
+ }
+ return photo;
+ }
+
+ private class LoadNaturalSiblingTask extends AsyncTask<View, Void, View> {
+ private final BitmapFactory.Options mOptions;
+ private final int mSlot;
+
+ public LoadNaturalSiblingTask (int slot) {
+ mOptions = new BitmapFactory.Options();
+ mOptions.inTempStorage = new byte[32768];
+ mSlot = slot;
+ }
+
+ @Override
+ public View doInBackground(View... views) {
+ log("load natural %s", (mSlot == NEXT ? "next" : "previous"));
+ final PhotoTable table = PhotoTable.this;
+ final Bitmap current = getBitmap(views[0]);
+ Bitmap decodedPhoto;
+ if (mSlot == NEXT) {
+ decodedPhoto = table.mPhotoSource.naturalNext(current,
+ mOptions, table.mLongSide, table.mShortSide);
+ } else {
+ decodedPhoto = table.mPhotoSource.naturalPrevious(current,
+ mOptions, table.mLongSide, table.mShortSide);
+ }
+ return applyFrame(PhotoTable.this, mOptions, decodedPhoto);
+ }
+
+ @Override
+ public void onPostExecute(View photo) {
+ if (photo != null) {
+ log("natural %s being rendered", (mSlot == NEXT ? "next" : "previous"));
+ PhotoTable.this.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+ float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue();
+ float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue();
+ photo.setX(mSlot == PREV ? -2 * width : mWidth + 2 * width);
+ photo.setY((mHeight - height) / 2);
+ photo.addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ PhotoTable.this.placeOnDeck(v, mSlot);
+ v.removeOnLayoutChangeListener(this);
+ }
+ });
+ } else {
+ log("natural, %s was null!", (mSlot == NEXT ? "next" : "previous"));
+ }
+ }
+ };
+
private class PhotoLaunchTask extends AsyncTask<Void, Void, View> {
private final BitmapFactory.Options mOptions;
@@ -428,35 +503,9 @@
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(mOptions,
- table.mLongSide, table.mShortSide);
- int photoWidth = mOptions.outWidth;
- int photoHeight = mOptions.outHeight;
- if (decodedPhoto == null || mOptions.outWidth <= 0 || mOptions.outHeight <= 0) {
- photo = null;
- } else {
- decodedPhoto.setHasMipMap(true);
- 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;
+ return applyFrame(PhotoTable.this, mOptions,
+ table.mPhotoSource.next(mOptions,
+ table.mLongSide, table.mShortSide));
}
@Override
@@ -465,12 +514,15 @@
final PhotoTable table = PhotoTable.this;
table.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT));
+ LayoutParams.WRAP_CONTENT));
if (table.hasSelection()) {
- table.moveToTopOfPile(table.getSelected());
+ for (int slot = 0; slot < mOnDeck.length; slot++) {
+ if (mOnDeck[slot] != null) {
+ table.moveToTopOfPile(mOnDeck[slot]);
+ }
+ }
+ table.moveToTopOfPile(table.getSelection());
}
- 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);
@@ -489,11 +541,12 @@
}
};
+ /** Bring a new photo onto the table. */
public void launch() {
log("launching");
- setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
+ setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
if (hasSelection() &&
- (System.currentTimeMillis() - mSelectedTime) > MAX_SELECTION_TIME) {
+ (System.currentTimeMillis() - mSelectionTime) > MAX_SELECTION_TIME) {
clearSelection();
} else {
log("inflate it");
@@ -504,12 +557,11 @@
}
}
}
+
+ /** Dispose of the photo gracefully, in case we can see some of it. */
public void fadeAway(final View photo, final boolean replace) {
// fade out of view
mOnTable.remove(photo);
- if (photo == getFocused()) {
- clearFocus();
- }
photo.animate().cancel();
photo.animate()
.withLayer()
@@ -518,7 +570,9 @@
.withEndAction(new Runnable() {
@Override
public void run() {
- removeView(photo);
+ if (photo == getFocus()) {
+ clearFocus();
+ }
recycle(photo);
if (replace) {
scheduleNext(mNowDropDelay);
@@ -527,6 +581,7 @@
});
}
+ /** Visually on top, and also freshest, for the purposes of timeouts. */
public void moveToTopOfPile(View photo) {
// make this photo the last to be removed.
bringChildToFront(photo);
@@ -535,11 +590,53 @@
mOnTable.offer(photo);
}
+ /** On deck is to the left or right of the selected photo. */
+ private void placeOnDeck(final View photo, final int slot ) {
+ if (slot < mOnDeck.length) {
+ if (mOnDeck[slot] != null && mOnDeck[slot] != photo) {
+ fadeAway(mOnDeck[slot], false);
+ }
+ mOnDeck[slot] = photo;
+ float photoWidth = photo.getWidth();
+ float photoHeight = photo.getHeight();
+ float scale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth);
+
+ float x = (getWidth() - photoWidth) / 2f;
+ float y = (getHeight() - photoHeight) / 2f;
+
+ View selected = getSelection();
+ float selectedWidth = selected.getWidth();
+ float selectedHeight = selected.getHeight();
+ float selectedScale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth);
+
+ float offset = (((float) mWidth + scale * (photoWidth - 2f * mInset)) / 2f);
+ x += (slot == NEXT? 1f : -1f) * offset;
+
+ photo.animate()
+ .rotation(0f)
+ .rotationY(0f)
+ .scaleX(scale)
+ .scaleY(scale)
+ .x(x)
+ .y(y)
+ .setDuration(1000)
+ .setInterpolator(new DecelerateInterpolator(2f));
+ }
+ }
+
+ /** Move in response to touch. */
+ public void move(final View photo, float x, float y, float a) {
+ photo.animate().cancel();
+ photo.setAlpha(1f);
+ photo.setX((int) x);
+ photo.setY((int) y);
+ photo.setRotation((int) a);
+ }
+
+ /** Wind up off screen, so we can animate in. */
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(mThrowRotation);
photo.setX(-mLongSide);
photo.setY(-mLongSide);
@@ -560,6 +657,7 @@
}
}
+ /** Fling with no touch hints, then land off screen. */
public void fling(final View photo) {
final float[] o = { mWidth + mLongSide / 2f,
mHeight + mLongSide / 2f };
@@ -580,8 +678,9 @@
fling(photo, delta[0], delta[1], duration, true);
}
+ /** Continue dynamically after a fling gesture, possibly off the screen. */
public void fling(final View photo, float dx, float dy, int duration, boolean spin) {
- if (photo == getFocused()) {
+ if (photo == getFocus()) {
if (moveFocus(photo, 0f) == null) {
moveFocus(photo, 180f);
}
@@ -618,10 +717,12 @@
hit.right < 0f || hit.left > getWidth());
}
+ /** Animate to a random place and orientation, down on the table (visually small). */
public void dropOnTable(final View photo) {
dropOnTable(photo, mDropInterpolator);
}
+ /** Animate to a random place and orientation, down on the table (visually small). */
public void dropOnTable(final View photo, final Interpolator interpolator) {
float angle = randfrange(-mImageRotationLimit, mImageRotationLimit);
PointF p = randMultiDrop(sRNG.nextInt(),
@@ -630,16 +731,14 @@
float x = p.x;
float y = p.y;
- log("drop it at " + x + ", " + y);
+ log("drop it at %f, %f", 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 -= mLongSide / 2f;
y -= mShortSide / 2f;
- log("fixed offset is " + x + ", " + y);
+ log("fixed offset is %f, %f ", x, y);
float dx = x - x0;
float dy = y - y0;
@@ -668,12 +767,14 @@
return result;
}
+ /** Animate the selected photo to the foregound: zooming in to bring it foreward. */
private void pickUp(final View photo) {
float photoWidth = photo.getWidth();
float photoHeight = photo.getHeight();
float scale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth);
+ log("scale is %f", scale);
log("target it");
float x = (getWidth() - photoWidth) / 2f;
float y = (getHeight() - photoHeight) / 2f;
@@ -690,9 +791,10 @@
photo.setRotation(wrapAngle(photo.getRotation()));
log("animate it");
- // toss onto table
+ // lift up to the glass for a good look
photo.animate()
.rotation(0f)
+ .rotationY(0f)
.scaleX(scale)
.scaleY(scale)
.x(x)
@@ -702,16 +804,35 @@
.withEndAction(new Runnable() {
@Override
public void run() {
- log("endtimes: " + photo.getX());
+ log("endtimes: %f", photo.getX());
}
});
}
- private void recycle(View photo) {
+ private Bitmap getBitmap(View photo) {
+ if (photo == null) {
+ return null;
+ }
ImageView image = (ImageView) photo;
LayerDrawable layers = (LayerDrawable) image.getDrawable();
+ if (layers == null) {
+ return null;
+ }
BitmapDrawable bitmap = (BitmapDrawable) layers.getDrawable(0);
- bitmap.getBitmap().recycle();
+ if (bitmap == null) {
+ return null;
+ }
+ return bitmap.getBitmap();
+ }
+
+ private void recycle(View photo) {
+ if (photo != null) {
+ removeView(photo);
+ Bitmap bitmap = getBitmap(photo);
+ if (bitmap != null) {
+ bitmap.recycle();
+ }
+ }
}
public void setHighlight(View photo, boolean highlighted) {
@@ -724,6 +845,7 @@
}
}
+ /** Schedule the first launch. Idempotent. */
public void start() {
if (!mStarted) {
log("kick it");
@@ -747,9 +869,11 @@
postDelayed(mLauncher, delay);
}
- private static void log(String message) {
+ private static void log(String message, Object... args) {
if (DEBUG) {
- Log.i(TAG, message);
+ Formatter formatter = new Formatter();
+ formatter.format(message, args);
+ Log.i(TAG, formatter.toString());
}
}
}
diff --git a/src/com/android/dreams/phototable/PhotoTableDream.java b/src/com/android/dreams/phototable/PhotoTableDream.java
index 37ea019..dd23be2 100644
--- a/src/com/android/dreams/phototable/PhotoTableDream.java
+++ b/src/com/android/dreams/phototable/PhotoTableDream.java
@@ -15,20 +15,14 @@
*/
package com.android.dreams.phototable;
-import android.content.Context;
-import android.content.SharedPreferences;
import android.content.res.Resources;
import android.service.dreams.DreamService;
-import java.util.Set;
-
/**
* Example interactive screen saver: flick photos onto a table.
*/
public class PhotoTableDream extends DreamService {
public static final String TAG = "PhotoTableDream";
- private PhotoTable mTable;
-
@Override
public void onDreamingStarted() {
super.onDreamingStarted();
diff --git a/src/com/android/dreams/phototable/PhotoTableDreamSettings.java b/src/com/android/dreams/phototable/PhotoTableDreamSettings.java
index a42d4a6..7ae8df3 100644
--- a/src/com/android/dreams/phototable/PhotoTableDreamSettings.java
+++ b/src/com/android/dreams/phototable/PhotoTableDreamSettings.java
@@ -15,21 +15,13 @@
*/
package com.android.dreams.phototable;
-import android.content.SharedPreferences;
-import android.app.ListActivity;
-import android.os.AsyncTask;
import android.os.Bundle;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.widget.ListAdapter;
-
-import java.util.LinkedList;
/**
* Settings panel for photo flipping dream.
*/
public class PhotoTableDreamSettings extends FlipperDreamSettings {
+ @SuppressWarnings("unused")
private static final String TAG = "PhotoTableDreamSettings";
public static final String PREFS_NAME = PhotoTableDream.TAG;
diff --git a/src/com/android/dreams/phototable/PhotoTouchListener.java b/src/com/android/dreams/phototable/PhotoTouchListener.java
index 190dc3d..8bcec6b 100644
--- a/src/com/android/dreams/phototable/PhotoTouchListener.java
+++ b/src/com/android/dreams/phototable/PhotoTouchListener.java
@@ -34,7 +34,6 @@
private final int mTapTimeout;
private final PhotoTable mTable;
private final float mBeta;
- private final float mTableRatio;
private final boolean mEnableFling;
private final boolean mManualImageRotation;
private long mLastEventTime;
@@ -52,16 +51,14 @@
private int mA = INVALID_POINTER;
private int mB = INVALID_POINTER;
private float[] pts = new float[MAX_POINTER_COUNT];
- private float[] tmp = new float[MAX_POINTER_COUNT];
public PhotoTouchListener(Context context, PhotoTable table) {
mTable = table;
final ViewConfiguration configuration = ViewConfiguration.get(context);
mTouchSlop = configuration.getScaledTouchSlop();
- mTapTimeout = configuration.getTapTimeout();
+ mTapTimeout = ViewConfiguration.getTapTimeout();
final Resources resources = context.getResources();
mBeta = resources.getInteger(R.integer.table_damping) / 1000000f;
- mTableRatio = resources.getInteger(R.integer.table_ratio) / 1000000f;
mEnableFling = resources.getBoolean(R.bool.enable_fling);
mManualImageRotation = resources.getBoolean(R.bool.enable_manual_image_rotation);
}
@@ -184,16 +181,16 @@
mLastTouchY = y;
}
- if (mTable.getSelected() != target) {
- target.animate().cancel();
-
- target.setX((int) (mInitialTargetX + x - mInitialTouchX));
- target.setY((int) (mInitialTargetY + y - mInitialTouchY));
+ if (!mTable.hasSelection()) {
+ float rotation = target.getRotation();
if (mManualImageRotation && mB != INVALID_POINTER) {
float a = getAngle(target, ev);
- target.setRotation(
- (int) (mInitialTargetA + a - mInitialTouchA));
+ rotation = mInitialTargetA + a - mInitialTouchA;
}
+ mTable.move(target,
+ mInitialTargetX + x - mInitialTouchX,
+ mInitialTargetY + y - mInitialTouchY,
+ rotation);
}
}
}
@@ -210,13 +207,19 @@
}
double distance = Math.hypot(x0 - mInitialTouchX,
y0 - mInitialTouchY);
- if (mTable.getSelected() == target) {
- mTable.dropOnTable(target);
- mTable.clearSelection();
+ if (mTable.hasSelection()) {
+ if (distance < mTouchSlop) {
+ mTable.clearSelection();
+ } else {
+ if ((x0 - mInitialTouchX) > 0f) {
+ mTable.selectPrevious();
+ } else {
+ mTable.selectNext();
+ }
+ }
} else if ((ev.getEventTime() - mInitialTouchTime) < mTapTimeout &&
distance < mTouchSlop) {
// tap
- target.animate().cancel();
mTable.setSelection(target);
} else {
onFling(target, mDX, mDY);
diff --git a/src/com/android/dreams/phototable/PicasaSource.java b/src/com/android/dreams/phototable/PicasaSource.java
index ef4a7c4..eb8fd1f 100644
--- a/src/com/android/dreams/phototable/PicasaSource.java
+++ b/src/com/android/dreams/phototable/PicasaSource.java
@@ -26,7 +26,6 @@
import java.io.FileNotFoundException;
import java.io.InputStream;
-import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -36,7 +35,7 @@
/**
* Loads images from Picasa.
*/
-public class PicasaSource extends PhotoSource {
+public class PicasaSource extends CursorPhotoSource {
private static final String TAG = "PhotoTable.PicasaSource";
private static final String PICASA_AUTHORITY =
@@ -61,7 +60,6 @@
private static final String PICASA_TYPE_KEY = "type";
private static final String PICASA_TYPE_FULL_VALUE = "full";
private static final String PICASA_TYPE_SCREEN_VALUE = "screennail";
- private static final String PICASA_TYPE_THUMB_VALUE = "thumbnail";
private static final String PICASA_TYPE_IMAGE_VALUE = "image";
private static final String PICASA_POSTS_TYPE = "Buzz";
private static final String PICASA_UPLOAD_TYPE = "InstantUpload";
@@ -90,6 +88,7 @@
mConnectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
mRecycleBin = new LinkedList<ImageData>();
+
fillQueue();
mDisplayLongSide = getDisplayLongSide();
}
@@ -103,6 +102,65 @@
}
@Override
+ protected void openCursor(ImageData data) {
+ log(TAG, "opening single album");
+
+ String[] projection = {PICASA_ID, PICASA_URL, PICASA_ROTATION, PICASA_ALBUM_ID};
+ String selection = PICASA_ALBUM_ID + " = '" + data.albumId + "'";
+
+ Uri.Builder picasaUriBuilder = new Uri.Builder()
+ .scheme("content")
+ .authority(PICASA_AUTHORITY)
+ .appendPath(PICASA_PHOTO_PATH);
+ data.cursor = mResolver.query(picasaUriBuilder.build(),
+ projection, selection, null, null);
+ }
+
+ @Override
+ protected void findPosition(ImageData data) {
+ if (data.position == UNINITIALIZED) {
+ if (data.cursor == null) {
+ openCursor(data);
+ }
+ if (data.cursor != null) {
+ int idIndex = data.cursor.getColumnIndex(PICASA_ID);
+ data.cursor.moveToPosition(-1);
+ while (data.position == -1 && data.cursor.moveToNext()) {
+ String id = data.cursor.getString(idIndex);
+ if (id != null && id.equals(data.id)) {
+ data.position = data.cursor.getPosition();
+ }
+ }
+ if (data.position == -1) {
+ // oops! The image isn't in this album. How did we get here?
+ data.position = INVALID;
+ }
+ }
+ }
+ }
+
+ @Override
+ protected ImageData unpackImageData(Cursor cursor, ImageData data) {
+ if (data == null) {
+ data = new ImageData();
+ }
+ int idIndex = cursor.getColumnIndex(PICASA_ID);
+ int urlIndex = cursor.getColumnIndex(PICASA_URL);
+ int bucketIndex = cursor.getColumnIndex(PICASA_ALBUM_ID);
+
+ data.id = cursor.getString(idIndex);
+ if (bucketIndex >= 0) {
+ data.albumId = cursor.getString(bucketIndex);
+ }
+ if (urlIndex >= 0) {
+ data.url = cursor.getString(urlIndex);
+ }
+ data.position = UNINITIALIZED;
+ data.cursor = null;
+ return data;
+ }
+
+ @Override
protected Collection<ImageData> findImages(int howMany) {
log(TAG, "finding images");
LinkedList<ImageData> foundImages = new LinkedList<ImageData>();
@@ -117,7 +175,6 @@
}
String[] projection = {PICASA_ID, PICASA_URL, PICASA_ROTATION, PICASA_ALBUM_ID};
- boolean usePosts = false;
LinkedList<String> albumIds = new LinkedList<String>();
for (String id : getFoundAlbums()) {
if (mSettings.isAlbumEnabled(id)) {
@@ -170,22 +227,13 @@
cursor.moveToPosition(mLastPosition);
int idIndex = cursor.getColumnIndex(PICASA_ID);
- int urlIndex = cursor.getColumnIndex(PICASA_URL);
- int orientationIndex = cursor.getColumnIndex(PICASA_ROTATION);
- int bucketIndex = cursor.getColumnIndex(PICASA_ALBUM_ID);
if (idIndex < 0) {
log(TAG, "can't find the ID column!");
} else {
while (cursor.moveToNext()) {
if (idIndex >= 0) {
- ImageData data = new ImageData();
- data.id = cursor.getString(idIndex);
-
- if (urlIndex >= 0) {
- data.url = cursor.getString(urlIndex);
- }
-
+ ImageData data = unpackImageData(cursor, null);
foundImages.offer(data);
}
mLastPosition = cursor.getPosition();
@@ -255,7 +303,6 @@
cursor.moveToPosition(-1);
int idIndex = cursor.getColumnIndex(PICASA_ID);
- int typeIndex = cursor.getColumnIndex(PICASA_ALBUM_TYPE);
if (idIndex < 0) {
log(TAG, "can't find the ID column!");
@@ -398,9 +445,6 @@
} catch (FileNotFoundException fnf) {
log(TAG, "file not found: " + fnf);
is = null;
- } catch (IOException ioe) {
- log(TAG, "i/o exception: " + ioe);
- is = null;
}
if (is != null) {
diff --git a/src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java b/src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java
index 6c5a88a..42f2eb0 100644
--- a/src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java
+++ b/src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java
@@ -21,10 +21,9 @@
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.widget.TextView;
import android.widget.ListAdapter;
+import android.widget.TextView;
import java.util.Arrays;
import java.util.List;
@@ -39,13 +38,11 @@
private final LayoutInflater mInflater;
private final int mLayout;
private final AlbumDataAdapter mAlbumData;
- private final Context mContext;
private int[] sections;
public SectionedAlbumDataAdapter(Context context, SharedPreferences settings,
int headerLayout, int itemLayout, List<PhotoSource.AlbumData> objects) {
mLayout = headerLayout;
- mContext = context;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mAlbumData = new AlbumDataAdapter(context, settings, itemLayout, objects);
mAlbumData.sort(new AlbumDataAdapter.AccountComparator());
diff --git a/src/com/android/dreams/phototable/SoftLandingInterpolator.java b/src/com/android/dreams/phototable/SoftLandingInterpolator.java
index bb2c1bd..6a6020d 100644
--- a/src/com/android/dreams/phototable/SoftLandingInterpolator.java
+++ b/src/com/android/dreams/phototable/SoftLandingInterpolator.java
@@ -16,10 +16,9 @@
package com.android.dreams.phototable;
-import android.view.animation.Interpolator;
import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
-import android.util.Log;
/**
* An interpolator where the rate of change starts out quickly and
@@ -31,7 +30,6 @@
private final DecelerateInterpolator slide;
private final float mI;
private final float mO;
- private final float lowerRange;
private final float upperRange;
private final float bottom;
private final float top;
@@ -44,7 +42,6 @@
final float epsilon = Math.min(mI / 2f, (1f - mI) / 2f);
bottom = mI - epsilon;
top = mI + epsilon;
- lowerRange = top;
upperRange = 1f - bottom;
}
diff --git a/src/com/android/dreams/phototable/StockSource.java b/src/com/android/dreams/phototable/StockSource.java
index 3d44309..d7b3500 100644
--- a/src/com/android/dreams/phototable/StockSource.java
+++ b/src/com/android/dreams/phototable/StockSource.java
@@ -17,45 +17,55 @@
import android.content.Context;
import android.content.SharedPreferences;
-import android.util.Log;
import java.io.InputStream;
-import java.util.Collection;
import java.util.ArrayList;
+import java.util.Collection;
/**
* Picks a random image from the local store.
*/
-public class
-
-StockSource extends PhotoSource {
+public class StockSource extends PhotoSource {
public static final String ALBUM_ID = "com.android.dreams.phototable.StockSource";
private static final String TAG = "PhotoTable.StockSource";
private static final int[] PHOTOS = { R.drawable.blank_photo };
+ private final ArrayList<ImageData> mImageCache;
+ private final ArrayList<AlbumData> mAlbumCache;
+
private final ArrayList<ImageData> mImageList;
private final ArrayList<AlbumData> mAlbumList;
private final String mStockPhotoName;
- private int mNextPosition;
public StockSource(Context context, SharedPreferences settings) {
super(context, settings, null);
mSourceName = TAG;
mStockPhotoName = mResources.getString(R.string.stock_photo_album_name, "Default Photos");
+ mImageCache = new ArrayList<ImageData>(PHOTOS.length);
+ mAlbumCache = new ArrayList<AlbumData>(1);
mImageList = new ArrayList<ImageData>(PHOTOS.length);
mAlbumList = new ArrayList<AlbumData>(1);
+
+ AlbumData albumData = new AlbumData();
+ albumData.id = ALBUM_ID;
+ albumData.account = mStockPhotoName;
+ albumData.title = mStockPhotoName;
+ mAlbumCache.add(albumData);
+
+ for (int i = 0; i < PHOTOS.length; i++) {
+ ImageData imageData = new ImageData();
+ imageData.id = Integer.toString(i);
+ mImageCache.add(imageData);
+ }
+
fillQueue();
}
@Override
public Collection<AlbumData> findAlbums() {
if (mAlbumList.isEmpty()) {
- AlbumData data = new AlbumData();
- data.id = ALBUM_ID;
- data.account = mStockPhotoName;
- data.title = mStockPhotoName;
- mAlbumList.add(data);
+ mAlbumList.addAll(mAlbumCache);
}
log(TAG, "returning a list of albums: " + mAlbumList.size());
return mAlbumList;
@@ -64,11 +74,7 @@
@Override
protected Collection<ImageData> findImages(int howMany) {
if (mImageList.isEmpty()) {
- for (int i = 0; i < PHOTOS.length; i++) {
- ImageData data = new ImageData();
- data.id = Integer.toString(PHOTOS[i]);
- mImageList.add(data);
- }
+ mImageList.addAll(mImageCache);
}
return mImageList;
}
@@ -78,7 +84,8 @@
InputStream is = null;
try {
log(TAG, "opening:" + data.id);
- is = mResources.openRawResource(Integer.valueOf(data.id));
+ int idx = Integer.valueOf(data.id);
+ is = mResources.openRawResource(PHOTOS[idx]);
} catch (Exception ex) {
log(TAG, ex.toString());
is = null;
@@ -86,5 +93,17 @@
return is;
}
+
+ public ImageData naturalNext(ImageData current) {
+ int idx = Integer.valueOf(current.id);
+ idx = (idx + 1) % PHOTOS.length;
+ return mImageCache.get(idx);
+ }
+
+ public ImageData naturalPrevious(ImageData current) {
+ int idx = Integer.valueOf(current.id);
+ idx = (PHOTOS.length + idx - 1) % PHOTOS.length;
+ return mImageCache.get(idx);
+ }
}