1c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren/*
2c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren * Copyright (C) 2012 The Android Open Source Project
3c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren *
4c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren * Licensed under the Apache License, Version 2.0 (the "License");
5c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren * you may not use this file except in compliance with the License.
6c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren * You may obtain a copy of the License at
7c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren *
8c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren *      http://www.apache.org/licenses/LICENSE-2.0
9c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren *
10c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren * Unless required by applicable law or agreed to in writing, software
11c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren * distributed under the License is distributed on an "AS IS" BASIS,
12c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren * See the License for the specific language governing permissions and
14c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren * limitations under the License.
15c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren */
16c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wrenpackage com.android.dreams.phototable;
17c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren
18d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.content.Context;
19d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.content.res.Resources;
20d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.Bitmap;
21d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.BitmapFactory;
22d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.PointF;
23eb70217554d14807d73cae033b4f756c9b80fe3bChris Wrenimport android.graphics.PorterDuff;
241dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wrenimport android.graphics.Rect;
25d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.drawable.BitmapDrawable;
26d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.drawable.Drawable;
27d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.drawable.LayerDrawable;
28d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.os.AsyncTask;
2988d80f4471c900628e2cb6eef23029b99af48e09Chris Wrenimport android.service.dreams.DreamService;
30d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.util.AttributeSet;
31d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.util.Log;
32eb70217554d14807d73cae033b4f756c9b80fe3bChris Wrenimport android.view.KeyEvent;
33d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.view.LayoutInflater;
34d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.view.MotionEvent;
35d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.view.View;
36f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wrenimport android.view.ViewGroup;
37eb70217554d14807d73cae033b4f756c9b80fe3bChris Wrenimport android.view.ViewPropertyAnimator;
38d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.view.animation.DecelerateInterpolator;
3976086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wrenimport android.view.animation.Interpolator;
40d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.widget.FrameLayout;
41d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.widget.ImageView;
4288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
43f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wrenimport java.util.ArrayList;
4488d80f4471c900628e2cb6eef23029b99af48e09Chris Wrenimport java.util.Formatter;
45f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wrenimport java.util.HashSet;
46d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport java.util.LinkedList;
473910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wrenimport java.util.List;
48d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport java.util.Random;
49f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wrenimport java.util.Set;
50c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren
51c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren/**
52d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren * A surface where photos sit.
53c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren */
54d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenpublic class PhotoTable extends FrameLayout {
55c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren    private static final String TAG = "PhotoTable";
56d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private static final boolean DEBUG = false;
57d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
58d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    class Launcher implements Runnable {
59eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        @Override
60eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        public void run() {
61eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            PhotoTable.this.scheduleNext(mDropPeriod);
62eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            PhotoTable.this.launch();
63d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
64eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
65d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
66eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    class FocusReaper implements Runnable {
67d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        @Override
68d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        public void run() {
69eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            PhotoTable.this.clearFocus();
70d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
71d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
72d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
7384c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren    class SelectionReaper implements Runnable {
7484c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        @Override
7584c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        public void run() {
7684c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren            PhotoTable.this.clearSelection();
7784c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        }
7884c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren    }
7984c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren
8088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private static final int NEXT = 1;
8188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private static final int PREV = 0;
82d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private static Random sRNG = new Random();
83d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
84d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final Launcher mLauncher;
85eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    private final FocusReaper mFocusReaper;
8684c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren    private final SelectionReaper mSelectionReaper;
87d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final LinkedList<View> mOnTable;
88d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final int mDropPeriod;
89d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final int mFastDropPeriod;
90d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final int mNowDropDelay;
91d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final float mImageRatio;
92d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final float mTableRatio;
93d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final float mImageRotationLimit;
9476086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    private final float mThrowRotation;
9576086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    private final float mThrowSpeed;
96d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final boolean mTapToExit;
97d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final int mTableCapacity;
98f98687b22f4091083287ab7de7a0f3102bd9c1baChris Wren    private final int mRedealCount;
99d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final int mInset;
100f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    private final PhotoSource mPhotoSource;
101d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final Resources mResources;
10276086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    private final Interpolator mThrowInterpolator;
10376086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    private final Interpolator mDropInterpolator;
10488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private final DragGestureDetector mDragGestureDetector;
10588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private final EdgeSwipeDetector mEdgeSwipeDetector;
10688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private final KeyboardInterpreter mKeyboardInterpreter;
10788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private final boolean mStoryModeEnabled;
108a924553fc2a5bf5ab88ac031adf154480b970bd9Chris Wren    private final long mPickUpDuration;
10984c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren    private final int mMaxSelectionTime;
11084c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren    private final int mMaxFocusTime;
111f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    private final List<View> mAnimating;
11284c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren
1131c1faec579b96aeac7416a80a7ee7155a813fffeDianne Hackborn    private DreamService mDream;
114d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private PhotoLaunchTask mPhotoLaunchTask;
11588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private LoadNaturalSiblingTask mLoadOnDeckTasks[];
116d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private boolean mStarted;
117d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private boolean mIsLandscape;
118d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private int mLongSide;
119d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private int mShortSide;
120d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private int mWidth;
121d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private int mHeight;
12288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private View mSelection;
12388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private View mOnDeck[];
12488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private View mFocus;
125eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    private int mHighlightColor;
126f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    private ViewGroup mBackground;
127f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    private ViewGroup mStageLeft;
128d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
129651ee92128e1899957758abeeb1c2c669f240093Chris Wren    public PhotoTable(Context context, AttributeSet as) {
130651ee92128e1899957758abeeb1c2c669f240093Chris Wren        super(context, as);
131d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mResources = getResources();
132d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mInset = mResources.getDimensionPixelSize(R.dimen.photo_inset);
13370ef3e7da59212c3c4baedf92f418eb9012a4db3Chris Wren        mDropPeriod = mResources.getInteger(R.integer.table_drop_period);
134d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mFastDropPeriod = mResources.getInteger(R.integer.fast_drop);
135d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mNowDropDelay = mResources.getInteger(R.integer.now_drop);
136d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mImageRatio = mResources.getInteger(R.integer.image_ratio) / 1000000f;
137d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mTableRatio = mResources.getInteger(R.integer.table_ratio) / 1000000f;
138d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mImageRotationLimit = (float) mResources.getInteger(R.integer.max_image_rotation);
13976086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        mThrowSpeed = mResources.getDimension(R.dimen.image_throw_speed);
140a924553fc2a5bf5ab88ac031adf154480b970bd9Chris Wren        mPickUpDuration = mResources.getInteger(R.integer.photo_pickup_duration);
14176086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        mThrowRotation = (float) mResources.getInteger(R.integer.image_throw_rotatioan);
142d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mTableCapacity = mResources.getInteger(R.integer.table_capacity);
143f98687b22f4091083287ab7de7a0f3102bd9c1baChris Wren        mRedealCount = mResources.getInteger(R.integer.redeal_count);
144d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mTapToExit = mResources.getBoolean(R.bool.enable_tap_to_exit);
14588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        mStoryModeEnabled = mResources.getBoolean(R.bool.enable_story_mode);
146eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        mHighlightColor = mResources.getColor(R.color.highlight_color);
14784c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        mMaxSelectionTime = mResources.getInteger(R.integer.max_selection_time);
14884c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        mMaxFocusTime = mResources.getInteger(R.integer.max_focus_time);
14976086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        mThrowInterpolator = new SoftLandingInterpolator(
15076086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren                mResources.getInteger(R.integer.soft_landing_time) / 1000000f,
15176086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren                mResources.getInteger(R.integer.soft_landing_distance) / 1000000f);
15276086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        mDropInterpolator = new DecelerateInterpolator(
15376086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren                (float) mResources.getInteger(R.integer.drop_deceleration_exponent));
154d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mOnTable = new LinkedList<View>();
155d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mPhotoSource = new PhotoSourcePlexor(getContext(),
156d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                getContext().getSharedPreferences(PhotoTableDreamSettings.PREFS_NAME, 0));
157f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        mAnimating = new ArrayList<View>();
158eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        mLauncher = new Launcher();
159eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        mFocusReaper = new FocusReaper();
16084c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        mSelectionReaper = new SelectionReaper();
1611dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        mDragGestureDetector = new DragGestureDetector(context, this);
16288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        mEdgeSwipeDetector = new EdgeSwipeDetector(context, this);
16388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        mKeyboardInterpreter = new KeyboardInterpreter(this);
16488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        mLoadOnDeckTasks = new LoadNaturalSiblingTask[2];
16588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        mOnDeck = new View[2];
166d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mStarted = false;
167d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
168d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
169f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    @Override
170f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    public void onFinishInflate() {
171f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        mBackground = (ViewGroup) findViewById(R.id.background);
172f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        mStageLeft = (ViewGroup) findViewById(R.id.stageleft);
173f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    }
174387363c426e097c358bd41367e0a8f4339fc8dc3Chris Wren
1751c1faec579b96aeac7416a80a7ee7155a813fffeDianne Hackborn    public void setDream(DreamService dream) {
176651ee92128e1899957758abeeb1c2c669f240093Chris Wren        mDream = dream;
177651ee92128e1899957758abeeb1c2c669f240093Chris Wren    }
178651ee92128e1899957758abeeb1c2c669f240093Chris Wren
179d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public boolean hasSelection() {
18088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        return mSelection != null;
181d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
182d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
18388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    public View getSelection() {
18488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        return mSelection;
185d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
186d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
187d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void clearSelection() {
188eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        if (hasSelection()) {
189bcfd4439d730a4d783a02596c8ab444796323aadChris Wren            dropOnTable(mSelection);
190bcfd4439d730a4d783a02596c8ab444796323aadChris Wren            mPhotoSource.donePaging(getBitmap(mSelection));
1913910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren            if (mStoryModeEnabled) {
192f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                fadeInBackground(mSelection);
1933910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren            }
194bcfd4439d730a4d783a02596c8ab444796323aadChris Wren            mSelection = null;
19588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
19688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        for (int slot = 0; slot < mOnDeck.length; slot++) {
19788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            if (mOnDeck[slot] != null) {
19888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                fadeAway(mOnDeck[slot], false);
19988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                mOnDeck[slot] = null;
20088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            }
201a96a454c9084e98e20f263b435b536a194f25673Chris Wren            if (mLoadOnDeckTasks[slot] != null &&
202a96a454c9084e98e20f263b435b536a194f25673Chris Wren                    mLoadOnDeckTasks[slot].getStatus() != AsyncTask.Status.FINISHED) {
203a96a454c9084e98e20f263b435b536a194f25673Chris Wren                mLoadOnDeckTasks[slot].cancel(true);
204a96a454c9084e98e20f263b435b536a194f25673Chris Wren                mLoadOnDeckTasks[slot] = null;
205a96a454c9084e98e20f263b435b536a194f25673Chris Wren            }
206eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        }
207d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
208d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
209d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void setSelection(View selected) {
21088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (selected != null) {
21188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            clearSelection();
21288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            mSelection = selected;
21388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            promoteSelection();
2143910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren            if (mStoryModeEnabled) {
215f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                fadeOutBackground(mSelection);
2163910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren            }
21788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
21888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    }
21988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
22088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    public void selectNext() {
22188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (mStoryModeEnabled) {
22288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            log("selectNext");
22388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            if (hasSelection() && mOnDeck[NEXT] != null) {
22488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                placeOnDeck(mSelection, PREV);
22588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                mSelection = mOnDeck[NEXT];
22688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                mOnDeck[NEXT] = null;
22788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                promoteSelection();
22888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            }
22988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        } else {
23088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            clearSelection();
23188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
23288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    }
23388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
23488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    public void selectPrevious() {
23588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (mStoryModeEnabled) {
23688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            log("selectPrevious");
23788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            if (hasSelection() && mOnDeck[PREV] != null) {
23888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                placeOnDeck(mSelection, NEXT);
23988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                mSelection = mOnDeck[PREV];
24088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                mOnDeck[PREV] = null;
24188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                promoteSelection();
24288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            }
24388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        } else {
24488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            clearSelection();
24588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
24688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    }
24788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
24888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private void promoteSelection() {
24988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (hasSelection()) {
25084c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren            scheduleSelectionReaper(mMaxSelectionTime);
25188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            mSelection.animate().cancel();
25288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            mSelection.setAlpha(1f);
25388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            moveToTopOfPile(mSelection);
25488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            pickUp(mSelection);
25588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            if (mStoryModeEnabled) {
25688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                for (int slot = 0; slot < mOnDeck.length; slot++) {
25788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    if (mLoadOnDeckTasks[slot] != null &&
25888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                            mLoadOnDeckTasks[slot].getStatus() != AsyncTask.Status.FINISHED) {
25988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                        mLoadOnDeckTasks[slot].cancel(true);
26088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    }
26188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    if (mOnDeck[slot] == null) {
26288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                        mLoadOnDeckTasks[slot] = new LoadNaturalSiblingTask(slot);
26388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                        mLoadOnDeckTasks[slot].execute(mSelection);
26488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    }
26588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                }
26688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            }
26788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
268d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
269d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
270eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    public boolean hasFocus() {
27188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        return mFocus != null;
272eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
273eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
27488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    public View getFocus() {
27588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        return mFocus;
276eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
277eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
278eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    public void clearFocus() {
279eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        if (hasFocus()) {
28088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            setHighlight(getFocus(), false);
281eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        }
28288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        mFocus = null;
28388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    }
28488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
28588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    public void setDefaultFocus() {
28688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        setFocus(mOnTable.getLast());
287eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
288eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
289eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    public void setFocus(View focus) {
290eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        assert(focus != null);
291eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        clearFocus();
29288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        mFocus = focus;
293eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        moveToTopOfPile(focus);
294eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        setHighlight(focus, true);
29584c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        scheduleFocusReaper(mMaxFocusTime);
296eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
297eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
298d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    static float lerp(float a, float b, float f) {
299d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return (b-a)*f + a;
300d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
301d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
302d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    static float randfrange(float a, float b) {
303d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return lerp(a, b, sRNG.nextFloat());
304d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
305d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
306d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    static PointF randFromCurve(float t, PointF[] v) {
307d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        PointF p = new PointF();
308d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (v.length == 4 && t >= 0f && t <= 1f) {
309d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            float a = (float) Math.pow(1f-t, 3f);
310d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            float b = (float) Math.pow(1f-t, 2f) * t;
311d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            float c = (1f-t) * (float) Math.pow(t, 2f);
312d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            float d = (float) Math.pow(t, 3f);
313d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
314d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            p.x = a * v[0].x + 3 * b * v[1].x + 3 * c * v[2].x + d * v[3].x;
315d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            p.y = a * v[0].y + 3 * b * v[1].y + 3 * c * v[2].y + d * v[3].y;
316d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
317d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return p;
318d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
319d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
320f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren    private static PointF randMultiDrop(int n, float i, float j, int width, int height) {
32188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        log("randMultiDrop (%d, %f, %f, %d, %d)", n, i, j, width, height);
322f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        final float[] cx = {0.3f, 0.3f, 0.5f, 0.7f, 0.7f};
323f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        final float[] cy = {0.3f, 0.7f, 0.5f, 0.3f, 0.7f};
324f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        n = Math.abs(n);
325f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        float x = cx[n % cx.length];
326f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        float y = cy[n % cx.length];
327f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        PointF p = new PointF();
328f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        p.x = x * width + 0.05f * width * i;
329f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        p.y = y * height + 0.05f * height * j;
33088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        log("randInCenter returning %f, %f", p.x, p.y);
331f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        return p;
332f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren    }
333f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren
334eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    private double cross(double[] a, double[] b) {
335eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        return a[0] * b[1] - a[1] * b[0];
336eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
337eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
338eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    private double norm(double[] a) {
339eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        return Math.hypot(a[0], a[1]);
340eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
341eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
342eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    private double[] getCenter(View photo) {
343eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue();
344eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue();
345eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        double[] center = { photo.getX() + width / 2f,
346eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                            - (photo.getY() + height / 2f) };
347eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        return center;
348eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
349eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
350eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    public View moveFocus(View focus, float direction) {
351eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        return moveFocus(focus, direction, 90f);
352eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
353eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
354eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    public View moveFocus(View focus, float direction, float angle) {
355eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        if (focus == null) {
356eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            setFocus(mOnTable.getLast());
357eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        } else {
358eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            final double alpha = Math.toRadians(direction);
359eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            final double beta = Math.toRadians(Math.min(angle, 180f) / 2f);
360eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            final double[] left = { Math.sin(alpha - beta),
361eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                                    Math.cos(alpha - beta) };
362eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            final double[] right = { Math.sin(alpha + beta),
363eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                                     Math.cos(alpha + beta) };
364eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            final double[] a = getCenter(focus);
365eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            View bestFocus = null;
366eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            double bestDistance = Double.MAX_VALUE;
367eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            for (View candidate: mOnTable) {
368eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                if (candidate != focus) {
369eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                    final double[] b = getCenter(candidate);
370eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                    final double[] delta = { b[0] - a[0],
371eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                                             b[1] - a[1] };
372eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                    if (cross(delta, left) > 0.0 && cross(delta, right) < 0.0) {
373eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                        final double distance = norm(delta);
374eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                        if (bestDistance > distance) {
375eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                            bestDistance = distance;
376eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                            bestFocus = candidate;
377eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                        }
378eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                    }
379eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                }
380eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            }
381eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            if (bestFocus == null) {
382eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                if (angle < 180f) {
383eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                    return moveFocus(focus, direction, 180f);
384eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                }
385eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            } else {
386eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                setFocus(bestFocus);
387eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            }
388eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        }
38988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        return getFocus();
390eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
391eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
392eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    @Override
393eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    public boolean onKeyDown(int keyCode, KeyEvent event) {
39488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        return mKeyboardInterpreter.onKeyDown(keyCode, event);
395eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
396eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
397d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    @Override
398bf44ca22d40dd60b4c30f6a979b80fbd2ffa0078Chris Wren    public boolean onGenericMotionEvent(MotionEvent event) {
3991dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        return mEdgeSwipeDetector.onTouchEvent(event) || mDragGestureDetector.onTouchEvent(event);
400bf44ca22d40dd60b4c30f6a979b80fbd2ffa0078Chris Wren    }
401bf44ca22d40dd60b4c30f6a979b80fbd2ffa0078Chris Wren
402bf44ca22d40dd60b4c30f6a979b80fbd2ffa0078Chris Wren    @Override
403d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public boolean onTouchEvent(MotionEvent event) {
404d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
405d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            if (hasSelection()) {
406d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                clearSelection();
407d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            } else  {
408651ee92128e1899957758abeeb1c2c669f240093Chris Wren                if (mTapToExit && mDream != null) {
409d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    mDream.finish();
410d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                }
411d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            }
412d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            return true;
413d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
414d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return false;
415d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
416c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren
417c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren    @Override
418d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
419d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        super.onLayout(changed, left, top, right, bottom);
42088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        log("onLayout (%d, %d, %d, %d)", left, top, right, bottom);
421d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
422d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mHeight = bottom - top;
423d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mWidth = right - left;
424d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
425d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mLongSide = (int) (mImageRatio * Math.max(mWidth, mHeight));
426d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mShortSide = (int) (mImageRatio * Math.min(mWidth, mHeight));
427d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
428d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        boolean isLandscape = mWidth > mHeight;
429d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (mIsLandscape != isLandscape) {
430d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            for (View photo: mOnTable) {
43188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                if (photo != getSelection()) {
432d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    dropOnTable(photo);
433d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                }
434d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            }
43588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            if (hasSelection()) {
43688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                pickUp(getSelection());
43788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                for (int slot = 0; slot < mOnDeck.length; slot++) {
43888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    if (mOnDeck[slot] != null) {
43988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                        placeOnDeck(mOnDeck[slot], slot);
44088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    }
44188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                }
44288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            }
443d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            mIsLandscape = isLandscape;
444d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
445d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        start();
446c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren    }
447c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren
448c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren    @Override
449d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public boolean isOpaque() {
450d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return true;
451d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
452d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
45388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Put a nice border on the bitmap. */
45488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private static View applyFrame(final PhotoTable table, final BitmapFactory.Options options,
455f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            Bitmap decodedPhoto) {
45688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        LayoutInflater inflater = (LayoutInflater) table.getContext()
45788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
45888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        View photo = inflater.inflate(R.layout.photo, null);
45988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        ImageView image = (ImageView) photo;
46088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        Drawable[] layers = new Drawable[2];
46188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        int photoWidth = options.outWidth;
46288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        int photoHeight = options.outHeight;
46388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (decodedPhoto == null || options.outWidth <= 0 || options.outHeight <= 0) {
46488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            photo = null;
46588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        } else {
46688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            decodedPhoto.setHasMipMap(true);
46788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            layers[0] = new BitmapDrawable(table.mResources, decodedPhoto);
46888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            layers[1] = table.mResources.getDrawable(R.drawable.frame);
46988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            LayerDrawable layerList = new LayerDrawable(layers);
47088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            layerList.setLayerInset(0, table.mInset, table.mInset,
47188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                                    table.mInset, table.mInset);
47288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            image.setImageDrawable(layerList);
47388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
47488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            photo.setTag(R.id.photo_width, Integer.valueOf(photoWidth));
47588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            photo.setTag(R.id.photo_height, Integer.valueOf(photoHeight));
47688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
47788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            photo.setOnTouchListener(new PhotoTouchListener(table.getContext(),
47888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                                                            table));
47988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
48088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        return photo;
48188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    }
48288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
48388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private class LoadNaturalSiblingTask extends AsyncTask<View, Void, View> {
484b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren        private final BitmapFactory.Options mOptions;
48588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        private final int mSlot;
486a96a454c9084e98e20f263b435b536a194f25673Chris Wren        private View mParent;
487b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren
48888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        public LoadNaturalSiblingTask (int slot) {
489b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren            mOptions = new BitmapFactory.Options();
490b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren            mOptions.inTempStorage = new byte[32768];
49188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            mSlot = slot;
492b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren        }
493b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren
494d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        @Override
49588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        public View doInBackground(View... views) {
49688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            log("load natural %s", (mSlot == NEXT ? "next" : "previous"));
497d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            final PhotoTable table = PhotoTable.this;
498a96a454c9084e98e20f263b435b536a194f25673Chris Wren            mParent = views[0];
499a96a454c9084e98e20f263b435b536a194f25673Chris Wren            final Bitmap current = getBitmap(mParent);
50088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            Bitmap decodedPhoto;
50188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            if (mSlot == NEXT) {
50288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                decodedPhoto = table.mPhotoSource.naturalNext(current,
50388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    mOptions, table.mLongSide, table.mShortSide);
50488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            } else {
50588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                decodedPhoto = table.mPhotoSource.naturalPrevious(current,
50688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    mOptions, table.mLongSide, table.mShortSide);
50788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            }
50888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            return applyFrame(PhotoTable.this, mOptions, decodedPhoto);
50988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
510d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
51188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        @Override
51288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        public void onPostExecute(View photo) {
51388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            if (photo != null) {
514a96a454c9084e98e20f263b435b536a194f25673Chris Wren                if (hasSelection() && getSelection() == mParent) {
515a96a454c9084e98e20f263b435b536a194f25673Chris Wren                    log("natural %s being rendered", (mSlot == NEXT ? "next" : "previous"));
516a96a454c9084e98e20f263b435b536a194f25673Chris Wren                    PhotoTable.this.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
517a96a454c9084e98e20f263b435b536a194f25673Chris Wren                            LayoutParams.WRAP_CONTENT));
518a96a454c9084e98e20f263b435b536a194f25673Chris Wren                    PhotoTable.this.mOnDeck[mSlot] = photo;
519a96a454c9084e98e20f263b435b536a194f25673Chris Wren                    float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue();
520a96a454c9084e98e20f263b435b536a194f25673Chris Wren                    float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue();
521a96a454c9084e98e20f263b435b536a194f25673Chris Wren                    photo.setX(mSlot == PREV ? -2 * width : mWidth + 2 * width);
522a96a454c9084e98e20f263b435b536a194f25673Chris Wren                    photo.setY((mHeight - height) / 2);
523a96a454c9084e98e20f263b435b536a194f25673Chris Wren                    photo.addOnLayoutChangeListener(new OnLayoutChangeListener() {
524a96a454c9084e98e20f263b435b536a194f25673Chris Wren                        @Override
525a96a454c9084e98e20f263b435b536a194f25673Chris Wren                        public void onLayoutChange(View v, int left, int top, int right, int bottom,
526a96a454c9084e98e20f263b435b536a194f25673Chris Wren                                int oldLeft, int oldTop, int oldRight, int oldBottom) {
527a96a454c9084e98e20f263b435b536a194f25673Chris Wren                            PhotoTable.this.placeOnDeck(v, mSlot);
528a96a454c9084e98e20f263b435b536a194f25673Chris Wren                            v.removeOnLayoutChangeListener(this);
529a96a454c9084e98e20f263b435b536a194f25673Chris Wren                        }
530a96a454c9084e98e20f263b435b536a194f25673Chris Wren                    });
531a96a454c9084e98e20f263b435b536a194f25673Chris Wren                } else {
532a96a454c9084e98e20f263b435b536a194f25673Chris Wren                   recycle(photo);
533a96a454c9084e98e20f263b435b536a194f25673Chris Wren                }
534d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            } else {
53588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                log("natural, %s was null!", (mSlot == NEXT ? "next" : "previous"));
536d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            }
53788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
53888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    };
53988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
54088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private class PhotoLaunchTask extends AsyncTask<Void, Void, View> {
54188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        private final BitmapFactory.Options mOptions;
542d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
54388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        public PhotoLaunchTask () {
54488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            mOptions = new BitmapFactory.Options();
54588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            mOptions.inTempStorage = new byte[32768];
54688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
54788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
54888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        @Override
54988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        public View doInBackground(Void... unused) {
55088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            log("load a new photo");
55188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            final PhotoTable table = PhotoTable.this;
55288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            return applyFrame(PhotoTable.this, mOptions,
55388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                 table.mPhotoSource.next(mOptions,
55488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                      table.mLongSide, table.mShortSide));
555d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
556d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
557d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        @Override
558d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        public void onPostExecute(View photo) {
559d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            if (photo != null) {
560d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                final PhotoTable table = PhotoTable.this;
561d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
562d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                table.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
56388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    LayoutParams.WRAP_CONTENT));
564d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                if (table.hasSelection()) {
56588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    for (int slot = 0; slot < mOnDeck.length; slot++) {
56688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                        if (mOnDeck[slot] != null) {
56788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                            table.moveToTopOfPile(mOnDeck[slot]);
56888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                        }
56988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    }
57088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                    table.moveToTopOfPile(table.getSelection());
571d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                }
572d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
573d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                log("drop it");
574d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                table.throwOnTable(photo);
575d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
576387363c426e097c358bd41367e0a8f4339fc8dc3Chris Wren                if (mOnTable.size() > mTableCapacity) {
577387363c426e097c358bd41367e0a8f4339fc8dc3Chris Wren                    int targetSize = Math.max(0, mOnTable.size() - mRedealCount);
578387363c426e097c358bd41367e0a8f4339fc8dc3Chris Wren                    while (mOnTable.size() > targetSize) {
579387363c426e097c358bd41367e0a8f4339fc8dc3Chris Wren                        fadeAway(mOnTable.poll(), false);
580387363c426e097c358bd41367e0a8f4339fc8dc3Chris Wren                    }
581387363c426e097c358bd41367e0a8f4339fc8dc3Chris Wren                }
582387363c426e097c358bd41367e0a8f4339fc8dc3Chris Wren
583d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                if(table.mOnTable.size() < table.mTableCapacity) {
584d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    table.scheduleNext(table.mFastDropPeriod);
585d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                }
586d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            }
587d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
588d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    };
589d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
59088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Bring a new photo onto the table. */
591d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void launch() {
592d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("launching");
59388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
59484c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        if (!hasSelection()) {
595d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            log("inflate it");
596d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            if (mPhotoLaunchTask == null ||
597d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                mPhotoLaunchTask.getStatus() == AsyncTask.Status.FINISHED) {
598d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                mPhotoLaunchTask = new PhotoLaunchTask();
599d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                mPhotoLaunchTask.execute();
600d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            }
601d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
602d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
60388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
6043910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren    /** De-emphasize the other photos on the table. */
605f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    public void fadeOutBackground(final View photo) {
606f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        mBackground.animate()
607f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        .withLayer()
608f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        .setDuration(mPickUpDuration)
609f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        .alpha(0f);
6103910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren    }
6113910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren
6123910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren
6133910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren    /** Return the other photos to foreground status. */
614f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    public void fadeInBackground(final View photo) {
615f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        mAnimating.add(photo);
616f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        mBackground.animate()
617f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        .withLayer()
618f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        .setDuration(mPickUpDuration)
619f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        .alpha(1f)
620f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        .withEndAction(new Runnable() {
621f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            @Override
622f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            public void run() {
623f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                mAnimating.remove(photo);
624f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                if (!mAnimating.contains(photo)) {
625f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                    moveToBackground(photo);
626f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                }
6273910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren            }
628f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        });
6293910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren    }
6303910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren
63188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Dispose of the photo gracefully, in case we can see some of it. */
632d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void fadeAway(final View photo, final boolean replace) {
633d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        // fade out of view
634d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mOnTable.remove(photo);
635f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        exitStageLeft(photo);
636f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        photo.setOnTouchListener(null);
637d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.animate().cancel();
638d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.animate()
639d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .withLayer()
640d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .alpha(0f)
6413910ecc7c3a93485d32f8c1eac79e47dbf3753fbChris Wren                .setDuration(mPickUpDuration)
642d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .withEndAction(new Runnable() {
643d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                        @Override
644d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                        public void run() {
64588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                            if (photo == getFocus()) {
64688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                                clearFocus();
64788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                            }
648f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                            mStageLeft.removeView(photo);
649d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            recycle(photo);
650d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            if (replace) {
651d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                                scheduleNext(mNowDropDelay);
652d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            }
653d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                        }
654d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    });
655d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
656d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
65788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Visually on top, and also freshest, for the purposes of timeouts. */
658eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    public void moveToTopOfPile(View photo) {
659d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        // make this photo the last to be removed.
660f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        if (isInBackground(photo)) {
661f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren           mBackground.bringChildToFront(photo);
662f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        } else {
663f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            bringChildToFront(photo);
664f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        }
665d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        invalidate();
666d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mOnTable.remove(photo);
667d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mOnTable.offer(photo);
668d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
669d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
67088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** On deck is to the left or right of the selected photo. */
67188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private void placeOnDeck(final View photo, final int slot ) {
67288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (slot < mOnDeck.length) {
67388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            if (mOnDeck[slot] != null && mOnDeck[slot] != photo) {
67488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                fadeAway(mOnDeck[slot], false);
67588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            }
67688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            mOnDeck[slot] = photo;
67788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            float photoWidth = photo.getWidth();
67888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            float photoHeight = photo.getHeight();
67988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            float scale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth);
68088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
68188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            float x = (getWidth() - photoWidth) / 2f;
68288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            float y = (getHeight() - photoHeight) / 2f;
68388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
68488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            float offset = (((float) mWidth + scale * (photoWidth - 2f * mInset)) / 2f);
68588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            x += (slot == NEXT? 1f : -1f) * offset;
68688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
68788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            photo.animate()
688f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                .withLayer()
68988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                .rotation(0f)
69088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                .rotationY(0f)
69188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                .scaleX(scale)
69288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                .scaleY(scale)
69388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                .x(x)
69488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                .y(y)
695a924553fc2a5bf5ab88ac031adf154480b970bd9Chris Wren                .setDuration(mPickUpDuration)
69688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren                .setInterpolator(new DecelerateInterpolator(2f));
69788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
69888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    }
69988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
70088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Move in response to touch. */
70188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    public void move(final View photo, float x, float y, float a) {
70288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        photo.animate().cancel();
70388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        photo.setAlpha(1f);
70488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        photo.setX((int) x);
70588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        photo.setY((int) y);
70688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        photo.setRotation((int) a);
70788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    }
70888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
70988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Wind up off screen, so we can animate in. */
710d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private void throwOnTable(final View photo) {
711d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mOnTable.offer(photo);
712d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("start offscreen");
71376086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        photo.setRotation(mThrowRotation);
714d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.setX(-mLongSide);
715d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.setY(-mLongSide);
71676086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren
71776086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        dropOnTable(photo, mThrowInterpolator);
718d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
719d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
7201dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren    public void move(final View photo, float dx, float dy, boolean drop) {
7211dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        if (photo != null) {
7221dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren            final float x = photo.getX() + dx;
7231dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren            final float y = photo.getY() + dy;
7241dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren            photo.setX(x);
7251dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren            photo.setY(y);
7261dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren            Log.d(TAG, "[" + photo.getX() + ", " + photo.getY() + "] + (" + dx + "," + dy + ")");
7271dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren            if (drop && photoOffTable(photo)) {
7281dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren                fadeAway(photo, true);
7291dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren            }
7301dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        }
7311dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren    }
7321dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren
73388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Fling with no touch hints, then land off screen. */
734eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    public void fling(final View photo) {
735eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        final float[] o = { mWidth + mLongSide / 2f,
736eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                            mHeight + mLongSide / 2f };
737eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        final float[] a = { photo.getX(), photo.getY() };
738eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        final float[] b = { o[0], a[1] + o[0] - a[0] };
739eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        final float[] c = { a[0] + o[1] - a[1], o[1] };
740eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        float[] delta = { 0f, 0f };
741eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        if (Math.hypot(b[0] - a[0], b[1] - a[1]) < Math.hypot(c[0] - a[0], c[1] - a[1])) {
742eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            delta[0] = b[0] - a[0];
743eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            delta[1] = b[1] - a[1];
744eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        } else {
745eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            delta[0] = c[0] - a[0];
746eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            delta[1] = c[1] - a[1];
747eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        }
748eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
749eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        final float dist = (float) Math.hypot(delta[0], delta[1]);
750eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        final int duration = (int) (1000f * dist / mThrowSpeed);
7511dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        fling(photo, delta[0], delta[1], duration, true);
752eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
753eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
75488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Continue dynamically after a fling gesture, possibly off the screen. */
7551dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren    public void fling(final View photo, float dx, float dy, int duration, boolean spin) {
75688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (photo == getFocus()) {
757eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            if (moveFocus(photo, 0f) == null) {
758eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                moveFocus(photo, 180f);
759eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            }
760eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        }
761f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        moveToForeground(photo);
762eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        ViewPropertyAnimator animator = photo.animate()
763f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                .withLayer()
764eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                .xBy(dx)
765eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                .yBy(dy)
766eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                .setDuration(duration)
767eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                .setInterpolator(new DecelerateInterpolator(2f));
768eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
769eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        if (spin) {
770eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            animator.rotation(mThrowRotation);
771eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        }
772eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
7731dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        if (photoOffTable(photo, (int) dx, (int) dy)) {
774eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            log("fling away");
775eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            animator.withEndAction(new Runnable() {
776eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                    @Override
777eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                    public void run() {
778eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                        fadeAway(photo, true);
779eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                    }
780eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren                });
781eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        }
782eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
7831dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren    public boolean photoOffTable(final View photo) {
7841dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        return photoOffTable(photo, 0, 0);
7851dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren    }
7861dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren
7871dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren    public boolean photoOffTable(final View photo, final int dx, final int dy) {
7881dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        Rect hit = new Rect();
7891dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        photo.getHitRect(hit);
7901dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        hit.offset(dx, dy);
7911dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren        return (hit.bottom < 0f || hit.top > getHeight() ||
7921dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren                hit.right < 0f || hit.left > getWidth());
7931dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren    }
794eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
79588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Animate to a random place and orientation, down on the table (visually small). */
796d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void dropOnTable(final View photo) {
79776086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        dropOnTable(photo, mDropInterpolator);
79876086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    }
79976086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren
80088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Animate to a random place and orientation, down on the table (visually small). */
80176086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    public void dropOnTable(final View photo, final Interpolator interpolator) {
802d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float angle = randfrange(-mImageRotationLimit, mImageRotationLimit);
803f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        PointF p = randMultiDrop(sRNG.nextInt(),
804f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren                                 (float) sRNG.nextGaussian(), (float) sRNG.nextGaussian(),
805f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren                                 mWidth, mHeight);
806d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float x = p.x;
807d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float y = p.y;
808d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
80988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        log("drop it at %f, %f", x, y);
810d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
811d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float x0 = photo.getX();
812d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float y0 = photo.getY();
813d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
814f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        x -= mLongSide / 2f;
815f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        y -= mShortSide / 2f;
81688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        log("fixed offset is %f, %f ", x, y);
817d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
818d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float dx = x - x0;
819d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float dy = y - y0;
820d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
821eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        float dist = (float) Math.hypot(dx, dy);
82276086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        int duration = (int) (1000f * dist / mThrowSpeed);
823d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        duration = Math.max(duration, 1000);
824d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
825d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("animate it");
826d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        // toss onto table
827f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        mAnimating.add(photo);
828d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.animate()
829f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .withLayer()
830f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .scaleX(mTableRatio / mImageRatio)
831f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .scaleY(mTableRatio / mImageRatio)
832f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .rotation(angle)
833f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .x(x)
834f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .y(y)
835f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .setDuration(duration)
836f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .setInterpolator(interpolator)
837f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .withEndAction(new Runnable() {
838f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                @Override
839f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                public void run() {
840f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                    mAnimating.remove(photo);
841f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                    if (!mAnimating.contains(photo)) {
842f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                        moveToBackground(photo);
843f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                    }
844f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                }
845f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            });
846f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    }
847f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren
848f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    private void moveToBackground(View photo) {
849f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        if (!isInBackground(photo)) {
850f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            removeView(photo);
851f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            mBackground.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
852f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                    LayoutParams.WRAP_CONTENT));
853f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        }
854f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    }
855f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren
856f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    private void exitStageLeft(View photo) {
857f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        if (isInBackground(photo)) {
858f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            mBackground.removeView(photo);
859f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        } else {
860f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            removeView(photo);
861f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        }
862f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        mStageLeft.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
863f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                LayoutParams.WRAP_CONTENT));
864f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    }
865f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren
866f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    private void moveToForeground(View photo) {
867f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        if (isInBackground(photo)) {
868f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            mBackground.removeView(photo);
869f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
870f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                    LayoutParams.WRAP_CONTENT));
871f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        }
872f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    }
873f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren
874f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren    private boolean isInBackground(View photo) {
875f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        return mBackground.indexOfChild(photo) != -1;
876d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
877d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
878d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    /** wrap all orientations to the interval [-180, 180). */
879d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private float wrapAngle(float angle) {
880d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float result = angle + 180;
881d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        result = ((result % 360) + 360) % 360; // catch negative numbers
882d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        result -= 180;
883d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return result;
884d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
885d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
88688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Animate the selected photo to the foregound: zooming in to bring it foreward. */
887d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private void pickUp(final View photo) {
888d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float photoWidth = photo.getWidth();
889d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float photoHeight = photo.getHeight();
890d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
891d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float scale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth);
892d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
89388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        log("scale is %f", scale);
894d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("target it");
895d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float x = (getWidth() - photoWidth) / 2f;
896d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float y = (getHeight() - photoHeight) / 2f;
897d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
898d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.setRotation(wrapAngle(photo.getRotation()));
899d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
900d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("animate it");
90188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        // lift up to the glass for a good look
902f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren        moveToForeground(photo);
903d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.animate()
904f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .withLayer()
905f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .rotation(0f)
906f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .rotationY(0f)
907f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .alpha(1f)
908f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .scaleX(scale)
909f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .scaleY(scale)
910f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .x(x)
911f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .y(y)
912f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .setDuration(mPickUpDuration)
913f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .setInterpolator(new DecelerateInterpolator(2f))
914f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            .withEndAction(new Runnable() {
915f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                @Override
916f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                public void run() {
917f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                    log("endtimes: %f", photo.getX());
918f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren                }
919f362b8f1d503df040c55ac04913dabdbb2cd7155Chris Wren            });
920d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
921d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
92288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private Bitmap getBitmap(View photo) {
92388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (photo == null) {
92488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            return null;
92588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
926d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        ImageView image = (ImageView) photo;
927d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        LayerDrawable layers = (LayerDrawable) image.getDrawable();
92888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (layers == null) {
92988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            return null;
93088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
931d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        BitmapDrawable bitmap = (BitmapDrawable) layers.getDrawable(0);
93288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (bitmap == null) {
93388d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            return null;
93488d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
93588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        return bitmap.getBitmap();
93688d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    }
93788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren
93888d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private void recycle(View photo) {
93988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        if (photo != null) {
94088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            removeView(photo);
941bcfd4439d730a4d783a02596c8ab444796323aadChris Wren            mPhotoSource.recycle(getBitmap(photo));
94288d80f4471c900628e2cb6eef23029b99af48e09Chris Wren        }
943d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
944d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
945eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    public void setHighlight(View photo, boolean highlighted) {
946eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        ImageView image = (ImageView) photo;
947eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        LayerDrawable layers = (LayerDrawable) image.getDrawable();
948eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        if (highlighted) {
949eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            layers.getDrawable(1).setColorFilter(mHighlightColor, PorterDuff.Mode.SRC_IN);
950eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        } else {
951eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren            layers.getDrawable(1).clearColorFilter();
952eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        }
953eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
954eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
95588d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    /** Schedule the first launch.  Idempotent. */
956d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void start() {
957d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (!mStarted) {
958d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            log("kick it");
959d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            mStarted = true;
96084c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren            scheduleNext(0);
961d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
962d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
963d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
96484c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren    public void refreshSelection() {
96584c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        scheduleSelectionReaper(mMaxFocusTime);
96684c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren    }
96784c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren
96884c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren    public void scheduleSelectionReaper(int delay) {
96984c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        removeCallbacks(mSelectionReaper);
97084c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        postDelayed(mSelectionReaper, delay);
97184c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren    }
97284c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren
9731dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren    public void refreshFocus() {
97484c3d5580e7168d1d150c1dd24340b19eb501cd2Chris Wren        scheduleFocusReaper(mMaxFocusTime);
9751dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren    }
9761dc94b55d59f5c4dbf86788d0c32b7ac4449909aChris Wren
977eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    public void scheduleFocusReaper(int delay) {
978eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        removeCallbacks(mFocusReaper);
979eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren        postDelayed(mFocusReaper, delay);
980eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren    }
981eb70217554d14807d73cae033b4f756c9b80fe3bChris Wren
982d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void scheduleNext(int delay) {
983d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        removeCallbacks(mLauncher);
984d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        postDelayed(mLauncher, delay);
985d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
986d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
98788d80f4471c900628e2cb6eef23029b99af48e09Chris Wren    private static void log(String message, Object... args) {
988d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (DEBUG) {
98988d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            Formatter formatter = new Formatter();
99088d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            formatter.format(message, args);
99188d80f4471c900628e2cb6eef23029b99af48e09Chris Wren            Log.i(TAG, formatter.toString());
992d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
993c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren    }
994c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren}
995