diff options
author | Chris Wren <cwren@android.com> | 2013-04-04 09:17:26 -0400 |
---|---|---|
committer | Chris Wren <cwren@android.com> | 2013-04-09 01:54:16 -0400 |
commit | 88d80f4471c900628e2cb6eef23029b99af48e09 (patch) | |
tree | 6829842f0dff21db530936037cd242853f43b416 /src/com/android | |
parent | 1dc94b55d59f5c4dbf86788d0c32b7ac4449909a (diff) | |
download | android_packages_screensavers_PhotoTable-88d80f4471c900628e2cb6eef23029b99af48e09.tar.gz android_packages_screensavers_PhotoTable-88d80f4471c900628e2cb6eef23029b99af48e09.tar.bz2 android_packages_screensavers_PhotoTable-88d80f4471c900628e2cb6eef23029b99af48e09.zip |
story mode for PhotoTable.
also some cleanup and refactoring
also fix stuck alphas
Bug: 8399588
Change-Id: Id236b29701ede3696c8f1f0ccc6522eb8256ff25
Diffstat (limited to 'src/com/android')
20 files changed, 713 insertions, 279 deletions
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 @@ 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. 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 class AlbumSettings { 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 @@ package com.android.dreams.phototable; 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 @@ public class DragGestureDetector { 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 @@ public class DragGestureDetector { } 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 @@ 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; @@ -61,7 +62,7 @@ public class EdgeSwipeDetector { * 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 @@ 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; 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 @@ 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; @@ -113,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>(); @@ -138,23 +191,18 @@ public class LocalSource extends PhotoSource { 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 @@ 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 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.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; @@ -191,9 +190,9 @@ public class PhotoCarousel extends FrameLayout { 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 @@ 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) @@ -286,7 +285,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 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.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; @@ -43,9 +42,6 @@ public abstract class PhotoSource { 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 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 class AlbumData { @@ -79,6 +85,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; @@ -102,6 +109,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; } @@ -124,11 +132,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; } @@ -259,7 +267,38 @@ public abstract class PhotoSource { } } + 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 @@ 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,14 @@ 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(); + } } 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.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; @@ -39,8 +37,9 @@ 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.Formatter; import java.util.LinkedList; import java.util.Random; @@ -68,8 +67,8 @@ public class PhotoTable extends FrameLayout { 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 @@ public class PhotoTable extends FrameLayout { 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 @@ public class PhotoTable extends FrameLayout { 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 @@ public class PhotoTable extends FrameLayout { 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 class PhotoTable extends FrameLayout { } 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()); + } + for (int slot = 0; slot < mOnDeck.length; slot++) { + if (mOnDeck[slot] != null) { + fadeAway(mOnDeck[slot], false); + mOnDeck[slot] = null; + } } - mSelected = 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 @@ 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); @@ -233,7 +288,7 @@ 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; } @@ -241,10 +296,6 @@ public class PhotoTable extends FrameLayout { 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 @@ public class PhotoTable extends FrameLayout { 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 @@ 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; @@ -400,12 +389,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(); @@ -416,47 +411,101 @@ 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, + 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 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; + 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); + } - 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; + @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 { - 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; - return photo; + public PhotoLaunchTask () { + mOptions = new BitmapFactory.Options(); + mOptions.inTempStorage = new byte[32768]; + } + + @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 @@ -465,12 +514,15 @@ 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.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 @@ public class PhotoTable extends FrameLayout { } }; + /** 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 @@ public class PhotoTable extends FrameLayout { } } } + + /** 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 @@ public class PhotoTable extends FrameLayout { .withEndAction(new Runnable() { @Override public void run() { - removeView(photo); + if (photo == getFocus()) { + clearFocus(); + } recycle(photo); if (replace) { scheduleNext(mNowDropDelay); @@ -527,6 +581,7 @@ public class PhotoTable extends FrameLayout { }); } + /** 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 @@ public class PhotoTable extends FrameLayout { 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 @@ public class PhotoTable extends FrameLayout { } } + /** 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 @@ public class PhotoTable extends FrameLayout { 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 @@ public class PhotoTable extends FrameLayout { 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 @@ 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; @@ -668,12 +767,14 @@ 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; @@ -690,9 +791,10 @@ public class PhotoTable extends FrameLayout { 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 @@ public class PhotoTable extends FrameLayout { .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 @@ public class PhotoTable extends FrameLayout { } } + /** Schedule the first launch. Idempotent. */ public void start() { if (!mStarted) { log("kick it"); @@ -747,9 +869,11 @@ public class PhotoTable extends FrameLayout { 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 @@ 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; @@ -52,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); } @@ -184,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); } } } @@ -210,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 ef4a7c4..eb8fd1f 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,7 +60,6 @@ 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"; @@ -90,6 +88,7 @@ public class PicasaSource extends PhotoSource { mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); mRecycleBin = new LinkedList<ImageData>(); + fillQueue(); mDisplayLongSide = getDisplayLongSide(); } @@ -103,6 +102,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 +175,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)) { @@ -170,22 +227,13 @@ public class PicasaSource extends PhotoSource { 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 @@ public class PicasaSource extends PhotoSource { 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 @@ 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 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.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()); 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..d7b3500 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,17 @@ StockSource extends PhotoSource { 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); + } } |