PreviewGestures.java revision d202574157ef3f9e655bbadc6fe87a5638ecb690
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;
30
31import java.util.ArrayList;
32import java.util.List;
33
34public class PreviewGestures
35        implements ScaleGestureDetector.OnScaleGestureListener {
36
37    private static final String TAG = "CAM_gestures";
38
39    private static final long TIMEOUT_PIE = 200;
40    private static final int MSG_PIE = 1;
41    private static final int MODE_NONE = 0;
42    private static final int MODE_PIE = 1;
43    private static final int MODE_ZOOM = 2;
44    private static final int MODE_MODULE = 3;
45    private static final int MODE_ALL = 4;
46
47    private CameraActivity mActivity;
48    private CameraModule mModule;
49    private RenderOverlay mOverlay;
50    private PieRenderer mPie;
51    private ZoomRenderer mZoom;
52    private MotionEvent mDown;
53    private MotionEvent mCurrent;
54    private ScaleGestureDetector mScale;
55    private List<View> mReceivers;
56    private int mMode;
57    private int mSlop;
58    private int mTapTimeout;
59    private boolean mEnabled;
60    private boolean mZoomOnly;
61    private int mOrientation;
62    private Handler mHandler = new Handler() {
63        public void handleMessage(Message msg) {
64            if (msg.what == MSG_PIE) {
65                mMode = MODE_PIE;
66                openPie();
67                cancelActivityTouchHandling(mDown);
68            }
69        }
70    };
71
72    public PreviewGestures(CameraActivity ctx, CameraModule module,
73            ZoomRenderer zoom, PieRenderer pie) {
74        mActivity = ctx;
75        mModule = module;
76        mPie = pie;
77        mZoom = zoom;
78        mMode = MODE_ALL;
79        mScale = new ScaleGestureDetector(ctx, this);
80        mSlop = (int) ctx.getResources().getDimension(R.dimen.pie_touch_slop);
81        mTapTimeout = ViewConfiguration.getTapTimeout();
82        mEnabled = true;
83    }
84
85    public void setRenderOverlay(RenderOverlay overlay) {
86        mOverlay = overlay;
87    }
88
89    public void setOrientation(int orientation) {
90        mOrientation = orientation;
91    }
92
93    public void setEnabled(boolean enabled) {
94        mEnabled = enabled;
95        if (!enabled) {
96            cancelPie();
97        }
98    }
99
100    public void setZoomOnly(boolean zoom) {
101        mZoomOnly = zoom;
102    }
103
104    public void addTouchReceiver(View v) {
105        if (mReceivers == null) {
106            mReceivers = new ArrayList<View>();
107        }
108        mReceivers.add(v);
109    }
110
111    public void clearTouchReceivers() {
112        if (mReceivers != null) {
113            mReceivers.clear();
114        }
115    }
116
117    public boolean dispatchTouch(MotionEvent m) {
118        if (!mEnabled) {
119            return mActivity.superDispatchTouchEvent(m);
120        }
121        mCurrent = m;
122        if (MotionEvent.ACTION_DOWN == m.getActionMasked()) {
123            if (checkReceivers(m)) {
124                mMode = MODE_MODULE;
125                return mActivity.superDispatchTouchEvent(m);
126            } else {
127                mMode = MODE_ALL;
128                mDown = MotionEvent.obtain(m);
129                if (mPie != null && mPie.showsItems()) {
130                    mMode = MODE_PIE;
131                    return sendToPie(m);
132                }
133                if (mPie != null && !mZoomOnly) {
134                    mHandler.sendEmptyMessageDelayed(MSG_PIE, TIMEOUT_PIE);
135                }
136                if (mZoom != null) {
137                    mScale.onTouchEvent(m);
138                }
139                // make sure this is ok
140                return mActivity.superDispatchTouchEvent(m);
141            }
142        } else if (mMode == MODE_NONE) {
143            return false;
144        } else if (mMode == MODE_PIE) {
145            if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
146                sendToPie(makeCancelEvent(m));
147                if (mZoom != null) {
148                    onScaleBegin(mScale);
149                }
150            } else {
151                return sendToPie(m);
152            }
153            return true;
154        } else if (mMode == MODE_ZOOM) {
155            mScale.onTouchEvent(m);
156            if (!mScale.isInProgress() && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
157                mMode = MODE_NONE;
158                onScaleEnd(mScale);
159            }
160            return true;
161        } else if (mMode == MODE_MODULE) {
162            return mActivity.superDispatchTouchEvent(m);
163        } else {
164            // didn't receive down event previously;
165            // assume module wasn't initialzed and ignore this event.
166            if (mDown == null) {
167                return true;
168            }
169            if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
170                if (!mZoomOnly) {
171                    cancelPie();
172                    sendToPie(makeCancelEvent(m));
173                }
174                if (mZoom != null) {
175                    mScale.onTouchEvent(m);
176                    onScaleBegin(mScale);
177                }
178            } else if ((mMode == MODE_ZOOM) && !mScale.isInProgress()
179                    && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
180                // user initiated and stopped zoom gesture without zooming
181                mScale.onTouchEvent(m);
182                onScaleEnd(mScale);
183            }
184            // not zoom or pie mode and no timeout yet
185            if (mZoom != null) {
186                boolean res = mScale.onTouchEvent(m);
187                if (mScale.isInProgress()) {
188                    cancelPie();
189                    cancelActivityTouchHandling(m);
190                    return res;
191                }
192            }
193            if (MotionEvent.ACTION_UP == m.getActionMasked()) {
194                cancelPie();
195                cancelActivityTouchHandling(m);
196                // must have been tap
197                if (m.getEventTime() - mDown.getEventTime() < mTapTimeout) {
198                    mModule.onSingleTapUp(null,
199                            (int) mDown.getX() - mOverlay.getWindowPositionX(),
200                            (int) mDown.getY() - mOverlay.getWindowPositionY());
201                    return true;
202                } else {
203                    return mActivity.superDispatchTouchEvent(m);
204                }
205            } else if (MotionEvent.ACTION_MOVE == m.getActionMasked()) {
206                if ((Math.abs(m.getX() - mDown.getX()) > mSlop)
207                        || Math.abs(m.getY() - mDown.getY()) > mSlop) {
208                    // moved too far and no timeout yet, no focus or pie
209                    cancelPie();
210                    if (isSwipe(m, true)) {
211                        mMode = MODE_MODULE;
212                        return mActivity.superDispatchTouchEvent(m);
213                    } else {
214                        cancelActivityTouchHandling(m);
215                        if (isSwipe(m , false)) {
216                            mMode = MODE_NONE;
217                        } else if (!mZoomOnly) {
218                            mMode = MODE_PIE;
219                            openPie();
220                            sendToPie(m);
221                        }
222                    }
223                }
224            }
225            return false;
226        }
227    }
228
229    private boolean checkReceivers(MotionEvent m) {
230        if (mReceivers != null) {
231            for (View receiver : mReceivers) {
232                if (isInside(m, receiver)) {
233                    return true;
234                }
235            }
236        }
237        return false;
238    }
239
240    // left tests for finger moving right to left
241    private boolean isSwipe(MotionEvent m, boolean left) {
242        float dx = 0;
243        float dy = 0;
244        switch (mOrientation) {
245        case 0:
246            dx = m.getX() - mDown.getX();
247            dy = Math.abs(m.getY() - mDown.getY());
248            break;
249        case 90:
250            dx = - (m.getY() - mDown.getY());
251            dy = Math.abs(m.getX() - mDown.getX());
252            break;
253        case 180:
254            dx = -(m.getX() - mDown.getX());
255            dy = Math.abs(m.getY() - mDown.getY());
256            break;
257        case 270:
258            dx = m.getY() - mDown.getY();
259            dy = Math.abs(m.getX() - mDown.getX());
260            break;
261        }
262        if (left) {
263            return (dx < 0 && dy / -dx < 0.6f);
264        } else {
265            return (dx > 0 && dy / dx < 0.6f);
266        }
267    }
268
269    private boolean isInside(MotionEvent evt, View v) {
270        return (v.getVisibility() == View.VISIBLE
271                && evt.getX() >= v.getLeft() && evt.getX() < v.getRight()
272                && evt.getY() >= v.getTop() && evt.getY() < v.getBottom());
273    }
274
275    public void cancelActivityTouchHandling(MotionEvent m) {
276        mActivity.superDispatchTouchEvent(makeCancelEvent(m));
277    }
278
279    private MotionEvent makeCancelEvent(MotionEvent m) {
280        MotionEvent c = MotionEvent.obtain(m);
281        c.setAction(MotionEvent.ACTION_CANCEL);
282        return c;
283    }
284
285    private void openPie() {
286        mDown.offsetLocation(-mOverlay.getWindowPositionX(),
287                -mOverlay.getWindowPositionY());
288        mOverlay.directDispatchTouch(mDown, mPie);
289    }
290
291    private void cancelPie() {
292        mHandler.removeMessages(MSG_PIE);
293    }
294
295    private boolean sendToPie(MotionEvent m) {
296        m.offsetLocation(-mOverlay.getWindowPositionX(),
297                -mOverlay.getWindowPositionY());
298        return mOverlay.directDispatchTouch(m, mPie);
299    }
300
301    @Override
302    public boolean onScale(ScaleGestureDetector detector) {
303        return mZoom.onScale(detector);
304    }
305
306    @Override
307    public boolean onScaleBegin(ScaleGestureDetector detector) {
308        if (mMode != MODE_ZOOM) {
309            mMode = MODE_ZOOM;
310        }
311        if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
312            return mZoom.onScaleBegin(detector);
313        } else {
314            return true;
315        }
316    }
317
318    @Override
319    public void onScaleEnd(ScaleGestureDetector detector) {
320        if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
321            mZoom.onScaleEnd(detector);
322        }
323    }
324}
325