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