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