blob: 0b80ff688b2bc4ef10131c6a2b20c694416ec995 [file] [log] [blame]
/*
* 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<View> mReceivers;
private List<View> 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<View>();
}
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<View>();
}
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);
}
}
}