/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.camera; import android.os.Handler; import android.os.Message; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; import android.view.ViewConfiguration; import com.android.camera.ui.PieRenderer; import com.android.camera.ui.RenderOverlay; import com.android.camera.ui.ZoomRenderer; import com.android.gallery3d.R; import java.util.ArrayList; import java.util.List; public class PreviewGestures implements ScaleGestureDetector.OnScaleGestureListener { private static final String TAG = "CAM_gestures"; private static final long TIMEOUT_PIE = 200; private static final int MSG_PIE = 1; private static final int MODE_NONE = 0; private static final int MODE_PIE = 1; private static final int MODE_ZOOM = 2; private static final int MODE_MODULE = 3; private static final int MODE_ALL = 4; private static final int MODE_SWIPE = 5; public static final int DIR_UP = 0; public static final int DIR_DOWN = 1; public static final int DIR_LEFT = 2; public static final int DIR_RIGHT = 3; private CameraActivity mActivity; private SingleTapListener mTapListener; private RenderOverlay mOverlay; private PieRenderer mPie; private ZoomRenderer mZoom; private MotionEvent mDown; private MotionEvent mCurrent; private ScaleGestureDetector mScale; private List mReceivers; private List mUnclickableAreas; private int mMode; private int mSlop; private int mTapTimeout; private boolean mEnabled; private boolean mZoomOnly; private int mOrientation; private int[] mLocation; private SwipeListener mSwipeListener; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { if (msg.what == MSG_PIE) { mMode = MODE_PIE; openPie(); cancelActivityTouchHandling(mDown); } } }; public interface SingleTapListener { public void onSingleTapUp(View v, int x, int y); } interface SwipeListener { public void onSwipe(int direction); } public PreviewGestures(CameraActivity ctx, SingleTapListener tapListener, ZoomRenderer zoom, PieRenderer pie, SwipeListener swipe) { mActivity = ctx; mTapListener = tapListener; mPie = pie; mZoom = zoom; mMode = MODE_ALL; mScale = new ScaleGestureDetector(ctx, this); mSlop = (int) ctx.getResources().getDimension(R.dimen.pie_touch_slop); mTapTimeout = ViewConfiguration.getTapTimeout(); mEnabled = true; mLocation = new int[2]; mSwipeListener = swipe; } public void setRenderOverlay(RenderOverlay overlay) { mOverlay = overlay; } public void setOrientation(int orientation) { mOrientation = orientation; } public void setEnabled(boolean enabled) { mEnabled = enabled; if (!enabled) { cancelPie(); } } public void setZoomOnly(boolean zoom) { mZoomOnly = zoom; } public void addTouchReceiver(View v) { if (mReceivers == null) { mReceivers = new ArrayList(); } mReceivers.add(v); } public void removeTouchReceiver(View v) { if (mReceivers == null || v == null) return; mReceivers.remove(v); } public void addUnclickableArea(View v) { if (mUnclickableAreas == null) { mUnclickableAreas = new ArrayList(); } mUnclickableAreas.add(v); } public void clearTouchReceivers() { if (mReceivers != null) { mReceivers.clear(); } } public void clearUnclickableAreas() { if (mUnclickableAreas != null) { mUnclickableAreas.clear(); } } private boolean checkClickable(MotionEvent m) { if (mUnclickableAreas != null) { for (View v : mUnclickableAreas) { if (isInside(m, v)) { return false; } } } return true; } public void reset() { clearTouchReceivers(); clearUnclickableAreas(); } public boolean dispatchTouch(MotionEvent m) { if (!mEnabled) { return mActivity.superDispatchTouchEvent(m); } mCurrent = m; if (MotionEvent.ACTION_DOWN == m.getActionMasked()) { if (checkReceivers(m)) { mMode = MODE_MODULE; return mActivity.superDispatchTouchEvent(m); } else { mMode = MODE_ALL; mDown = MotionEvent.obtain(m); if (mPie != null && mPie.showsItems()) { mMode = MODE_PIE; return sendToPie(m); } if (mPie != null && !mZoomOnly && checkClickable(m)) { mHandler.sendEmptyMessageDelayed(MSG_PIE, TIMEOUT_PIE); } if (mZoom != null) { mScale.onTouchEvent(m); } // make sure this is ok return mActivity.superDispatchTouchEvent(m); } } else if (mMode == MODE_NONE) { return false; } else if (mMode == MODE_SWIPE) { if (MotionEvent.ACTION_UP == m.getActionMasked()) { mSwipeListener.onSwipe(getSwipeDirection(m)); } return true; } else if (mMode == MODE_PIE) { if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) { sendToPie(makeCancelEvent(m)); if (mZoom != null) { onScaleBegin(mScale); } } else { return sendToPie(m); } return true; } else if (mMode == MODE_ZOOM) { mScale.onTouchEvent(m); if (!mScale.isInProgress() && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) { mMode = MODE_NONE; onScaleEnd(mScale); } return true; } else if (mMode == MODE_MODULE) { return mActivity.superDispatchTouchEvent(m); } else { // didn't receive down event previously; // assume module wasn't initialzed and ignore this event. if (mDown == null) { return true; } if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) { if (!mZoomOnly) { cancelPie(); sendToPie(makeCancelEvent(m)); } if (mZoom != null) { mScale.onTouchEvent(m); onScaleBegin(mScale); } } else if ((mMode == MODE_ZOOM) && !mScale.isInProgress() && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) { // user initiated and stopped zoom gesture without zooming mScale.onTouchEvent(m); onScaleEnd(mScale); } // not zoom or pie mode and no timeout yet if (mZoom != null) { boolean res = mScale.onTouchEvent(m); if (mScale.isInProgress()) { cancelPie(); cancelActivityTouchHandling(m); return res; } } if (MotionEvent.ACTION_UP == m.getActionMasked()) { cancelPie(); // must have been tap if (m.getEventTime() - mDown.getEventTime() < mTapTimeout && checkClickable(m)) { cancelActivityTouchHandling(m); mTapListener.onSingleTapUp(null, (int) mDown.getX() - mOverlay.getWindowPositionX(), (int) mDown.getY() - mOverlay.getWindowPositionY()); return true; } else { return mActivity.superDispatchTouchEvent(m); } } else if (MotionEvent.ACTION_MOVE == m.getActionMasked()) { if ((Math.abs(m.getX() - mDown.getX()) > mSlop) || Math.abs(m.getY() - mDown.getY()) > mSlop) { // moved too far and no timeout yet, no focus or pie cancelPie(); int dir = getSwipeDirection(m); if (dir == DIR_LEFT) { mMode = MODE_MODULE; return mActivity.superDispatchTouchEvent(m); } else { cancelActivityTouchHandling(m); mMode = MODE_NONE; } } } return false; } } private boolean checkReceivers(MotionEvent m) { if (mReceivers != null) { for (View receiver : mReceivers) { if (isInside(m, receiver)) { return true; } } } return false; } // left tests for finger moving right to left private int getSwipeDirection(MotionEvent m) { float dx = 0; float dy = 0; switch (mOrientation) { case 0: dx = m.getX() - mDown.getX(); dy = m.getY() - mDown.getY(); break; case 90: dx = - (m.getY() - mDown.getY()); dy = m.getX() - mDown.getX(); break; case 180: dx = -(m.getX() - mDown.getX()); dy = m.getY() - mDown.getY(); break; case 270: dx = m.getY() - mDown.getY(); dy = m.getX() - mDown.getX(); break; } if (dx < 0 && (Math.abs(dy) / -dx < 2)) return DIR_LEFT; if (dx > 0 && (Math.abs(dy) / dx < 2)) return DIR_RIGHT; if (dy > 0) return DIR_DOWN; return DIR_UP; } private boolean isInside(MotionEvent evt, View v) { v.getLocationInWindow(mLocation); // when view is flipped horizontally if ((int) v.getRotationY() == 180) { mLocation[0] -= v.getWidth(); } // when view is flipped vertically if ((int) v.getRotationX() == 180) { mLocation[1] -= v.getHeight(); } return (v.getVisibility() == View.VISIBLE && evt.getX() >= mLocation[0] && evt.getX() < mLocation[0] + v.getWidth() && evt.getY() >= mLocation[1] && evt.getY() < mLocation[1] + v.getHeight()); } public void cancelActivityTouchHandling(MotionEvent m) { mActivity.superDispatchTouchEvent(makeCancelEvent(m)); } private MotionEvent makeCancelEvent(MotionEvent m) { MotionEvent c = MotionEvent.obtain(m); c.setAction(MotionEvent.ACTION_CANCEL); return c; } private void openPie() { mDown.offsetLocation(-mOverlay.getWindowPositionX(), -mOverlay.getWindowPositionY()); mOverlay.directDispatchTouch(mDown, mPie); } private void cancelPie() { mHandler.removeMessages(MSG_PIE); } private boolean sendToPie(MotionEvent m) { m.offsetLocation(-mOverlay.getWindowPositionX(), -mOverlay.getWindowPositionY()); return mOverlay.directDispatchTouch(m, mPie); } @Override public boolean onScale(ScaleGestureDetector detector) { return mZoom.onScale(detector); } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { if (mMode != MODE_ZOOM) { mMode = MODE_ZOOM; cancelActivityTouchHandling(mCurrent); } if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) { return mZoom.onScaleBegin(detector); } else { return true; } } @Override public void onScaleEnd(ScaleGestureDetector detector) { if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) { mZoom.onScaleEnd(detector); } } }