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
181c1faec579b96aeac7416a80a7ee7155a813fffeDianne Hackbornimport android.service.dreams.DreamService;
19d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.content.Context;
20d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.content.SharedPreferences;
21d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.content.res.Resources;
22d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.Bitmap;
23d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.BitmapFactory;
24d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.PointF;
25d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.drawable.BitmapDrawable;
26d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.drawable.Drawable;
27d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.graphics.drawable.LayerDrawable;
28d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.os.AsyncTask;
29d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.util.AttributeSet;
30d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.util.Log;
31d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.view.LayoutInflater;
32d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.view.MotionEvent;
33d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.view.View;
34d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.view.animation.DecelerateInterpolator;
3576086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wrenimport android.view.animation.Interpolator;
36d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.widget.FrameLayout;
37d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.widget.FrameLayout.LayoutParams;
38d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport android.widget.ImageView;
39d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
40d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport java.util.LinkedList;
41d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenimport java.util.Random;
42c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren
43c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren/**
44d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren * A surface where photos sit.
45c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren */
46d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wrenpublic class PhotoTable extends FrameLayout {
47c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren    private static final String TAG = "PhotoTable";
48d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private static final boolean DEBUG = false;
49d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
50d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    class Launcher implements Runnable {
51d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        private final PhotoTable mTable;
52d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        public Launcher(PhotoTable table) {
53d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            mTable = table;
54d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
55d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
56d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        @Override
57d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        public void run() {
58d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            mTable.scheduleNext(mDropPeriod);
59d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            mTable.launch();
60d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
61d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
62d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
63d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private static final long MAX_SELECTION_TIME = 10000L;
64d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private static Random sRNG = new Random();
65d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
66d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final Launcher mLauncher;
67d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final LinkedList<View> mOnTable;
68d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final int mDropPeriod;
69d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final int mFastDropPeriod;
70d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final int mNowDropDelay;
71d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final float mImageRatio;
72d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final float mTableRatio;
73d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final float mImageRotationLimit;
7476086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    private final float mThrowRotation;
7576086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    private final float mThrowSpeed;
76d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final boolean mTapToExit;
77d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final int mTableCapacity;
78f98687b22f4091083287ab7de7a0f3102bd9c1baChris Wren    private final int mRedealCount;
79d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final int mInset;
80d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final PhotoSourcePlexor mPhotoSource;
81d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final Resources mResources;
8276086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    private final Interpolator mThrowInterpolator;
8376086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    private final Interpolator mDropInterpolator;
841c1faec579b96aeac7416a80a7ee7155a813fffeDianne Hackborn    private DreamService mDream;
85d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private PhotoLaunchTask mPhotoLaunchTask;
86d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private boolean mStarted;
87d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private boolean mIsLandscape;
88d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private int mLongSide;
89d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private int mShortSide;
90d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private int mWidth;
91d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private int mHeight;
92d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private View mSelected;
93d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private long mSelectedTime;
94d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
95651ee92128e1899957758abeeb1c2c669f240093Chris Wren    public PhotoTable(Context context, AttributeSet as) {
96651ee92128e1899957758abeeb1c2c669f240093Chris Wren        super(context, as);
97d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mResources = getResources();
98d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mInset = mResources.getDimensionPixelSize(R.dimen.photo_inset);
9970ef3e7da59212c3c4baedf92f418eb9012a4db3Chris Wren        mDropPeriod = mResources.getInteger(R.integer.table_drop_period);
100d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mFastDropPeriod = mResources.getInteger(R.integer.fast_drop);
101d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mNowDropDelay = mResources.getInteger(R.integer.now_drop);
102d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mImageRatio = mResources.getInteger(R.integer.image_ratio) / 1000000f;
103d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mTableRatio = mResources.getInteger(R.integer.table_ratio) / 1000000f;
104d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mImageRotationLimit = (float) mResources.getInteger(R.integer.max_image_rotation);
10576086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        mThrowSpeed = mResources.getDimension(R.dimen.image_throw_speed);
10676086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        mThrowRotation = (float) mResources.getInteger(R.integer.image_throw_rotatioan);
107d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mTableCapacity = mResources.getInteger(R.integer.table_capacity);
108f98687b22f4091083287ab7de7a0f3102bd9c1baChris Wren        mRedealCount = mResources.getInteger(R.integer.redeal_count);
109d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mTapToExit = mResources.getBoolean(R.bool.enable_tap_to_exit);
11076086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        mThrowInterpolator = new SoftLandingInterpolator(
11176086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren                mResources.getInteger(R.integer.soft_landing_time) / 1000000f,
11276086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren                mResources.getInteger(R.integer.soft_landing_distance) / 1000000f);
11376086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        mDropInterpolator = new DecelerateInterpolator(
11476086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren                (float) mResources.getInteger(R.integer.drop_deceleration_exponent));
115d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mOnTable = new LinkedList<View>();
116d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mPhotoSource = new PhotoSourcePlexor(getContext(),
117d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                getContext().getSharedPreferences(PhotoTableDreamSettings.PREFS_NAME, 0));
118d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mLauncher = new Launcher(this);
119d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mStarted = false;
120d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
121d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
122651ee92128e1899957758abeeb1c2c669f240093Chris Wren
1231c1faec579b96aeac7416a80a7ee7155a813fffeDianne Hackborn    public void setDream(DreamService dream) {
124651ee92128e1899957758abeeb1c2c669f240093Chris Wren        mDream = dream;
125651ee92128e1899957758abeeb1c2c669f240093Chris Wren    }
126651ee92128e1899957758abeeb1c2c669f240093Chris Wren
127d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public boolean hasSelection() {
128d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return mSelected != null;
129d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
130d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
131d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public View getSelected() {
132d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return mSelected;
133d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
134d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
135d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void clearSelection() {
136d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mSelected = null;
137d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
138d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
139d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void setSelection(View selected) {
140d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        assert(selected != null);
141d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (mSelected != null) {
142d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            dropOnTable(mSelected);
143d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
144d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mSelected = selected;
145d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mSelectedTime = System.currentTimeMillis();
146d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        bringChildToFront(selected);
147d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        pickUp(selected);
148d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
149d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
150d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    static float lerp(float a, float b, float f) {
151d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return (b-a)*f + a;
152d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
153d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
154d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    static float randfrange(float a, float b) {
155d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return lerp(a, b, sRNG.nextFloat());
156d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
157d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
158d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    static PointF randFromCurve(float t, PointF[] v) {
159d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        PointF p = new PointF();
160d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (v.length == 4 && t >= 0f && t <= 1f) {
161d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            float a = (float) Math.pow(1f-t, 3f);
162d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            float b = (float) Math.pow(1f-t, 2f) * t;
163d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            float c = (1f-t) * (float) Math.pow(t, 2f);
164d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            float d = (float) Math.pow(t, 3f);
165d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
166d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            p.x = a * v[0].x + 3 * b * v[1].x + 3 * c * v[2].x + d * v[3].x;
167d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            p.y = a * v[0].y + 3 * b * v[1].y + 3 * c * v[2].y + d * v[3].y;
168d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
169d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return p;
170d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
171d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
172d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private static PointF randInCenter(float i, float j, int width, int height) {
173d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("randInCenter (" + i + ", " + j + ", " + width + ", " + height + ")");
174d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        PointF p = new PointF();
175d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        p.x = 0.5f * width + 0.15f * width * i;
176d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        p.y = 0.5f * height + 0.15f * height * j;
177d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("randInCenter returning " + p.x + "," + p.y);
178d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return p;
179d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
180d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
181f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren    private static PointF randMultiDrop(int n, float i, float j, int width, int height) {
182f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        log("randMultiDrop (" + n + "," + i + ", " + j + ", " + width + ", " + height + ")");
183f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        final float[] cx = {0.3f, 0.3f, 0.5f, 0.7f, 0.7f};
184f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        final float[] cy = {0.3f, 0.7f, 0.5f, 0.3f, 0.7f};
185f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        n = Math.abs(n);
186f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        float x = cx[n % cx.length];
187f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        float y = cy[n % cx.length];
188f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        PointF p = new PointF();
189f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        p.x = x * width + 0.05f * width * i;
190f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        p.y = y * height + 0.05f * height * j;
191f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        log("randInCenter returning " + p.x + "," + p.y);
192f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        return p;
193f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren    }
194f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren
195d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    @Override
196d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public boolean onTouchEvent(MotionEvent event) {
197d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
198d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            if (hasSelection()) {
199d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                dropOnTable(getSelected());
200d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                clearSelection();
201d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            } else  {
202651ee92128e1899957758abeeb1c2c669f240093Chris Wren                if (mTapToExit && mDream != null) {
203d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    mDream.finish();
204d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                }
205d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            }
206d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            return true;
207d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
208d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return false;
209d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
210c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren
211c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren    @Override
212d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
213d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        super.onLayout(changed, left, top, right, bottom);
214d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("onLayout (" + left + ", " + top + ", " + right + ", " + bottom + ")");
215d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
216d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mHeight = bottom - top;
217d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mWidth = right - left;
218d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
219d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mLongSide = (int) (mImageRatio * Math.max(mWidth, mHeight));
220d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mShortSide = (int) (mImageRatio * Math.min(mWidth, mHeight));
221d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
222d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        boolean isLandscape = mWidth > mHeight;
223d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (mIsLandscape != isLandscape) {
224d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            for (View photo: mOnTable) {
225d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                if (photo == getSelected()) {
226d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    pickUp(photo);
227d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                } else {
228d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    dropOnTable(photo);
229d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                }
230d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            }
231d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            mIsLandscape = isLandscape;
232d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
233d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        start();
234c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren    }
235c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren
236c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren    @Override
237d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public boolean isOpaque() {
238d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return true;
239d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
240d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
241d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private class PhotoLaunchTask extends AsyncTask<Void, Void, View> {
242b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren        private final BitmapFactory.Options mOptions;
243b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren
244b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren        public PhotoLaunchTask () {
245b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren            mOptions = new BitmapFactory.Options();
246b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren            mOptions.inTempStorage = new byte[32768];
247b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren        }
248b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren
249d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        @Override
250d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        public View doInBackground(Void... unused) {
251d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            log("load a new photo");
252d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            final PhotoTable table = PhotoTable.this;
253d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
254d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            LayoutInflater inflater = (LayoutInflater) table.getContext()
255d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                   .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
256d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            View photo = inflater.inflate(R.layout.photo, null);
257d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            ImageView image = (ImageView) photo;
258d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            Drawable[] layers = new Drawable[2];
259b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren            Bitmap decodedPhoto = table.mPhotoSource.next(mOptions,
260d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    table.mLongSide, table.mShortSide);
261b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren            int photoWidth = mOptions.outWidth;
262b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren            int photoHeight = mOptions.outHeight;
263b6c75a5e1a9dfc7753a451e97607510d37264374Chris Wren            if (mOptions.outWidth <= 0 || mOptions.outHeight <= 0) {
264d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                photo = null;
265d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            } else {
266f64bb8f6838663783d55d07cee20f96b93aa5b42Romain Guy                decodedPhoto.setHasMipMap(true);
267d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                layers[0] = new BitmapDrawable(table.mResources, decodedPhoto);
268d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                layers[1] = table.mResources.getDrawable(R.drawable.frame);
269d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                LayerDrawable layerList = new LayerDrawable(layers);
270d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                layerList.setLayerInset(0, table.mInset, table.mInset,
271d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                                        table.mInset, table.mInset);
272d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                image.setImageDrawable(layerList);
273d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
274d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                photo.setTag(R.id.photo_width, new Integer(photoWidth));
275d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                photo.setTag(R.id.photo_height, new Integer(photoHeight));
276d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
277d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                photo.setOnTouchListener(new PhotoTouchListener(table.getContext(),
278d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                                                                table));
279d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            }
280d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
281d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            return photo;
282d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
283d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
284d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        @Override
285d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        public void onPostExecute(View photo) {
286d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            if (photo != null) {
287d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                final PhotoTable table = PhotoTable.this;
288d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
289d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                table.addView(photo, new LayoutParams(LayoutParams.WRAP_CONTENT,
290d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                                                       LayoutParams.WRAP_CONTENT));
291d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                if (table.hasSelection()) {
292d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    table.bringChildToFront(table.getSelected());
293d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                }
294d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                int width = ((Integer) photo.getTag(R.id.photo_width)).intValue();
295d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                int height = ((Integer) photo.getTag(R.id.photo_height)).intValue();
296d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
297d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                log("drop it");
298d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                table.throwOnTable(photo);
299d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
300d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                if(table.mOnTable.size() < table.mTableCapacity) {
301d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    table.scheduleNext(table.mFastDropPeriod);
302d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                }
303d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            }
304d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
305d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    };
306d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
307d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void launch() {
308d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("launching");
309d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
310d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (hasSelection() &&
311d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                (System.currentTimeMillis() - mSelectedTime) > MAX_SELECTION_TIME) {
312d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            dropOnTable(getSelected());
313d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            clearSelection();
314d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        } else {
315d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            log("inflate it");
316d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            if (mPhotoLaunchTask == null ||
317d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                mPhotoLaunchTask.getStatus() == AsyncTask.Status.FINISHED) {
318d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                mPhotoLaunchTask = new PhotoLaunchTask();
319d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                mPhotoLaunchTask.execute();
320d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            }
321d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
322d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
323d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void fadeAway(final View photo, final boolean replace) {
324d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        // fade out of view
325d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mOnTable.remove(photo);
326d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.animate().cancel();
327d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.animate()
328d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .withLayer()
329d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .alpha(0f)
330d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .setDuration(1000)
331d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .withEndAction(new Runnable() {
332d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                        @Override
333d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                        public void run() {
334d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            removeView(photo);
335d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            recycle(photo);
336d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            if (replace) {
337d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                                scheduleNext(mNowDropDelay);
338d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            }
339d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                        }
340d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    });
341d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
342d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
343d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void moveToBackOfQueue(View photo) {
344d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        // make this photo the last to be removed.
345d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        bringChildToFront(photo);
346d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        invalidate();
347d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mOnTable.remove(photo);
348d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mOnTable.offer(photo);
349d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
350d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
351d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private void throwOnTable(final View photo) {
352d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        mOnTable.offer(photo);
353d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("start offscreen");
354d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        int width = ((Integer) photo.getTag(R.id.photo_width));
355d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        int height = ((Integer) photo.getTag(R.id.photo_height));
35676086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        photo.setRotation(mThrowRotation);
357d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.setX(-mLongSide);
358d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.setY(-mLongSide);
35976086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren
36076086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        dropOnTable(photo, mThrowInterpolator);
361d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
362d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
363d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void dropOnTable(final View photo) {
36476086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        dropOnTable(photo, mDropInterpolator);
36576086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    }
36676086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren
36776086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren    public void dropOnTable(final View photo, final Interpolator interpolator) {
368d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float angle = randfrange(-mImageRotationLimit, mImageRotationLimit);
369f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        PointF p = randMultiDrop(sRNG.nextInt(),
370f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren                                 (float) sRNG.nextGaussian(), (float) sRNG.nextGaussian(),
371f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren                                 mWidth, mHeight);
372d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float x = p.x;
373d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float y = p.y;
374d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
375d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("drop it at " + x + ", " + y);
376d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
377d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float x0 = photo.getX();
378d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float y0 = photo.getY();
379d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float width = (float) ((Integer) photo.getTag(R.id.photo_width)).intValue();
380d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float height = (float) ((Integer) photo.getTag(R.id.photo_height)).intValue();
381d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
382f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        x -= mLongSide / 2f;
383f40bfb6272ceec0f3ec471495a8e1b739c5b9d4fChris Wren        y -= mShortSide / 2f;
384d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("fixed offset is " + x + ", " + y);
385d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
386d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float dx = x - x0;
387d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float dy = y - y0;
388d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
389d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float dist = (float) (Math.sqrt(dx * dx + dy * dy));
39076086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        int duration = (int) (1000f * dist / mThrowSpeed);
391d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        duration = Math.max(duration, 1000);
392d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
393d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("animate it");
394d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        // toss onto table
395d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.animate()
396d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .scaleX(mTableRatio / mImageRatio)
397d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .scaleY(mTableRatio / mImageRatio)
398d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .rotation(angle)
399d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .x(x)
400d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .y(y)
401d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .setDuration(duration)
40276086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren                .setInterpolator(interpolator)
403d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .withEndAction(new Runnable() {
404d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                        @Override
405d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            public void run() {
406f98687b22f4091083287ab7de7a0f3102bd9c1baChris Wren                            if (mOnTable.size() > mTableCapacity) {
407f98687b22f4091083287ab7de7a0f3102bd9c1baChris Wren                                while (mOnTable.size() > (mTableCapacity - mRedealCount)) {
408f98687b22f4091083287ab7de7a0f3102bd9c1baChris Wren                                    fadeAway(mOnTable.poll(), false);
409f98687b22f4091083287ab7de7a0f3102bd9c1baChris Wren                                }
410f98687b22f4091083287ab7de7a0f3102bd9c1baChris Wren                                // zero delay because we already waited duration ms
411f98687b22f4091083287ab7de7a0f3102bd9c1baChris Wren                                scheduleNext(0);
412d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            }
413d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                        }
414d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    });
415d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
416d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
417d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    /** wrap all orientations to the interval [-180, 180). */
418d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private float wrapAngle(float angle) {
419d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float result = angle + 180;
420d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        result = ((result % 360) + 360) % 360; // catch negative numbers
421d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        result -= 180;
422d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        return result;
423d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
424d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
425d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private void pickUp(final View photo) {
426d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float photoWidth = photo.getWidth();
427d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float photoHeight = photo.getHeight();
428d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
429d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float scale = Math.min(getHeight() / photoHeight, getWidth() / photoWidth);
430d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
431d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("target it");
432d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float x = (getWidth() - photoWidth) / 2f;
433d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float y = (getHeight() - photoHeight) / 2f;
434d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
435d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float x0 = photo.getX();
436d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float y0 = photo.getY();
437d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float dx = x - x0;
438d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float dy = y - y0;
439d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
440d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        float dist = (float) (Math.sqrt(dx * dx + dy * dy));
44176086400f8e68b810bf1b3db0dc4f7133c8644aeChris Wren        int duration = (int) (1000f * dist / 600f);
442d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        duration = Math.max(duration, 500);
443d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
444d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.setRotation(wrapAngle(photo.getRotation()));
445d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
446d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        log("animate it");
447d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        // toss onto table
448d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        photo.animate()
449d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .rotation(0f)
450d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .scaleX(scale)
451d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .scaleY(scale)
452d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .x(x)
453d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .y(y)
454d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .setDuration(duration)
455d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .setInterpolator(new DecelerateInterpolator(2f))
456d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                .withEndAction(new Runnable() {
457d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                        @Override
458d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            public void run() {
459d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                            log("endtimes: " + photo.getX());
460d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                        }
461d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren                    });
462d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
463d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
464d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private void recycle(View photo) {
465d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        ImageView image = (ImageView) photo;
466d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        LayerDrawable layers = (LayerDrawable) image.getDrawable();
467d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        BitmapDrawable bitmap = (BitmapDrawable) layers.getDrawable(0);
468d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        bitmap.getBitmap().recycle();
469d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
470d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
471d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void start() {
472d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (!mStarted) {
473d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            log("kick it");
474d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            mStarted = true;
475d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            scheduleNext(mDropPeriod);
476d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            launch();
477d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
478d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
479d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
480d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public void scheduleNext(int delay) {
481d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        removeCallbacks(mLauncher);
482d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        postDelayed(mLauncher, delay);
483d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    }
484d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren
485d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private static void log(String message) {
486d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        if (DEBUG) {
487d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren            Log.i(TAG, message);
488d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren        }
489c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren    }
490c8ca07eb52a54efe327d024dc542e0a7f8849bfbChris Wren}
491