PreviewGestures.java revision d6954f337e20365fc24ecffdd6f30e17c6b31eff
1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera;
18
19import android.os.Handler;
20import android.os.Message;
21import android.util.Log;
22import android.view.MotionEvent;
23import android.view.ScaleGestureDetector;
24import android.view.View;
25import android.view.ViewConfiguration;
26
27import com.android.camera.ui.PieRenderer;
28import com.android.camera.ui.RenderOverlay;
29import com.android.camera.ui.ZoomRenderer;
30import com.android.gallery3d.R;
31
32import java.util.ArrayList;
33import java.util.List;
34
35public class PreviewGestures
36        implements ScaleGestureDetector.OnScaleGestureListener {
37
38    private static final String TAG = "CAM_gestures";
39
40    private static final long TIMEOUT_PIE = 200;
41    private static final int MSG_PIE = 1;
42    private static final int MODE_NONE = 0;
43    private static final int MODE_PIE = 1;
44    private static final int MODE_ZOOM = 2;
45    private static final int MODE_MODULE = 3;
46    private static final int MODE_ALL = 4;
47
48    private CameraActivity mActivity;
49    private SingleTapListener mTapListener;
50    private RenderOverlay mOverlay;
51    private PieRenderer mPie;
52    private ZoomRenderer mZoom;
53    private MotionEvent mDown;
54    private MotionEvent mCurrent;
55    private ScaleGestureDetector mScale;
56    private List<View> mReceivers;
57    private int mMode;
58    private int mSlop;
59    private int mTapTimeout;
60    private boolean mEnabled;
61    private boolean mZoomOnly;
62    private int mOrientation;
63    private int[] mLocation;
64
65    private Handler mHandler = new Handler() {
66        public void handleMessage(Message msg) {
67            if (msg.what == MSG_PIE) {
68                mMode = MODE_PIE;
69                openPie();
70                cancelActivityTouchHandling(mDown);
71            }
72        }
73    };
74
75    public interface SingleTapListener {
76        public void onSingleTapUp(View v, int x, int y);
77    }
78
79    public PreviewGestures(CameraActivity ctx, SingleTapListener tapListener,
80            ZoomRenderer zoom, PieRenderer pie) {
81        mActivity = ctx;
82        mTapListener = tapListener;
83        mPie = pie;
84        mZoom = zoom;
85        mMode = MODE_ALL;
86        mScale = new ScaleGestureDetector(ctx, this);
87        mSlop = (int) ctx.getResources().getDimension(R.dimen.pie_touch_slop);
88        mTapTimeout = ViewConfiguration.getTapTimeout();
89        mEnabled = true;
90        mLocation = new int[2];
91    }
92
93    public void setRenderOverlay(RenderOverlay overlay) {
94        mOverlay = overlay;
95    }
96
97    public void setOrientation(int orientation) {
98        mOrientation = orientation;
99    }
100
101    public void setEnabled(boolean enabled) {
102        mEnabled = enabled;
103        if (!enabled) {
104            cancelPie();
105        }
106    }
107
108    public void setZoomOnly(boolean zoom) {
109        mZoomOnly = zoom;
110    }
111
112    public void addTouchReceiver(View v) {
113        if (mReceivers == null) {
114            mReceivers = new ArrayList<View>();
115        }
116        mReceivers.add(v);
117    }
118
119    public void clearTouchReceivers() {
120        if (mReceivers != null) {
121            mReceivers.clear();
122        }
123    }
124
125    public boolean dispatchTouch(MotionEvent m) {
126        if (!mEnabled) {
127            return mActivity.superDispatchTouchEvent(m);
128        }
129        mCurrent = m;
130        if (MotionEvent.ACTION_DOWN == m.getActionMasked()) {
131            if (checkReceivers(m)) {
132                mMode = MODE_MODULE;
133                return mActivity.superDispatchTouchEvent(m);
134            } else {
135                mMode = MODE_ALL;
136                mDown = MotionEvent.obtain(m);
137                if (mPie != null && mPie.showsItems()) {
138                    mMode = MODE_PIE;
139                    return sendToPie(m);
140                }
141                if (mPie != null && !mZoomOnly) {
142                    mHandler.sendEmptyMessageDelayed(MSG_PIE, TIMEOUT_PIE);
143                }
144                if (mZoom != null) {
145                    mScale.onTouchEvent(m);
146                }
147                // make sure this is ok
148                return mActivity.superDispatchTouchEvent(m);
149            }
150        } else if (mMode == MODE_NONE) {
151            return false;
152        } else if (mMode == MODE_PIE) {
153            if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
154                sendToPie(makeCancelEvent(m));
155                if (mZoom != null) {
156                    onScaleBegin(mScale);
157                }
158            } else {
159                return sendToPie(m);
160            }
161            return true;
162        } else if (mMode == MODE_ZOOM) {
163            mScale.onTouchEvent(m);
164            if (!mScale.isInProgress() && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
165                mMode = MODE_NONE;
166                onScaleEnd(mScale);
167            }
168            return true;
169        } else if (mMode == MODE_MODULE) {
170            return mActivity.superDispatchTouchEvent(m);
171        } else {
172            // didn't receive down event previously;
173            // assume module wasn't initialzed and ignore this event.
174            if (mDown == null) {
175                return true;
176            }
177            if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
178                if (!mZoomOnly) {
179                    cancelPie();
180                    sendToPie(makeCancelEvent(m));
181                }
182                if (mZoom != null) {
183                    mScale.onTouchEvent(m);
184                    onScaleBegin(mScale);
185                }
186            } else if ((mMode == MODE_ZOOM) && !mScale.isInProgress()
187                    && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
188                // user initiated and stopped zoom gesture without zooming
189                mScale.onTouchEvent(m);
190                onScaleEnd(mScale);
191            }
192            // not zoom or pie mode and no timeout yet
193            if (mZoom != null) {
194                boolean res = mScale.onTouchEvent(m);
195                if (mScale.isInProgress()) {
196                    cancelPie();
197                    cancelActivityTouchHandling(m);
198                    return res;
199                }
200            }
201            if (MotionEvent.ACTION_UP == m.getActionMasked()) {
202                cancelPie();
203                cancelActivityTouchHandling(m);
204                // must have been tap
205                if (m.getEventTime() - mDown.getEventTime() < mTapTimeout) {
206                    mTapListener.onSingleTapUp(null,
207                            (int) mDown.getX() - mOverlay.getWindowPositionX(),
208                            (int) mDown.getY() - mOverlay.getWindowPositionY());
209                    return true;
210                } else {
211                    return mActivity.superDispatchTouchEvent(m);
212                }
213            } else if (MotionEvent.ACTION_MOVE == m.getActionMasked()) {
214                if ((Math.abs(m.getX() - mDown.getX()) > mSlop)
215                        || Math.abs(m.getY() - mDown.getY()) > mSlop) {
216                    // moved too far and no timeout yet, no focus or pie
217                    cancelPie();
218                    if (isSwipe(m, true)) {
219                        mMode = MODE_MODULE;
220                        return mActivity.superDispatchTouchEvent(m);
221                    } else {
222                        cancelActivityTouchHandling(m);
223                        if (isSwipe(m , false)) {
224                            mMode = MODE_NONE;
225                        } else if (!mZoomOnly) {
226                            mMode = MODE_PIE;
227                            openPie();
228                            sendToPie(m);
229                        }
230                    }
231                }
232            }
233            return false;
234        }
235    }
236
237    private boolean checkReceivers(MotionEvent m) {
238        if (mReceivers != null) {
239            for (View receiver : mReceivers) {
240                if (isInside(m, receiver)) {
241                    return true;
242                }
243            }
244        }
245        return false;
246    }
247
248    // left tests for finger moving right to left
249    private boolean isSwipe(MotionEvent m, boolean left) {
250        float dx = 0;
251        float dy = 0;
252        switch (mOrientation) {
253        case 0:
254            dx = m.getX() - mDown.getX();
255            dy = Math.abs(m.getY() - mDown.getY());
256            break;
257        case 90:
258            dx = - (m.getY() - mDown.getY());
259            dy = Math.abs(m.getX() - mDown.getX());
260            break;
261        case 180:
262            dx = -(m.getX() - mDown.getX());
263            dy = Math.abs(m.getY() - mDown.getY());
264            break;
265        case 270:
266            dx = m.getY() - mDown.getY();
267            dy = Math.abs(m.getX() - mDown.getX());
268            break;
269        }
270        if (left) {
271            return (dx < 0 && dy / -dx < 0.6f);
272        } else {
273            return (dx > 0 && dy / dx < 0.6f);
274        }
275    }
276
277    private boolean isInside(MotionEvent evt, View v) {
278        v.getLocationInWindow(mLocation);
279        return (v.getVisibility() == View.VISIBLE
280                && evt.getX() >= mLocation[0] && evt.getX() < mLocation[0] + v.getWidth()
281                && evt.getY() >= mLocation[1] && evt.getY() < mLocation[1] + v.getHeight());
282    }
283
284    public void cancelActivityTouchHandling(MotionEvent m) {
285        mActivity.superDispatchTouchEvent(makeCancelEvent(m));
286    }
287
288    private MotionEvent makeCancelEvent(MotionEvent m) {
289        MotionEvent c = MotionEvent.obtain(m);
290        c.setAction(MotionEvent.ACTION_CANCEL);
291        return c;
292    }
293
294    private void openPie() {
295        mDown.offsetLocation(-mOverlay.getWindowPositionX(),
296                -mOverlay.getWindowPositionY());
297        mOverlay.directDispatchTouch(mDown, mPie);
298    }
299
300    private void cancelPie() {
301        mHandler.removeMessages(MSG_PIE);
302    }
303
304    private boolean sendToPie(MotionEvent m) {
305        m.offsetLocation(-mOverlay.getWindowPositionX(),
306                -mOverlay.getWindowPositionY());
307        return mOverlay.directDispatchTouch(m, mPie);
308    }
309
310    @Override
311    public boolean onScale(ScaleGestureDetector detector) {
312        return mZoom.onScale(detector);
313    }
314
315    @Override
316    public boolean onScaleBegin(ScaleGestureDetector detector) {
317        if (mMode != MODE_ZOOM) {
318            mMode = MODE_ZOOM;
319            cancelActivityTouchHandling(mCurrent);
320        }
321        if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
322            return mZoom.onScaleBegin(detector);
323        } else {
324            return true;
325        }
326    }
327
328    @Override
329    public void onScaleEnd(ScaleGestureDetector detector) {
330        if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
331            mZoom.onScaleEnd(detector);
332        }
333    }
334}
335