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