1135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren/*
2135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren * Copyright (C) 2012 The Android Open Source Project
3135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren *
4135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren * Licensed under the Apache License, Version 2.0 (the "License");
5135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren * you may not use this file except in compliance with the License.
6135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren * You may obtain a copy of the License at
7135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren *
8135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren *      http://www.apache.org/licenses/LICENSE-2.0
9135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren *
10135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren * Unless required by applicable law or agreed to in writing, software
11135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren * distributed under the License is distributed on an "AS IS" BASIS,
12135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren * See the License for the specific language governing permissions and
14135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren * limitations under the License.
15135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren */
16135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wrenpackage com.android.dreams.phototable;
17135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
18135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wrenimport android.content.Context;
19135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wrenimport android.content.res.Resources;
20135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wrenimport android.util.Log;
21135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wrenimport android.view.MotionEvent;
22135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wrenimport android.view.View;
23135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wrenimport android.view.ViewConfiguration;
24135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wrenimport android.view.ViewPropertyAnimator;
25135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wrenimport android.view.animation.DecelerateInterpolator;
26135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
27135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren/**
28135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren * Touch listener that implements phototable interactions.
29135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren */
30135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wrenpublic class PhotoTouchListener implements View.OnTouchListener {
31135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private static final String TAG = "PhotoTouchListener";
32c1501041b64faa6c205a93baf403c4c87a0c1acfChris Wren    private static final boolean DEBUG = false;
33135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private static final int INVALID_POINTER = -1;
34135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private static final int MAX_POINTER_COUNT = 10;
35135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private final int mTouchSlop;
36135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private final int mTapTimeout;
37d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    private final PhotoTable mTable;
38135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private final float mBeta;
39135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private final float mTableRatio;
40135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private final boolean mEnableFling;
41135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private final boolean mManualImageRotation;
42135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private long mLastEventTime;
43135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float mLastTouchX;
44135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float mLastTouchY;
45135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float mInitialTouchX;
46135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float mInitialTouchY;
47135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float mInitialTouchA;
48135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private long mInitialTouchTime;
49135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float mInitialTargetX;
50135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float mInitialTargetY;
51135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float mInitialTargetA;
52135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float mDX;
53135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float mDY;
54135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private int mA = INVALID_POINTER;
55135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private int mB = INVALID_POINTER;
56135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float[] pts = new float[MAX_POINTER_COUNT];
57135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float[] tmp = new float[MAX_POINTER_COUNT];
58135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
59d85f53c69dead1f1f6c0290b8104422143bc5166Chris Wren    public PhotoTouchListener(Context context, PhotoTable table) {
60135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mTable = table;
61135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final ViewConfiguration configuration = ViewConfiguration.get(context);
62135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mTouchSlop = configuration.getScaledTouchSlop();
63135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mTapTimeout = configuration.getTapTimeout();
64135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final Resources resources = context.getResources();
65135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mBeta = resources.getInteger(R.integer.table_damping) / 1000000f;
66135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mTableRatio = resources.getInteger(R.integer.table_ratio) / 1000000f;
67135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mEnableFling = resources.getBoolean(R.bool.enable_fling);
68135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mManualImageRotation = resources.getBoolean(R.bool.enable_manual_image_rotation);
69135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    }
70135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
71135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    /** Get angle defined by first two touches, in degrees */
72135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private float getAngle(View target, MotionEvent ev) {
73135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        float alpha = 0f;
74135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        int a = ev.findPointerIndex(mA);
75135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        int b = ev.findPointerIndex(mB);
76135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        if (a >=0 && b >=0) {
77135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            alpha = (float) (Math.atan2(pts[2*a + 1] - pts[2*b + 1],
78135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                                        pts[2*a] - pts[2*b]) *
79135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                             180f / Math.PI);
80135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        }
81135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        return alpha;
82135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    }
83135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
84135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private void resetTouch(View target) {
85135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mInitialTouchX = -1;
86135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mInitialTouchY = -1;
87135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mInitialTouchA = 0f;
88135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mInitialTargetX = (float) target.getX();
89135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mInitialTargetY = (float) target.getY();
90135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        mInitialTargetA = (float) target.getRotation();
91135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    }
92135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
93135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    public void onFling(View target, float dX, float dY) {
94135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        if (!mEnableFling) {
95135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            return;
96135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        }
97f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren        log("fling " + dX + ", " + dY);
98135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
99135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        // convert to pixel per frame
100135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        dX /= 60f;
101135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        dY /= 60f;
102135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
103135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        // starting position compionents in global corrdinate frame
104135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float x0 = pts[0];
105135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float y0 = pts[1];
106135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
107135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        // velocity
108135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float v = (float) Math.hypot(dX, dY);
109135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
110135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        if (v == 0f) {
111135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            return;
112135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        }
113135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
114135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        // number of steps to come to a stop
115135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float n = (float) Math.max(1.0, (- Math.log(v) / Math.log(mBeta)));
116135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        // distance travelled before stopping
117135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float s = (float) Math.max(0.0, (v * (1f - Math.pow(mBeta, n)) / (1f - mBeta)));
118135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
119135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        // ending posiiton after stopping
120135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float x1 = x0 + s * dX / v;
121135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float y1 = y0 + s * dY / v;
122135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
123135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float photoWidth = ((Integer) target.getTag(R.id.photo_width)).floatValue();
124135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float photoHeight = ((Integer) target.getTag(R.id.photo_height)).floatValue();
125135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float tableWidth = mTable.getWidth();
126135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float tableHeight = mTable.getHeight();
127135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final float halfShortSide =
128135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                Math.min(photoWidth * mTableRatio, photoHeight * mTableRatio) / 2f;
129135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final View photo = target;
130135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        ViewPropertyAnimator animator = photo.animate()
131135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                .xBy(x1 - x0)
132135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                .yBy(y1 - y0)
133135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                .setDuration((int) (1000f * n / 60f))
134135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                .setInterpolator(new DecelerateInterpolator(2f));
135135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
136135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        if (y1 + halfShortSide < 0f || y1 - halfShortSide > tableHeight ||
137135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            x1 + halfShortSide < 0f || x1 - halfShortSide > tableWidth) {
138135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            log("fling away");
139135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            animator.withEndAction(new Runnable() {
140135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    @Override
141135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    public void run() {
142135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mTable.fadeAway(photo, true);
143135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    }
144135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                });
145135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        }
146135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    }
147135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
148135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    @Override
149135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    public boolean onTouch(View target, MotionEvent ev) {
150135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        final int action = ev.getActionMasked();
151135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
152135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        // compute raw coordinates
153135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        for(int i = 0; i < 10 && i < ev.getPointerCount(); i++) {
154135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            pts[i*2] = ev.getX(i);
155135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            pts[i*2 + 1] = ev.getY(i);
156135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        }
157135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        target.getMatrix().mapPoints(pts);
158135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
159135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        switch (action) {
160135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        case MotionEvent.ACTION_DOWN:
161135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            mTable.moveToBackOfQueue(target);
162135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            mInitialTouchTime = ev.getEventTime();
163135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            mA = ev.getPointerId(ev.getActionIndex());
164135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            resetTouch(target);
165135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            break;
166135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
167135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        case MotionEvent.ACTION_POINTER_DOWN:
168135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            if (mB == INVALID_POINTER) {
169135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                mB = ev.getPointerId(ev.getActionIndex());
170135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                mInitialTouchA = getAngle(target, ev);
171135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            }
172135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            break;
173135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
174135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        case MotionEvent.ACTION_POINTER_UP:
175135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            if (mB == ev.getPointerId(ev.getActionIndex())) {
176135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                mB = INVALID_POINTER;
177135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                mInitialTargetA = (float) target.getRotation();
178135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            }
179135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            if (mA == ev.getPointerId(ev.getActionIndex())) {
180f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren                log("primary went up!");
181135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                mA = mB;
182135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                resetTouch(target);
183135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                mB = INVALID_POINTER;
184135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            }
185135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            break;
186135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
187135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        case MotionEvent.ACTION_MOVE: {
188135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                if (mA != INVALID_POINTER) {
189135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    int idx = ev.findPointerIndex(mA);
190135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    float x = pts[2 * idx];
191135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    float y = pts[2 * idx + 1];
192135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    if (mInitialTouchX == -1 && mInitialTouchY == -1) {
193135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mInitialTouchX = x;
194135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mInitialTouchY = y;
195135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    } else {
196135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        float dt = (float) (ev.getEventTime() - mLastEventTime) / 1000f;
197f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren                        float tmpDX = (x - mLastTouchX) / dt;
198f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren                        float tmpDY = (y - mLastTouchY) / dt;
199f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren                        if (dt > 0f && (Math.abs(tmpDX) > 5f || Math.abs(tmpDY) > 5f)) {
200f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren                            // work around odd bug with multi-finger flings
201f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren                            mDX = tmpDX;
202f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren                            mDY = tmpDY;
203f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren                        }
204f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren                        log("move " + mDX + ", " + mDY);
205f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren
206135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mLastEventTime = ev.getEventTime();
207135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mLastTouchX = x;
208135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mLastTouchY = y;
209135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    }
210135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
211135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    if (mTable.getSelected() != target) {
212135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        target.animate().cancel();
213135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
214135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        target.setX((int) (mInitialTargetX + x - mInitialTouchX));
215135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        target.setY((int) (mInitialTargetY + y - mInitialTouchY));
216135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        if (mManualImageRotation && mB != INVALID_POINTER) {
217135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                            float a = getAngle(target, ev);
218135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                            target.setRotation(
219135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                                    (int) (mInitialTargetA + a - mInitialTouchA));
220135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        }
221135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    }
222135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                }
223135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            }
224135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            break;
225135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
226135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        case MotionEvent.ACTION_UP: {
227135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                if (mA != INVALID_POINTER) {
228135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    int idx = ev.findPointerIndex(mA);
229135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    float x0 = pts[2 * idx];
230135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    float y0 = pts[2 * idx + 1];
231135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    if (mInitialTouchX == -1 && mInitialTouchY == -1) {
232135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mInitialTouchX = x0;
233135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mInitialTouchY = y0;
234135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    }
235135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    double distance = Math.hypot(x0 - mInitialTouchX,
236135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                                                 y0 - mInitialTouchY);
237135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    if (mTable.getSelected() == target) {
238135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mTable.dropOnTable(target);
239135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mTable.clearSelection();
240135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    } else if ((ev.getEventTime() - mInitialTouchTime) < mTapTimeout &&
241135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                               distance < mTouchSlop) {
242135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        // tap
243135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        target.animate().cancel();
244135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        mTable.setSelection(target);
245135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    } else {
246135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                        onFling(target, mDX, mDY);
247135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    }
248135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    mA = INVALID_POINTER;
249135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                    mB = INVALID_POINTER;
250135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren                }
251135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            }
252135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            break;
253135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
254135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        case MotionEvent.ACTION_CANCEL:
255f61019ceb816fed9e5035c3d9b8451f6e4ee1da9Chris Wren            log("action cancel!");
256135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            break;
257135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        }
258135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
259135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        return true;
260135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    }
261135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren
262135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    private static void log(String message) {
263135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        if (DEBUG) {
264135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren            Log.i(TAG, message);
265135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren        }
266135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren    }
267135f525b62eb20c31c593e09f8bdb92215d538a4Chris Wren}
268