summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/dreams/phototable/AlbumDataAdapter.java30
-rw-r--r--src/com/android/dreams/phototable/AlbumSettings.java22
-rw-r--r--src/com/android/dreams/phototable/CursorPhotoSource.java85
-rw-r--r--src/com/android/dreams/phototable/DragGestureDetector.java113
-rw-r--r--src/com/android/dreams/phototable/EdgeSwipeDetector.java76
-rw-r--r--src/com/android/dreams/phototable/FlipperDream.java1
-rw-r--r--src/com/android/dreams/phototable/FlipperDreamSettings.java92
-rw-r--r--src/com/android/dreams/phototable/KeyboardInterpreter.java122
-rw-r--r--src/com/android/dreams/phototable/LocalSource.java96
-rw-r--r--src/com/android/dreams/phototable/PhotoCarousel.java24
-rw-r--r--src/com/android/dreams/phototable/PhotoSource.java78
-rw-r--r--src/com/android/dreams/phototable/PhotoSourcePlexor.java20
-rw-r--r--src/com/android/dreams/phototable/PhotoTable.java782
-rw-r--r--src/com/android/dreams/phototable/PhotoTableDream.java6
-rw-r--r--src/com/android/dreams/phototable/PhotoTableDreamSettings.java34
-rw-r--r--src/com/android/dreams/phototable/PhotoTouchListener.java61
-rw-r--r--src/com/android/dreams/phototable/PicasaSource.java123
-rw-r--r--src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java15
-rw-r--r--src/com/android/dreams/phototable/SoftLandingInterpolator.java5
-rw-r--r--src/com/android/dreams/phototable/StockSource.java59
20 files changed, 1493 insertions, 351 deletions
diff --git a/src/com/android/dreams/phototable/AlbumDataAdapter.java b/src/com/android/dreams/phototable/AlbumDataAdapter.java
index a0c039b..570bbd7 100644
--- a/src/com/android/dreams/phototable/AlbumDataAdapter.java
+++ b/src/com/android/dreams/phototable/AlbumDataAdapter.java
@@ -17,7 +17,6 @@ package com.android.dreams.phototable;
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 android.widget.TextView;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
-import java.util.Set;
/**
* Settings panel for photo flipping dream.
@@ -45,6 +43,7 @@ public class AlbumDataAdapter extends ArrayAdapter<PhotoSource.AlbumData> {
private final LayoutInflater mInflater;
private final int mLayout;
private final ItemClickListener mListener;
+ private final HashSet<String> mValidAlbumIds;
public AlbumDataAdapter(Context context, SharedPreferences settings,
int resource, List<PhotoSource.AlbumData> objects) {
@@ -54,11 +53,29 @@ public class AlbumDataAdapter extends ArrayAdapter<PhotoSource.AlbumData> {
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mListener = new ItemClickListener();
- HashSet<String> validAlbumIds = new HashSet<String>(objects.size());
+ mValidAlbumIds = new HashSet<String>(objects.size());
for (PhotoSource.AlbumData albumData: objects) {
- validAlbumIds.add(albumData.id);
+ mValidAlbumIds.add(albumData.id);
}
- mSettings.pruneObsoleteSettings(validAlbumIds);
+ mSettings.pruneObsoleteSettings(mValidAlbumIds);
+ }
+
+ public boolean isSelected(int position) {
+ PhotoSource.AlbumData data = getItem(position);
+ return mSettings.isAlbumEnabled(data.id);
+ }
+
+ public boolean areAllSelected() {
+ return mSettings.areAllEnabled(mValidAlbumIds);
+ }
+
+ public void selectAll(boolean select) {
+ if (select) {
+ mSettings.enableAllAlbums(mValidAlbumIds);
+ } else {
+ mSettings.disableAllAlbums();
+ }
+ notifyDataSetChanged();
}
@Override
@@ -72,7 +89,7 @@ public class AlbumDataAdapter extends ArrayAdapter<PhotoSource.AlbumData> {
View vCheckBox = item.findViewById(R.id.enabled);
if (vCheckBox != null && vCheckBox instanceof CheckBox) {
CheckBox checkBox = (CheckBox) vCheckBox;
- checkBox.setChecked(mSettings.isAlbumEnabled(data.id));
+ checkBox.setChecked(isSelected(position));
checkBox.setTag(R.id.data_payload, data);
}
@@ -159,6 +176,7 @@ public class AlbumDataAdapter extends ArrayAdapter<PhotoSource.AlbumData> {
final boolean isChecked = !checkBox.isChecked();
checkBox.setChecked(isChecked);
mSettings.setAlbumEnabled(data.id, isChecked);
+ notifyDataSetChanged();
if (DEBUG) Log.i(TAG, data.title + " is " +
(isChecked ? "" : "not") + " enabled");
} else {
diff --git a/src/com/android/dreams/phototable/AlbumSettings.java b/src/com/android/dreams/phototable/AlbumSettings.java
index 23dda46..1ccd498 100644
--- a/src/com/android/dreams/phototable/AlbumSettings.java
+++ b/src/com/android/dreams/phototable/AlbumSettings.java
@@ -51,11 +51,16 @@ public class AlbumSettings {
public boolean isAlbumEnabled(String albumId) {
synchronized (mEnabledAlbums) {
- boolean isEnabled = mEnabledAlbums.contains(albumId);
return mEnabledAlbums.contains(albumId);
}
}
+ public boolean areAllEnabled(Collection<String> validAlbums) {
+ synchronized (mEnabledAlbums) {
+ return mEnabledAlbums.containsAll(validAlbums);
+ }
+ }
+
public void setAlbumEnabled(String albumId, boolean enabled) {
if (isAlbumEnabled(albumId) != enabled) {
synchronized (mEnabledAlbums) {
@@ -70,6 +75,21 @@ public class AlbumSettings {
}
}
+ public void disableAllAlbums() {
+ synchronized (mEnabledAlbums) {
+ mEnabledAlbums.clear();
+ writeEnabledAlbumsLocked();
+ }
+ }
+
+ public void enableAllAlbums(Collection<String> validAlbums) {
+ synchronized (mEnabledAlbums) {
+ mEnabledAlbums.clear();
+ mEnabledAlbums.addAll(validAlbums);
+ writeEnabledAlbumsLocked();
+ }
+ }
+
public void pruneObsoleteSettings(Collection<String> validAlbums) {
if (!validAlbums.containsAll(mEnabledAlbums)) {
synchronized (mEnabledAlbums) {
diff --git a/src/com/android/dreams/phototable/CursorPhotoSource.java b/src/com/android/dreams/phototable/CursorPhotoSource.java
new file mode 100644
index 0000000..f010a92
--- /dev/null
+++ b/src/com/android/dreams/phototable/CursorPhotoSource.java
@@ -0,0 +1,85 @@
+/*
+ * 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 || current.cursor.isClosed()) {
+ 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 || current.cursor.isClosed()) {
+ 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;
+ }
+
+ @Override
+ protected void donePaging(ImageData current) {
+ if (current.cursor != null && !current.cursor.isClosed()) {
+ current.cursor.close();
+ }
+ }
+
+ 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
new file mode 100644
index 0000000..2153c48
--- /dev/null
+++ b/src/com/android/dreams/phototable/DragGestureDetector.java
@@ -0,0 +1,113 @@
+/*
+ * 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.res.Resources;
+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 float[] mLast;
+ private float[] mCurrent;
+ private boolean mDrag;
+
+ public DragGestureDetector(Context context, PhotoTable table) {
+ Resources res = context.getResources();
+ mTouchGain = res.getInteger(R.integer.generalized_touch_gain) / 1000000f;
+ mTable = table;
+ mLast = new float[2];
+ mCurrent = new float[2];
+ }
+
+ private void computeAveragePosition(MotionEvent event, float[] position) {
+ computeAveragePosition(event, position, -1);
+ }
+
+ private void computeAveragePosition(MotionEvent event, float[] position, int ignore) {
+ final int pointerCount = event.getPointerCount();
+ position[0] = 0f;
+ position[1] = 0f;
+ float count = 0f;
+ for (int p = 0; p < pointerCount; p++) {
+ if (p != ignore) {
+ position[0] += event.getX(p);
+ position[1] += event.getY(p);
+ count += 1f;
+ }
+ }
+ position[0] /= count;
+ position[1] /= count;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ int index = event.getActionIndex();
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ computeAveragePosition(event, mLast);
+ mDrag = false;
+ break;
+
+ case MotionEvent.ACTION_POINTER_DOWN:
+ mDrag = mTable.hasFocus();
+ computeAveragePosition(event, mLast);
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ computeAveragePosition(event, mLast, index);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ computeAveragePosition(event, mCurrent);
+ if (mDrag) {
+ move(event, false);
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ if (mDrag) {
+ move(event, true);
+ }
+ mDrag = false;
+ break;
+ }
+
+ if (mDrag) {
+ mTable.refreshFocus();
+ }
+
+ return mDrag;
+ }
+
+ private void move(MotionEvent event, boolean drop) {
+ mTable.move(mTable.getFocus(),
+ mTouchGain * (mCurrent[0] - mLast[0]),
+ mTouchGain * (mCurrent[1] - mLast[1]),
+ drop);
+ mLast[0] = mCurrent[0];
+ mLast[1] = mCurrent[1];
+ }
+}
+
diff --git a/src/com/android/dreams/phototable/EdgeSwipeDetector.java b/src/com/android/dreams/phototable/EdgeSwipeDetector.java
new file mode 100644
index 0000000..e5ca23d
--- /dev/null
+++ b/src/com/android/dreams/phototable/EdgeSwipeDetector.java
@@ -0,0 +1,76 @@
+/*
+ * 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.res.Resources;
+import android.view.MotionEvent;
+
+/**
+ * Detect and dispatch edge events.
+ */
+public class EdgeSwipeDetector {
+ @SuppressWarnings("unused")
+ private static final String TAG = "EdgeSwipeDetector";
+ private float mEdgeSwipeGutter;
+ private float mEdgeSwipeThreshold;
+ private boolean mEdgeSwipe;
+
+ private final PhotoTable mTable;
+
+ public EdgeSwipeDetector(Context context, PhotoTable table) {
+ mTable = table;
+ final Resources resources = context.getResources();
+ mEdgeSwipeGutter = resources.getInteger(R.integer.table_edge_swipe_gutter) / 1000000f;
+ mEdgeSwipeThreshold = resources.getInteger(R.integer.table_edge_swipe_threshold) / 1000000f;
+ }
+
+ public boolean onTouchEvent(MotionEvent event) {
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ float edgeGutter = event.getDevice().getMotionRange(MotionEvent.AXIS_X).getMax()
+ * mEdgeSwipeGutter;
+ if (event.getX() < edgeGutter) {
+ mEdgeSwipe = true;
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mEdgeSwipe) {
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ if (mEdgeSwipe) {
+ mEdgeSwipe = false;
+ float enough = event.getDevice().getMotionRange(MotionEvent.AXIS_X).getMax()
+ * mEdgeSwipeThreshold;
+ if (event.getX() > enough) {
+ if (mTable.hasFocus()) {
+ mTable.fling(mTable.getFocus());
+ } else if (mTable.hasSelection()) {
+ mTable.clearSelection();
+ }
+ }
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+}
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 1252846..464029e 100644
--- a/src/com/android/dreams/phototable/FlipperDreamSettings.java
+++ b/src/com/android/dreams/phototable/FlipperDreamSettings.java
@@ -15,12 +15,16 @@
*/
package com.android.dreams.phototable;
-import android.content.SharedPreferences;
import android.app.ListActivity;
+import android.content.SharedPreferences;
+import android.database.DataSetObserver;
import android.os.AsyncTask;
+import android.os.AsyncTask.Status;
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;
@@ -28,22 +32,38 @@ import java.util.LinkedList;
* 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;
+ protected SharedPreferences mSettings;
+
private PhotoSourcePlexor mPhotoSource;
- private ListAdapter mAdapter;
- private SharedPreferences mSettings;
+ private SectionedAlbumDataAdapter mAdapter;
+ private MenuItem mSelectAll;
+ private AsyncTask<Void, Void, Void> mLoadingTask;
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
-
mSettings = getSharedPreferences(PREFS_NAME, 0);
+ init();
+ }
+
+ @Override
+ protected void onResume(){
+ super.onResume();
+ init();
+ }
+
+ protected void init() {
mPhotoSource = new PhotoSourcePlexor(this, mSettings);
setContentView(R.layout.settingslist);
-
- new AsyncTask<Void, Void, Void>() {
+ if (mLoadingTask != null && mLoadingTask.getStatus() != Status.FINISHED) {
+ mLoadingTask.cancel(true);
+ }
+ showApology(false);
+ mLoadingTask = new AsyncTask<Void, Void, Void>() {
@Override
public Void doInBackground(Void... unused) {
mAdapter = new SectionedAlbumDataAdapter(FlipperDreamSettings.this,
@@ -56,11 +76,61 @@ public class FlipperDreamSettings extends ListActivity {
@Override
public void onPostExecute(Void unused) {
+ mAdapter.registerDataSetObserver(new DataSetObserver () {
+ @Override
+ public void onChanged() {
+ updateActionItem();
+ }
+ @Override
+ public void onInvalidated() {
+ updateActionItem();
+ }
+ });
setListAdapter(mAdapter);
- if (mAdapter.getCount() == 0) {
- findViewById(android.R.id.empty).setVisibility(View.GONE);
- }
+ getListView().setItemsCanFocus(true);
+ updateActionItem();
+ showApology(mAdapter.getCount() == 0);
}
- }.execute();
+ };
+ mLoadingTask.execute();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.photodream_settings_menu, menu);
+ mSelectAll = menu.findItem(R.id.photodream_menu_all);
+ updateActionItem();
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.photodream_menu_all:
+ mAdapter.selectAll(!mAdapter.areAllSelected());
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void showApology(boolean apologize) {
+ View empty = findViewById(R.id.spinner);
+ View sorry = findViewById(R.id.sorry);
+ if (empty != null && sorry != null) {
+ empty.setVisibility(apologize ? View.GONE : View.VISIBLE);
+ sorry.setVisibility(apologize ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ private void updateActionItem() {
+ if (mAdapter != null && mSelectAll != null) {
+ if (mAdapter.areAllSelected()) {
+ mSelectAll.setTitle(R.string.photodream_select_none);
+ } else {
+ mSelectAll.setTitle(R.string.photodream_select_all);
+ }
+ }
}
}
diff --git a/src/com/android/dreams/phototable/KeyboardInterpreter.java b/src/com/android/dreams/phototable/KeyboardInterpreter.java
new file mode 100644
index 0000000..874599b
--- /dev/null
+++ b/src/com/android/dreams/phototable/KeyboardInterpreter.java
@@ -0,0 +1,122 @@
+/*
+ * 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;
+ private final long mBounce;
+ private long mLastDeckNavigation;
+
+ public KeyboardInterpreter(PhotoTable table) {
+ mBounce = 2000; // TODO: remove this once latencies in lower layers are removed.
+ 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;
+
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_L:
+ if ((System.currentTimeMillis() - mLastDeckNavigation) > mBounce) {
+ mLastDeckNavigation = System.currentTimeMillis();
+ mTable.selectPrevious();
+ }
+ break;
+
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_H:
+ if ((System.currentTimeMillis() - mLastDeckNavigation) > mBounce) {
+ mLastDeckNavigation = System.currentTimeMillis();
+ mTable.selectNext();
+ }
+ break;
+
+ default:
+ if (DEBUG) Log.d(TAG, "dropped unexpected: " + keyCode);
+ consumed = false;
+ // give the user some more time to figure it out
+ mTable.refreshSelection();
+ 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;
+ // give the user some more time to figure it out
+ mTable.refreshFocus();
+ break;
+ }
+ }
+
+ return consumed;
+ }
+}
diff --git a/src/com/android/dreams/phototable/LocalSource.java b/src/com/android/dreams/phototable/LocalSource.java
index 1faf589..cf2e0ec 100644
--- a/src/com/android/dreams/phototable/LocalSource.java
+++ b/src/com/android/dreams/phototable/LocalSource.java
@@ -30,20 +30,20 @@ import java.util.Set;
/**
* 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;
private final String mLocalSourceName;
private Set<String> mFoundAlbumIds;
- private int mNextPosition;
+ private int mLastPosition;
public LocalSource(Context context, SharedPreferences settings) {
super(context, settings);
mLocalSourceName = mResources.getString(R.string.local_source_name, "Photos on Device");
mUnknownAlbumName = mResources.getString(R.string.unknown_album_name, "Unknown");
mSourceName = TAG;
- mNextPosition = -1;
+ mLastPosition = INVALID;
fillQueue();
}
@@ -65,7 +65,7 @@ public class LocalSource extends PhotoSource {
Cursor cursor = mResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, null, null, null);
if (cursor != null) {
- cursor.moveToFirst();
+ cursor.moveToPosition(-1);
int dataIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
int bucketIndex = cursor.getColumnIndex(MediaStore.Images.Media.BUCKET_ID);
@@ -75,7 +75,7 @@ public class LocalSource extends PhotoSource {
if (bucketIndex < 0) {
log(TAG, "can't find the ID column!");
} else {
- while (!cursor.isAfterLast()) {
+ while (cursor.moveToNext()) {
String id = TAG + ":" + cursor.getString(bucketIndex);
AlbumData data = foundAlbums.get(id);
if (foundAlbums.get(id) == null) {
@@ -102,7 +102,6 @@ public class LocalSource extends PhotoSource {
updated :
Math.min(data.updated, updated));
}
- cursor.moveToNext();
}
}
cursor.close();
@@ -114,6 +113,59 @@ public class LocalSource extends PhotoSource {
}
@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>();
@@ -139,33 +191,26 @@ public class LocalSource extends PhotoSource {
Cursor cursor = mResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection, selection, null, null);
if (cursor != null) {
- if (cursor.getCount() > howMany && mNextPosition == -1) {
- mNextPosition = mRNG.nextInt() % (cursor.getCount() - howMany);
- }
- if (mNextPosition == -1) {
- mNextPosition = 0;
- }
- cursor.moveToPosition(mNextPosition);
-
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 (cursor.getCount() > howMany && mLastPosition == INVALID) {
+ mLastPosition = pickRandomStart(cursor.getCount(), howMany);
+ }
+ cursor.moveToPosition(mLastPosition);
if (dataIndex < 0) {
log(TAG, "can't find the DATA column!");
} else {
- while (foundImages.size() < howMany && !cursor.isAfterLast()) {
- ImageData data = new ImageData();
- data.url = cursor.getString(dataIndex);
- data.orientation = cursor.getInt(orientationIndex);
+ while (foundImages.size() < howMany && cursor.moveToNext()) {
+ ImageData data = unpackImageData(cursor, null);
foundImages.offer(data);
- if (cursor.moveToNext()) {
- mNextPosition++;
- }
+ mLastPosition = cursor.getPosition();
}
if (cursor.isAfterLast()) {
- mNextPosition = 0;
+ mLastPosition = -1;
+ }
+ if (cursor.isBeforeFirst()) {
+ mLastPosition = INVALID;
}
}
@@ -189,3 +234,4 @@ public class LocalSource extends PhotoSource {
return (InputStream) fis;
}
}
+
diff --git a/src/com/android/dreams/phototable/PhotoCarousel.java b/src/com/android/dreams/phototable/PhotoCarousel.java
index 70ba046..23939c9 100644
--- a/src/com/android/dreams/phototable/PhotoCarousel.java
+++ b/src/com/android/dreams/phototable/PhotoCarousel.java
@@ -20,7 +20,6 @@ import android.content.res.Resources;
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;
@@ -80,7 +79,8 @@ public class PhotoCarousel extends FrameLayout {
scheduleNext((int) mDropPeriod - elapsed);
} else {
scheduleNext(mDropPeriod);
- if (changePhoto() || canFlip()) {
+ if (changePhoto() ||
+ (elapsed > (5 * mDropPeriod) && canFlip())) {
flip(1f);
mLastFlipTime = now;
}
@@ -107,6 +107,7 @@ public class PhotoCarousel extends FrameLayout {
mPanel = new View[2];
mFlipper = new Flipper();
+ // this is dead code if the dream calls setInteractive(false)
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener() {
@Override
@@ -183,22 +184,18 @@ public class PhotoCarousel extends FrameLayout {
Bitmap photo = mBitmapQueue.poll();
if (photo != null) {
ImageView destination = getBackface();
- Bitmap old = mBitmapStore.get(destination);
int width = photo.getWidth();
int height = photo.getHeight();
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);
-
- if (old != null) {
- old.recycle();
- }
+ Bitmap old = mBitmapStore.put(destination, photo);
+ mPhotoSource.recycle(old);
return true;
} else {
@@ -247,8 +244,8 @@ public class PhotoCarousel extends FrameLayout {
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)
@@ -284,7 +281,6 @@ public class PhotoCarousel extends FrameLayout {
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 670bd02..fc4cf7b 100644
--- a/src/com/android/dreams/phototable/PhotoSource.java
+++ b/src/com/android/dreams/phototable/PhotoSource.java
@@ -23,16 +23,15 @@ 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.BufferedInputStream;
import java.io.FileNotFoundException;
-import java.io.InputStream;
import java.io.IOException;
-import java.io.BufferedInputStream;
+import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedList;
import java.util.Random;
@@ -52,9 +51,22 @@ public abstract class PhotoSource {
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 void donePaging() {
+ PhotoSource.this.donePaging(this);
+ }
}
public class AlbumData {
@@ -76,6 +88,7 @@ public abstract class PhotoSource {
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;
@@ -99,6 +112,7 @@ public abstract class PhotoSource {
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;
}
@@ -121,11 +135,11 @@ public abstract class PhotoSource {
if (mImageQueue.isEmpty()) {
fillQueue();
}
-
imageData = mImageQueue.poll();
}
if (imageData != null) {
image = load(imageData, options, longSide, shortSide);
+ mImageMap.put(image, imageData);
imageData = null;
}
@@ -248,7 +262,61 @@ public abstract class PhotoSource {
}
}
+ protected int pickRandomStart(int total, int max) {
+ if (max >= total) {
+ return -1;
+ } else {
+ return (mRNG.nextInt() % (total - max)) - 1;
+ }
+ }
+
+ 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;
+ }
+
+ public void donePaging(Bitmap current) {
+ ImageData data = mImageMap.get(current);
+ if (data != null) {
+ data.donePaging();
+ }
+ }
+
+ public void recycle(Bitmap trash) {
+ if (trash != null) {
+ mImageMap.remove(trash);
+ trash.recycle();
+ }
+ }
+
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);
+ protected abstract void donePaging(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..3733b02 100644
--- a/src/com/android/dreams/phototable/PhotoSourcePlexor.java
+++ b/src/com/android/dreams/phototable/PhotoSourcePlexor.java
@@ -17,12 +17,8 @@ package com.android.dreams.phototable;
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 @@ public class PhotoSourcePlexor extends PhotoSource {
private final PhotoSource mPicasaSource;
private final PhotoSource mLocalSource;
- private SharedPreferences mSettings;
public PhotoSourcePlexor(Context context, SharedPreferences settings) {
super(context, settings);
@@ -75,4 +70,19 @@ public class PhotoSourcePlexor extends PhotoSource {
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();
+ }
+
+ @Override
+ protected void donePaging(ImageData current) {
+ current.donePaging();
+ }
}
diff --git a/src/com/android/dreams/phototable/PhotoTable.java b/src/com/android/dreams/phototable/PhotoTable.java
index 50212c9..7e7f92e 100644
--- a/src/com/android/dreams/phototable/PhotoTable.java
+++ b/src/com/android/dreams/phototable/PhotoTable.java
@@ -15,30 +15,38 @@
*/
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.PointF;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
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;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
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.ArrayList;
+import java.util.Formatter;
+import java.util.HashSet;
import java.util.LinkedList;
+import java.util.List;
import java.util.Random;
+import java.util.Set;
/**
* A surface where photos sit.
@@ -48,22 +56,34 @@ public class PhotoTable extends FrameLayout {
private static final boolean DEBUG = false;
class Launcher implements Runnable {
- private final PhotoTable mTable;
- public Launcher(PhotoTable table) {
- mTable = table;
+ @Override
+ public void run() {
+ PhotoTable.this.scheduleNext(mDropPeriod);
+ PhotoTable.this.launch();
}
+ }
+ class FocusReaper implements Runnable {
@Override
public void run() {
- mTable.scheduleNext(mDropPeriod);
- mTable.launch();
+ PhotoTable.this.clearFocus();
}
}
- private static final long MAX_SELECTION_TIME = 10000L;
+ class SelectionReaper implements Runnable {
+ @Override
+ public void run() {
+ PhotoTable.this.clearSelection();
+ }
+ }
+
+ private static final int NEXT = 1;
+ private static final int PREV = 0;
private static Random sRNG = new Random();
private final Launcher mLauncher;
+ private final FocusReaper mFocusReaper;
+ private final SelectionReaper mSelectionReaper;
private final LinkedList<View> mOnTable;
private final int mDropPeriod;
private final int mFastDropPeriod;
@@ -77,20 +97,34 @@ public class PhotoTable extends FrameLayout {
private final int mTableCapacity;
private final int mRedealCount;
private final int mInset;
- private final PhotoSourcePlexor mPhotoSource;
+ private final PhotoSource mPhotoSource;
private final Resources mResources;
private final Interpolator mThrowInterpolator;
private final Interpolator mDropInterpolator;
+ private final DragGestureDetector mDragGestureDetector;
+ private final EdgeSwipeDetector mEdgeSwipeDetector;
+ private final KeyboardInterpreter mKeyboardInterpreter;
+ private final boolean mStoryModeEnabled;
+ private final long mPickUpDuration;
+ private final int mMaxSelectionTime;
+ private final int mMaxFocusTime;
+ private final List<View> mAnimating;
+
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 mSelection;
+ private View mOnDeck[];
+ private View mFocus;
+ private int mHighlightColor;
+ private ViewGroup mBackground;
+ private ViewGroup mStageLeft;
public PhotoTable(Context context, AttributeSet as) {
super(context, as);
@@ -103,10 +137,15 @@ public class PhotoTable extends FrameLayout {
mTableRatio = mResources.getInteger(R.integer.table_ratio) / 1000000f;
mImageRotationLimit = (float) mResources.getInteger(R.integer.max_image_rotation);
mThrowSpeed = mResources.getDimension(R.dimen.image_throw_speed);
+ mPickUpDuration = mResources.getInteger(R.integer.photo_pickup_duration);
mThrowRotation = (float) mResources.getInteger(R.integer.image_throw_rotatioan);
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);
+ mMaxSelectionTime = mResources.getInteger(R.integer.max_selection_time);
+ mMaxFocusTime = mResources.getInteger(R.integer.max_focus_time);
mThrowInterpolator = new SoftLandingInterpolator(
mResources.getInteger(R.integer.soft_landing_time) / 1000000f,
mResources.getInteger(R.integer.soft_landing_distance) / 1000000f);
@@ -115,36 +154,145 @@ public class PhotoTable extends FrameLayout {
mOnTable = new LinkedList<View>();
mPhotoSource = new PhotoSourcePlexor(getContext(),
getContext().getSharedPreferences(PhotoTableDreamSettings.PREFS_NAME, 0));
- mLauncher = new Launcher(this);
+ mAnimating = new ArrayList<View>();
+ mLauncher = new Launcher();
+ mFocusReaper = new FocusReaper();
+ mSelectionReaper = new SelectionReaper();
+ mDragGestureDetector = new DragGestureDetector(context, this);
+ mEdgeSwipeDetector = new EdgeSwipeDetector(context, this);
+ mKeyboardInterpreter = new KeyboardInterpreter(this);
+ mLoadOnDeckTasks = new LoadNaturalSiblingTask[2];
+ mOnDeck = new View[2];
mStarted = false;
}
-
+ @Override
+ public void onFinishInflate() {
+ mBackground = (ViewGroup) findViewById(R.id.background);
+ mStageLeft = (ViewGroup) findViewById(R.id.stageleft);
+ }
+
public void setDream(DreamService dream) {
mDream = dream;
}
public boolean hasSelection() {
- return mSelected != null;
+ return mSelection != null;
}
- public View getSelected() {
- return mSelected;
+ public View getSelection() {
+ return mSelection;
}
public void clearSelection() {
- mSelected = null;
+ if (hasSelection()) {
+ dropOnTable(mSelection);
+ mPhotoSource.donePaging(getBitmap(mSelection));
+ if (mStoryModeEnabled) {
+ fadeInBackground(mSelection);
+ }
+ mSelection = null;
+ }
+ for (int slot = 0; slot < mOnDeck.length; slot++) {
+ if (mOnDeck[slot] != null) {
+ fadeAway(mOnDeck[slot], false);
+ mOnDeck[slot] = null;
+ }
+ if (mLoadOnDeckTasks[slot] != null &&
+ mLoadOnDeckTasks[slot].getStatus() != AsyncTask.Status.FINISHED) {
+ mLoadOnDeckTasks[slot].cancel(true);
+ mLoadOnDeckTasks[slot] = null;
+ }
+ }
}
public void setSelection(View selected) {
- assert(selected != null);
- if (mSelected != null) {
- dropOnTable(mSelected);
+ if (selected != null) {
+ clearSelection();
+ mSelection = selected;
+ promoteSelection();
+ if (mStoryModeEnabled) {
+ fadeOutBackground(mSelection);
+ }
}
- mSelected = selected;
- mSelectedTime = System.currentTimeMillis();
- bringChildToFront(selected);
- pickUp(selected);
+ }
+
+ 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()) {
+ scheduleSelectionReaper(mMaxSelectionTime);
+ 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 mFocus != null;
+ }
+
+ public View getFocus() {
+ return mFocus;
+ }
+
+ public void clearFocus() {
+ if (hasFocus()) {
+ setHighlight(getFocus(), false);
+ }
+ mFocus = null;
+ }
+
+ public void setDefaultFocus() {
+ setFocus(mOnTable.getLast());
+ }
+
+ public void setFocus(View focus) {
+ assert(focus != null);
+ clearFocus();
+ mFocus = focus;
+ moveToTopOfPile(focus);
+ setHighlight(focus, true);
+ scheduleFocusReaper(mMaxFocusTime);
}
static float lerp(float a, float b, float f) {
@@ -169,17 +317,8 @@ public class PhotoTable extends FrameLayout {
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);
@@ -188,15 +327,82 @@ public class PhotoTable extends FrameLayout {
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;
}
+ private double cross(double[] a, double[] b) {
+ return a[0] * b[1] - a[1] * b[0];
+ }
+
+ private double norm(double[] a) {
+ return Math.hypot(a[0], a[1]);
+ }
+
+ private double[] getCenter(View photo) {
+ float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue();
+ float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue();
+ double[] center = { photo.getX() + width / 2f,
+ - (photo.getY() + height / 2f) };
+ return center;
+ }
+
+ public View moveFocus(View focus, float direction) {
+ return moveFocus(focus, direction, 90f);
+ }
+
+ public View moveFocus(View focus, float direction, float angle) {
+ if (focus == null) {
+ setFocus(mOnTable.getLast());
+ } else {
+ final double alpha = Math.toRadians(direction);
+ final double beta = Math.toRadians(Math.min(angle, 180f) / 2f);
+ final double[] left = { Math.sin(alpha - beta),
+ Math.cos(alpha - beta) };
+ final double[] right = { Math.sin(alpha + beta),
+ Math.cos(alpha + beta) };
+ final double[] a = getCenter(focus);
+ View bestFocus = null;
+ double bestDistance = Double.MAX_VALUE;
+ for (View candidate: mOnTable) {
+ if (candidate != focus) {
+ final double[] b = getCenter(candidate);
+ final double[] delta = { b[0] - a[0],
+ b[1] - a[1] };
+ if (cross(delta, left) > 0.0 && cross(delta, right) < 0.0) {
+ final double distance = norm(delta);
+ if (bestDistance > distance) {
+ bestDistance = distance;
+ bestFocus = candidate;
+ }
+ }
+ }
+ }
+ if (bestFocus == null) {
+ if (angle < 180f) {
+ return moveFocus(focus, direction, 180f);
+ }
+ } else {
+ setFocus(bestFocus);
+ }
+ }
+ return getFocus();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ return mKeyboardInterpreter.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onGenericMotionEvent(MotionEvent event) {
+ return mEdgeSwipeDetector.onTouchEvent(event) || mDragGestureDetector.onTouchEvent(event);
+ }
+
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
if (hasSelection()) {
- dropOnTable(getSelected());
clearSelection();
} else {
if (mTapToExit && mDream != null) {
@@ -211,7 +417,7 @@ public class PhotoTable extends FrameLayout {
@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;
@@ -222,12 +428,18 @@ public class PhotoTable extends FrameLayout {
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();
@@ -238,47 +450,108 @@ public class PhotoTable extends FrameLayout {
return true;
}
- private class PhotoLaunchTask extends AsyncTask<Void, Void, View> {
+ /** Put a nice border on the bitmap. */
+ private static View applyFrame(final PhotoTable table, final BitmapFactory.Options options,
+ 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;
+ private View mParent;
- public PhotoLaunchTask () {
+ public LoadNaturalSiblingTask (int slot) {
mOptions = new BitmapFactory.Options();
mOptions.inTempStorage = new byte[32768];
+ mSlot = slot;
}
@Override
- public View doInBackground(Void... unused) {
- log("load a new photo");
+ public View doInBackground(View... views) {
+ log("load natural %s", (mSlot == NEXT ? "next" : "previous"));
final PhotoTable table = PhotoTable.this;
+ mParent = views[0];
+ final Bitmap current = getBitmap(mParent);
+ 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);
+ }
- 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 (mOptions.outWidth <= 0 || mOptions.outHeight <= 0) {
- photo = null;
+ @Override
+ public void onPostExecute(View photo) {
+ if (photo != null) {
+ if (hasSelection() && getSelection() == mParent) {
+ log("natural %s being rendered", (mSlot == NEXT ? "next" : "previous"));
+ PhotoTable.this.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+ PhotoTable.this.mOnDeck[mSlot] = photo;
+ 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 {
+ recycle(photo);
+ }
} 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));
+ log("natural, %s was null!", (mSlot == NEXT ? "next" : "previous"));
}
+ }
+ };
+
+ private class PhotoLaunchTask extends AsyncTask<Void, Void, View> {
+ private final BitmapFactory.Options mOptions;
+
+ public PhotoLaunchTask () {
+ mOptions = new BitmapFactory.Options();
+ mOptions.inTempStorage = new byte[32768];
+ }
- return photo;
+ @Override
+ public View doInBackground(Void... unused) {
+ log("load a new photo");
+ final PhotoTable table = PhotoTable.this;
+ return applyFrame(PhotoTable.this, mOptions,
+ table.mPhotoSource.next(mOptions,
+ table.mLongSide, table.mShortSide));
}
@Override
@@ -287,16 +560,26 @@ public class PhotoTable extends FrameLayout {
final PhotoTable table = PhotoTable.this;
table.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT));
+ LayoutParams.WRAP_CONTENT));
if (table.hasSelection()) {
- table.bringChildToFront(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);
+ if (mOnTable.size() > mTableCapacity) {
+ int targetSize = Math.max(0, mOnTable.size() - mRedealCount);
+ while (mOnTable.size() > targetSize) {
+ fadeAway(mOnTable.poll(), false);
+ }
+ }
+
if(table.mOnTable.size() < table.mTableCapacity) {
table.scheduleNext(table.mFastDropPeriod);
}
@@ -304,14 +587,11 @@ public class PhotoTable extends FrameLayout {
}
};
+ /** Bring a new photo onto the table. */
public void launch() {
log("launching");
- setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
- if (hasSelection() &&
- (System.currentTimeMillis() - mSelectedTime) > MAX_SELECTION_TIME) {
- dropOnTable(getSelected());
- clearSelection();
- } else {
+ setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
+ if (!hasSelection()) {
log("inflate it");
if (mPhotoLaunchTask == null ||
mPhotoLaunchTask.getStatus() == AsyncTask.Status.FINISHED) {
@@ -320,18 +600,52 @@ public class PhotoTable extends FrameLayout {
}
}
}
+
+ /** De-emphasize the other photos on the table. */
+ public void fadeOutBackground(final View photo) {
+ mBackground.animate()
+ .withLayer()
+ .setDuration(mPickUpDuration)
+ .alpha(0f);
+ }
+
+
+ /** Return the other photos to foreground status. */
+ public void fadeInBackground(final View photo) {
+ mAnimating.add(photo);
+ mBackground.animate()
+ .withLayer()
+ .setDuration(mPickUpDuration)
+ .alpha(1f)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mAnimating.remove(photo);
+ if (!mAnimating.contains(photo)) {
+ moveToBackground(photo);
+ }
+ }
+ });
+ }
+
+ /** 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);
+ exitStageLeft(photo);
+ photo.setOnTouchListener(null);
photo.animate().cancel();
photo.animate()
.withLayer()
.alpha(0f)
- .setDuration(1000)
+ .setDuration(mPickUpDuration)
.withEndAction(new Runnable() {
@Override
public void run() {
- removeView(photo);
+ if (photo == getFocus()) {
+ clearFocus();
+ }
+ mStageLeft.removeView(photo);
recycle(photo);
if (replace) {
scheduleNext(mNowDropDelay);
@@ -340,19 +654,62 @@ public class PhotoTable extends FrameLayout {
});
}
- public void moveToBackOfQueue(View photo) {
+ /** 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);
+ if (isInBackground(photo)) {
+ mBackground.bringChildToFront(photo);
+ } else {
+ bringChildToFront(photo);
+ }
invalidate();
mOnTable.remove(photo);
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;
+
+ float offset = (((float) mWidth + scale * (photoWidth - 2f * mInset)) / 2f);
+ x += (slot == NEXT? 1f : -1f) * offset;
+
+ photo.animate()
+ .withLayer()
+ .rotation(0f)
+ .rotationY(0f)
+ .scaleX(scale)
+ .scaleY(scale)
+ .x(x)
+ .y(y)
+ .setDuration(mPickUpDuration)
+ .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);
@@ -360,10 +717,87 @@ public class PhotoTable extends FrameLayout {
dropOnTable(photo, mThrowInterpolator);
}
+ public void move(final View photo, float dx, float dy, boolean drop) {
+ if (photo != null) {
+ final float x = photo.getX() + dx;
+ final float y = photo.getY() + dy;
+ photo.setX(x);
+ photo.setY(y);
+ Log.d(TAG, "[" + photo.getX() + ", " + photo.getY() + "] + (" + dx + "," + dy + ")");
+ if (drop && photoOffTable(photo)) {
+ fadeAway(photo, true);
+ }
+ }
+ }
+
+ /** Fling with no touch hints, then land off screen. */
+ public void fling(final View photo) {
+ final float[] o = { mWidth + mLongSide / 2f,
+ mHeight + mLongSide / 2f };
+ final float[] a = { photo.getX(), photo.getY() };
+ final float[] b = { o[0], a[1] + o[0] - a[0] };
+ final float[] c = { a[0] + o[1] - a[1], o[1] };
+ float[] delta = { 0f, 0f };
+ if (Math.hypot(b[0] - a[0], b[1] - a[1]) < Math.hypot(c[0] - a[0], c[1] - a[1])) {
+ delta[0] = b[0] - a[0];
+ delta[1] = b[1] - a[1];
+ } else {
+ delta[0] = c[0] - a[0];
+ delta[1] = c[1] - a[1];
+ }
+
+ final float dist = (float) Math.hypot(delta[0], delta[1]);
+ final int duration = (int) (1000f * dist / mThrowSpeed);
+ 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 == getFocus()) {
+ if (moveFocus(photo, 0f) == null) {
+ moveFocus(photo, 180f);
+ }
+ }
+ moveToForeground(photo);
+ ViewPropertyAnimator animator = photo.animate()
+ .withLayer()
+ .xBy(dx)
+ .yBy(dy)
+ .setDuration(duration)
+ .setInterpolator(new DecelerateInterpolator(2f));
+
+ if (spin) {
+ animator.rotation(mThrowRotation);
+ }
+
+ if (photoOffTable(photo, (int) dx, (int) dy)) {
+ log("fling away");
+ animator.withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ fadeAway(photo, true);
+ }
+ });
+ }
+ }
+ public boolean photoOffTable(final View photo) {
+ return photoOffTable(photo, 0, 0);
+ }
+
+ public boolean photoOffTable(final View photo, final int dx, final int dy) {
+ Rect hit = new Rect();
+ photo.getHitRect(hit);
+ hit.offset(dx, dy);
+ return (hit.bottom < 0f || hit.top > getHeight() ||
+ 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(),
@@ -372,46 +806,73 @@ public class PhotoTable extends FrameLayout {
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;
- float dist = (float) (Math.sqrt(dx * dx + dy * dy));
+ float dist = (float) Math.hypot(dx, dy);
int duration = (int) (1000f * dist / mThrowSpeed);
duration = Math.max(duration, 1000);
log("animate it");
// toss onto table
+ mAnimating.add(photo);
photo.animate()
- .scaleX(mTableRatio / mImageRatio)
- .scaleY(mTableRatio / mImageRatio)
- .rotation(angle)
- .x(x)
- .y(y)
- .setDuration(duration)
- .setInterpolator(interpolator)
- .withEndAction(new Runnable() {
- @Override
- public void run() {
- if (mOnTable.size() > mTableCapacity) {
- while (mOnTable.size() > (mTableCapacity - mRedealCount)) {
- fadeAway(mOnTable.poll(), false);
- }
- // zero delay because we already waited duration ms
- scheduleNext(0);
- }
- }
- });
+ .withLayer()
+ .scaleX(mTableRatio / mImageRatio)
+ .scaleY(mTableRatio / mImageRatio)
+ .rotation(angle)
+ .x(x)
+ .y(y)
+ .setDuration(duration)
+ .setInterpolator(interpolator)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mAnimating.remove(photo);
+ if (!mAnimating.contains(photo)) {
+ moveToBackground(photo);
+ }
+ }
+ });
+ }
+
+ private void moveToBackground(View photo) {
+ if (!isInBackground(photo)) {
+ removeView(photo);
+ mBackground.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+ }
+ }
+
+ private void exitStageLeft(View photo) {
+ if (isInBackground(photo)) {
+ mBackground.removeView(photo);
+ } else {
+ removeView(photo);
+ }
+ mStageLeft.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+ }
+
+ private void moveToForeground(View photo) {
+ if (isInBackground(photo)) {
+ mBackground.removeView(photo);
+ addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+ }
+ }
+
+ private boolean isInBackground(View photo) {
+ return mBackground.indexOfChild(photo) != -1;
}
/** wrap all orientations to the interval [-180, 180). */
@@ -422,69 +883,112 @@ public class PhotoTable extends FrameLayout {
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;
- 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 / 600f);
- duration = Math.max(duration, 500);
-
photo.setRotation(wrapAngle(photo.getRotation()));
log("animate it");
- // toss onto table
+ // lift up to the glass for a good look
+ moveToForeground(photo);
photo.animate()
- .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());
- }
- });
+ .withLayer()
+ .rotation(0f)
+ .rotationY(0f)
+ .alpha(1f)
+ .scaleX(scale)
+ .scaleY(scale)
+ .x(x)
+ .y(y)
+ .setDuration(mPickUpDuration)
+ .setInterpolator(new DecelerateInterpolator(2f))
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ 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);
+ mPhotoSource.recycle(getBitmap(photo));
+ }
}
+ public void setHighlight(View photo, boolean highlighted) {
+ ImageView image = (ImageView) photo;
+ LayerDrawable layers = (LayerDrawable) image.getDrawable();
+ if (highlighted) {
+ layers.getDrawable(1).setColorFilter(mHighlightColor, PorterDuff.Mode.SRC_IN);
+ } else {
+ layers.getDrawable(1).clearColorFilter();
+ }
+ }
+
+ /** Schedule the first launch. Idempotent. */
public void start() {
if (!mStarted) {
log("kick it");
mStarted = true;
- scheduleNext(mDropPeriod);
- launch();
+ scheduleNext(0);
}
}
+ public void refreshSelection() {
+ scheduleSelectionReaper(mMaxFocusTime);
+ }
+
+ public void scheduleSelectionReaper(int delay) {
+ removeCallbacks(mSelectionReaper);
+ postDelayed(mSelectionReaper, delay);
+ }
+
+ public void refreshFocus() {
+ scheduleFocusReaper(mMaxFocusTime);
+ }
+
+ public void scheduleFocusReaper(int delay) {
+ removeCallbacks(mFocusReaper);
+ postDelayed(mFocusReaper, delay);
+ }
+
public void scheduleNext(int delay) {
removeCallbacks(mLauncher);
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 6f7e9f1..7ae8df3 100644
--- a/src/com/android/dreams/phototable/PhotoTableDreamSettings.java
+++ b/src/com/android/dreams/phototable/PhotoTableDreamSettings.java
@@ -15,48 +15,20 @@
*/
package com.android.dreams.phototable;
-import android.content.SharedPreferences;
-import android.app.ListActivity;
-import android.os.AsyncTask;
import android.os.Bundle;
-import android.widget.ListAdapter;
-
-import java.util.LinkedList;
/**
* Settings panel for photo flipping dream.
*/
-public class PhotoTableDreamSettings extends ListActivity {
+public class PhotoTableDreamSettings extends FlipperDreamSettings {
+ @SuppressWarnings("unused")
private static final String TAG = "PhotoTableDreamSettings";
public static final String PREFS_NAME = PhotoTableDream.TAG;
- private PhotoSourcePlexor mPhotoSource;
- private ListAdapter mAdapter;
- private SharedPreferences mSettings;
-
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
-
mSettings = getSharedPreferences(PREFS_NAME, 0);
- mPhotoSource = new PhotoSourcePlexor(this, mSettings);
- setContentView(R.layout.settingslist);
-
- new AsyncTask<Void, Void, Void>() {
- @Override
- public Void doInBackground(Void... unused) {
- mAdapter = new SectionedAlbumDataAdapter(PhotoTableDreamSettings.this,
- mSettings,
- R.layout.header,
- R.layout.album,
- new LinkedList<PhotoSource.AlbumData>(mPhotoSource.findAlbums()));
- return null;
- }
-
- @Override
- public void onPostExecute(Void unused) {
- setListAdapter(mAdapter);
- }
- }.execute();
+ init();
}
}
diff --git a/src/com/android/dreams/phototable/PhotoTouchListener.java b/src/com/android/dreams/phototable/PhotoTouchListener.java
index 8076e72..8bcec6b 100644
--- a/src/com/android/dreams/phototable/PhotoTouchListener.java
+++ b/src/com/android/dreams/phototable/PhotoTouchListener.java
@@ -21,8 +21,6 @@ import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.ViewPropertyAnimator;
-import android.view.animation.DecelerateInterpolator;
/**
* Touch listener that implements phototable interactions.
@@ -36,7 +34,6 @@ public class PhotoTouchListener implements View.OnTouchListener {
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;
@@ -54,16 +51,14 @@ public class PhotoTouchListener implements View.OnTouchListener {
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);
}
@@ -120,29 +115,7 @@ public class PhotoTouchListener implements View.OnTouchListener {
final float x1 = x0 + s * dX / v;
final float y1 = y0 + s * dY / v;
- final float photoWidth = ((Integer) target.getTag(R.id.photo_width)).floatValue();
- final float photoHeight = ((Integer) target.getTag(R.id.photo_height)).floatValue();
- final float tableWidth = mTable.getWidth();
- final float tableHeight = mTable.getHeight();
- final float halfShortSide =
- Math.min(photoWidth * mTableRatio, photoHeight * mTableRatio) / 2f;
- final View photo = target;
- ViewPropertyAnimator animator = photo.animate()
- .xBy(x1 - x0)
- .yBy(y1 - y0)
- .setDuration((int) (1000f * n / 60f))
- .setInterpolator(new DecelerateInterpolator(2f));
-
- if (y1 + halfShortSide < 0f || y1 - halfShortSide > tableHeight ||
- x1 + halfShortSide < 0f || x1 - halfShortSide > tableWidth) {
- log("fling away");
- animator.withEndAction(new Runnable() {
- @Override
- public void run() {
- mTable.fadeAway(photo, true);
- }
- });
- }
+ mTable.fling(target, x1 - x0, y1 - y0, (int) (1000f * n / 60f), false);
}
@Override
@@ -158,7 +131,7 @@ public class PhotoTouchListener implements View.OnTouchListener {
switch (action) {
case MotionEvent.ACTION_DOWN:
- mTable.moveToBackOfQueue(target);
+ mTable.moveToTopOfPile(target);
mInitialTouchTime = ev.getEventTime();
mA = ev.getPointerId(ev.getActionIndex());
resetTouch(target);
@@ -208,16 +181,16 @@ public class PhotoTouchListener implements View.OnTouchListener {
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);
}
}
}
@@ -234,13 +207,19 @@ public class PhotoTouchListener implements View.OnTouchListener {
}
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 9513d3c..0db98af 100644
--- a/src/com/android/dreams/phototable/PicasaSource.java
+++ b/src/com/android/dreams/phototable/PicasaSource.java
@@ -26,7 +26,6 @@ import android.view.WindowManager;
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 @@ import java.util.Set;
/**
* 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,10 +60,10 @@ public class PicasaSource extends PhotoSource {
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";
+ private static final String PICASA_UPLOADAUTO_TYPE = "InstantUploadAuto";
private final int mMaxPostAblums;
private final String mPostsAlbumName;
@@ -75,13 +74,13 @@ public class PicasaSource extends PhotoSource {
private final int mMaxRecycleSize;
private Set<String> mFoundAlbumIds;
- private int mNextPosition;
+ private int mLastPosition;
private int mDisplayLongSide;
public PicasaSource(Context context, SharedPreferences settings) {
super(context, settings);
mSourceName = TAG;
- mNextPosition = -1;
+ mLastPosition = INVALID;
mMaxPostAblums = mResources.getInteger(R.integer.max_post_albums);
mPostsAlbumName = mResources.getString(R.string.posts_album_name, "Posts");
mUploadsAlbumName = mResources.getString(R.string.uploads_album_name, "Instant Uploads");
@@ -90,6 +89,7 @@ public class PicasaSource extends PhotoSource {
mConnectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
mRecycleBin = new LinkedList<ImageData>();
+
fillQueue();
mDisplayLongSide = getDisplayLongSide();
}
@@ -103,6 +103,65 @@ public class PicasaSource extends PhotoSource {
}
@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 +176,6 @@ public class PicasaSource extends PhotoSource {
}
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)) {
@@ -162,41 +220,30 @@ public class PicasaSource extends PhotoSource {
Cursor cursor = mResolver.query(picasaUriBuilder.build(),
projection, selection.toString(), null, null);
if (cursor != null) {
- if (cursor.getCount() > howMany && mNextPosition == -1) {
- mNextPosition =
- (int) Math.abs(mRNG.nextInt() % (cursor.getCount() - howMany));
- }
- if (mNextPosition == -1) {
- mNextPosition = 0;
+ if (cursor.getCount() > howMany && mLastPosition == INVALID) {
+ mLastPosition = pickRandomStart(cursor.getCount(), howMany);
}
- log(TAG, "moving to position: " + mNextPosition);
- cursor.moveToPosition(mNextPosition);
+
+ log(TAG, "moving to position: " + mLastPosition);
+ 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 (foundImages.size() < howMany && !cursor.isAfterLast()) {
+ 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);
}
- if (cursor.moveToNext()) {
- mNextPosition++;
- }
+ mLastPosition = cursor.getPosition();
}
if (cursor.isAfterLast()) {
- mNextPosition = 0;
+ mLastPosition = -1;
+ }
+ if (cursor.isBeforeFirst()) {
+ mLastPosition = INVALID;
}
}
@@ -254,17 +301,15 @@ public class PicasaSource extends PhotoSource {
projection, selection, null, order);
if (cursor != null) {
log(TAG, " " + id + " resolved to " + cursor.getCount() + " albums");
- cursor.moveToFirst();
+ 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!");
} else {
- while (!cursor.isAfterLast()) {
+ while (cursor.moveToNext()) {
albumIds.add(cursor.getString(idIndex));
- cursor.moveToNext();
}
}
cursor.close();
@@ -296,7 +341,7 @@ public class PicasaSource extends PhotoSource {
Cursor cursor = mResolver.query(picasaUriBuilder.build(),
projection, null, null, null);
if (cursor != null) {
- cursor.moveToFirst();
+ cursor.moveToPosition(-1);
int idIndex = cursor.getColumnIndex(PICASA_ID);
int thumbIndex = cursor.getColumnIndex(PICASA_THUMB);
@@ -308,12 +353,13 @@ public class PicasaSource extends PhotoSource {
if (idIndex < 0) {
log(TAG, "can't find the ID column!");
} else {
- while (!cursor.isAfterLast()) {
+ while (cursor.moveToNext()) {
String id = TAG + ":" + cursor.getString(idIndex);
String user = (userIndex >= 0 ? cursor.getString(userIndex) : "-1");
String type = (typeIndex >= 0 ? cursor.getString(typeIndex) : "none");
boolean isPosts = (typeIndex >= 0 && PICASA_POSTS_TYPE.equals(type));
- boolean isUpload = (typeIndex >= 0 && PICASA_UPLOAD_TYPE.equals(type));
+ boolean isUpload = (typeIndex >= 0 &&
+ (PICASA_UPLOAD_TYPE.equals(type) || PICASA_UPLOADAUTO_TYPE.equals(type)));
String account = accounts.get(user);
if (account == null) {
@@ -367,8 +413,6 @@ public class PicasaSource extends PhotoSource {
if (data.thumbnailUrl == null || data.updated == updated) {
data.thumbnailUrl = thumbnailUrl;
}
-
- cursor.moveToNext();
}
}
cursor.close();
@@ -403,9 +447,6 @@ public class PicasaSource extends PhotoSource {
} 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 6dc3ddf..42f2eb0 100644
--- a/src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java
+++ b/src/com/android/dreams/phototable/SectionedAlbumDataAdapter.java
@@ -21,10 +21,9 @@ import android.database.DataSetObserver;
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 @@ public class SectionedAlbumDataAdapter extends DataSetObserver implements ListAd
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());
@@ -53,6 +50,16 @@ public class SectionedAlbumDataAdapter extends DataSetObserver implements ListAd
mAlbumData.registerDataSetObserver(this);
}
+ boolean areAllSelected() {
+ return mAlbumData != null && mAlbumData.areAllSelected();
+ }
+
+ void selectAll(boolean select) {
+ if (mAlbumData != null) {
+ mAlbumData.selectAll(select);
+ }
+ }
+
// DataSetObserver
@Override
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 @@ public class SoftLandingInterpolator implements Interpolator {
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 @@ public class SoftLandingInterpolator implements Interpolator {
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..be2a860 100644
--- a/src/com/android/dreams/phototable/StockSource.java
+++ b/src/com/android/dreams/phototable/StockSource.java
@@ -17,45 +17,55 @@ package com.android.dreams.phototable;
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 @@ StockSource extends PhotoSource {
@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 @@ StockSource extends PhotoSource {
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,23 @@ StockSource extends PhotoSource {
return is;
}
+
+ @Override
+ public ImageData naturalNext(ImageData current) {
+ int idx = Integer.valueOf(current.id);
+ idx = (idx + 1) % PHOTOS.length;
+ return mImageCache.get(idx);
+ }
+
+ @Override
+ public ImageData naturalPrevious(ImageData current) {
+ int idx = Integer.valueOf(current.id);
+ idx = (PHOTOS.length + idx - 1) % PHOTOS.length;
+ return mImageCache.get(idx);
+ }
+
+ @Override
+ protected void donePaging(ImageData current) {
+ }
}