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 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 88 public void setRenderOverlay(RenderOverlay overlay) { 89 mOverlay = overlay; 90 } 91 92 public void setOrientation(int orientation) { 93 mOrientation = orientation; 94 } 95 96 public void setEnabled(boolean enabled) { 97 mEnabled = enabled; 98 if (!enabled) { 99 cancelPie(); 100 } 101 } 102 103 public void setZoomOnly(boolean zoom) { 104 mZoomOnly = zoom; 105 } 106 107 public void addTouchReceiver(View v) { 108 if (mReceivers == null) { 109 mReceivers = new ArrayList<View>(); 110 } 111 mReceivers.add(v); 112 } 113 114 public void clearTouchReceivers() { 115 if (mReceivers != null) { 116 mReceivers.clear(); 117 } 118 } 119 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 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 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 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 279 public void cancelActivityTouchHandling(MotionEvent m) { 280 mActivity.superDispatchTouchEvent(makeCancelEvent(m)); 281 } 282 283 private MotionEvent makeCancelEvent(MotionEvent m) { 284 MotionEvent c = MotionEvent.obtain(m); 285 c.setAction(MotionEvent.ACTION_CANCEL); 286 return c; 287 } 288 289 private void openPie() { 290 mDown.offsetLocation(-mOverlay.getWindowPositionX(), 291 -mOverlay.getWindowPositionY()); 292 mOverlay.directDispatchTouch(mDown, mPie); 293 } 294 295 private void cancelPie() { 296 mHandler.removeMessages(MSG_PIE); 297 } 298 299 private boolean sendToPie(MotionEvent m) { 300 m.offsetLocation(-mOverlay.getWindowPositionX(), 301 -mOverlay.getWindowPositionY()); 302 return mOverlay.directDispatchTouch(m, mPie); 303 } 304 305 @Override 306 public boolean onScale(ScaleGestureDetector detector) { 307 return mZoom.onScale(detector); 308 } 309 310 @Override 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 324 public void onScaleEnd(ScaleGestureDetector detector) { 325 if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) { 326 mZoom.onScaleEnd(detector); 327 } 328 } 329 } 330