summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEthan Chen <intervigil@gmail.com>2013-10-08 15:20:02 -0700
committerSteve Kondik <steve@cyngn.com>2015-10-18 00:51:44 -0700
commitfca49c41c379336a345237c60d2577a3c5d20171 (patch)
tree292854cc71c8b3fcd061b63cf15d9a6c5f52bd93
parenta45a56b204c320c04176f71881e85e36357fc27f (diff)
downloadframeworks_base-fca49c41c379336a345237c60d2577a3c5d20171.zip
frameworks_base-fca49c41c379336a345237c60d2577a3c5d20171.tar.gz
frameworks_base-fca49c41c379336a345237c60d2577a3c5d20171.tar.bz2
Support GESTURE_SENSOR input device type with GestureService
* The GESTURE_SENSOR input device type is meant to support touch sensors which are meant for gesture input only, very similar to a touchpad, but without the pointer capability. * Define separate service to handle gestures from GESTURE_SENSOR device type. Change-Id: I9b273df2a3cc141774d7f7cd81e43a90ea5b230b Hide InputDevice.SOURCE_GESTURE_SENSOR from API Change-Id: If009e9595fc593594b0e7764669996de137483a1 GestureInput : Allow doubletap/longpress configuration Allows devices to specify pending intents for double tap and long press events. Change-Id: I7e7cc2f9f96a01d8f6232e5cf0e19832fdfd5359
-rw-r--r--Android.mk1
-rw-r--r--cmds/input/src/com/android/commands/input/Input.java1
-rw-r--r--core/java/android/service/gesture/IGestureService.aidl11
-rw-r--r--core/java/android/view/InputDevice.java13
-rwxr-xr-xcore/res/res/values/config.xml3
-rwxr-xr-xcore/res/res/values/symbols.xml3
-rw-r--r--services/core/java/com/android/server/gesture/GestureInputFilter.java333
-rw-r--r--services/core/java/com/android/server/gesture/GestureService.java63
-rw-r--r--services/java/com/android/server/SystemServer.java21
9 files changed, 449 insertions, 0 deletions
diff --git a/Android.mk b/Android.mk
index efc0962..a663411 100644
--- a/Android.mk
+++ b/Android.mk
@@ -246,6 +246,7 @@ LOCAL_SRC_FILES += \
core/java/android/service/voice/IVoiceInteractionService.aidl \
core/java/android/service/voice/IVoiceInteractionSession.aidl \
core/java/android/service/voice/IVoiceInteractionSessionService.aidl \
+ core/java/android/service/gesture/IGestureService.aidl \
core/java/android/service/wallpaper/IWallpaperConnection.aidl \
core/java/android/service/wallpaper/IWallpaperEngine.aidl \
core/java/android/service/wallpaper/IWallpaperService.aidl \
diff --git a/cmds/input/src/com/android/commands/input/Input.java b/cmds/input/src/com/android/commands/input/Input.java
index 2a7c79b..40148c6 100644
--- a/cmds/input/src/com/android/commands/input/Input.java
+++ b/cmds/input/src/com/android/commands/input/Input.java
@@ -47,6 +47,7 @@ public class Input {
put("touchpad", InputDevice.SOURCE_TOUCHPAD);
put("touchnavigation", InputDevice.SOURCE_TOUCH_NAVIGATION);
put("joystick", InputDevice.SOURCE_JOYSTICK);
+ put("gesture", InputDevice.SOURCE_GESTURE_SENSOR);
}};
diff --git a/core/java/android/service/gesture/IGestureService.aidl b/core/java/android/service/gesture/IGestureService.aidl
new file mode 100644
index 0000000..1944d50
--- /dev/null
+++ b/core/java/android/service/gesture/IGestureService.aidl
@@ -0,0 +1,11 @@
+package android.service.gesture;
+
+import android.app.PendingIntent;
+
+/** @hide */
+interface IGestureService {
+
+ void setOnLongPressPendingIntent(in PendingIntent pendingIntent);
+ void setOnDoubleClickPendingIntent(in PendingIntent pendingIntent);
+
+}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index cc4598d..7642ed9 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -256,6 +256,18 @@ public final class InputDevice implements Parcelable {
public static final int SOURCE_TOUCH_NAVIGATION = 0x00200000 | SOURCE_CLASS_NONE;
/**
+ * The input source is a touch device whose motions should be interpreted as gestures.
+ *
+ * For example, an upward swipe should be treated the same as a swipe of the touchscreen.
+ * The same should apply for left, right, down swipes. Complex gestures may also be input.
+ *
+ * @see #SOURCE_CLASS_NONE
+ *
+ * @hide
+ */
+ public static final int SOURCE_GESTURE_SENSOR = 0x00400000 | SOURCE_CLASS_NONE;
+
+ /**
* The input source is a joystick.
* (It may also be a {@link #SOURCE_GAMEPAD}).
*
@@ -948,6 +960,7 @@ public final class InputDevice implements Parcelable {
appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHPAD, "touchpad");
appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK, "joystick");
appendSourceDescriptionIfApplicable(description, SOURCE_GAMEPAD, "gamepad");
+ appendSourceDescriptionIfApplicable(description, SOURCE_GESTURE_SENSOR, "gesture");
description.append(" )\n");
final int numAxes = mMotionRanges.size();
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index bc95f43..1dd1c61 100755
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2121,6 +2121,9 @@
<bool name="config_auto_attach_data_on_creation">true</bool>
+ <!-- True if the gesture service should be started at system start -->
+ <bool name="config_enableGestureService">false</bool>
+
<!-- Values for GPS configuration -->
<string-array translatable="false" name="config_gpsParameters">
<item>SUPL_HOST=supl.google.com</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0fe919d..01a4bad 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2375,4 +2375,7 @@
<!-- Data Connectivity Error Configurations -->
<java-symbol type="bool" name="config_reject_ggsn_perm_failure" />
<java-symbol type="bool" name="config_protocol_errors_perm_failure" />
+
+ <!-- Gesture Sensor -->
+ <java-symbol type="bool" name="config_enableGestureService" />
</resources>
diff --git a/services/core/java/com/android/server/gesture/GestureInputFilter.java b/services/core/java/com/android/server/gesture/GestureInputFilter.java
new file mode 100644
index 0000000..e40761f
--- /dev/null
+++ b/services/core/java/com/android/server/gesture/GestureInputFilter.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod 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.server.gesture;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.Display;
+import android.view.GestureDetector;
+import android.view.GestureDetector.OnDoubleTapListener;
+import android.view.IInputFilter;
+import android.view.IInputFilterHost;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import java.io.PrintWriter;
+
+/**
+ * A simple input filter that listens for gesture sensor events and converts
+ * them to input events to be injected into the input stream.
+ */
+public class GestureInputFilter implements IInputFilter, GestureDetector.OnGestureListener, OnDoubleTapListener {
+
+ private static final String TAG = "GestureInputFilter";
+ private static final boolean DEBUG = false;
+
+ private IInputFilterHost mHost = null;
+
+ private GestureDetector mGestureDetector;
+ private InputManager mInputManager;
+ private OrientationEventListener mOrientationListener;
+ private final int mScreenWidth, mScreenHeight;
+ private float mGesturePadWidth, mGesturePadHeight;
+ private int mTouchSlop, mOrientation;
+ private Context mContext;
+ private PendingIntent mLongPressPendingIntent;
+ private PendingIntent mDoubleClickPendingIntent;
+
+ public GestureInputFilter(Context context) {
+ mInputManager = InputManager.getInstance();
+ mContext = context;
+ for (int id : mInputManager.getInputDeviceIds()) {
+ InputDevice inputDevice = mInputManager.getInputDevice(id);
+ if ((inputDevice.getSources() & InputDevice.SOURCE_GESTURE_SENSOR)
+ == mInputManager.getInputDevice(id).getSources()) {
+ mGesturePadWidth = inputDevice.getMotionRange(MotionEvent.AXIS_X).getMax();
+ mGesturePadHeight = inputDevice.getMotionRange(MotionEvent.AXIS_Y).getMax();
+ break;
+ }
+ }
+ ViewConfiguration vc = ViewConfiguration.get(context);
+ mTouchSlop = vc.getScaledTouchSlop();
+ WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ Display display = wm.getDefaultDisplay();
+ mScreenWidth = display.getWidth();
+ mScreenHeight = display.getHeight();
+ mGestureDetector = new GestureDetector(context, this);
+ mGestureDetector.setOnDoubleTapListener(this);
+ mOrientationListener = new OrientationEventListener(context) {
+ @Override
+ public void onOrientationChanged(int orientation) {
+ if (orientation == -1) {
+ return;
+ }
+ mOrientation = (orientation + 45) / 90 * 90;
+ }
+ };
+ }
+
+ /**
+ * Called to enqueue the input event for filtering.
+ * The event must be recycled after the input filter processed it.
+ * This method is guaranteed to be non-reentrant.
+ *
+ * @see InputFilter#filterInputEvent(InputEvent, int)
+ * @param event The input event to enqueue.
+ */
+ // called by the input dispatcher thread
+ public void filterInputEvent(InputEvent event, int policyFlags)
+ throws RemoteException {
+ if (DEBUG) Slog.d(TAG, event.toString());
+
+ try {
+ if (event.getSource() != InputDevice.SOURCE_GESTURE_SENSOR
+ || !(event instanceof MotionEvent)) {
+ try {
+ mHost.sendInputEvent(event, policyFlags);
+ } catch (RemoteException e) {
+ /* ignore */
+ }
+ return;
+ }
+
+ MotionEvent motionEvent = (MotionEvent) event;
+ mGestureDetector.onTouchEvent(motionEvent);
+ } finally {
+ event.recycle();
+ }
+ }
+
+ // called by the input dispatcher thread
+ public void install(IInputFilterHost host) throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "Gesture input filter installed.");
+ }
+ mHost = host;
+ mOrientationListener.enable();
+ }
+
+ // called by the input dispatcher thread
+ public void uninstall() throws RemoteException {
+ if (DEBUG) {
+ Slog.d(TAG, "Gesture input filter uninstalled.");
+ }
+ mHost = null;
+ mOrientationListener.disable();
+ mContext = null;
+ }
+
+ // should never be called
+ public IBinder asBinder() {
+ throw new UnsupportedOperationException();
+ }
+
+ // called by a Binder thread
+ public void dump(PrintWriter pw, String prefix) {
+
+ }
+
+ private boolean generateSwipe(MotionEvent e1, MotionEvent e2) {
+ switch (mOrientation) {
+ case 90:
+ Slog.d(TAG, "Adjusting motion for 90 degrees");
+ e1.setLocation(e1.getY(), e1.getX());
+ e2.setLocation(e2.getY(), e2.getX());
+ break;
+ case 180:
+ Slog.d(TAG, "Adjusting motion for 180 degrees");
+ e1.setLocation(mGesturePadWidth - e1.getX(),
+ mGesturePadHeight - e1.getY());
+ e2.setLocation(mGesturePadWidth - e2.getX(),
+ mGesturePadHeight - e2.getY());
+ break;
+ case 270:
+ Slog.d(TAG, "Adjusting motion for 270 degrees");
+ e1.setLocation(mGesturePadHeight - e1.getY(),
+ e1.getX());
+ e2.setLocation(mGesturePadHeight - e2.getY(),
+ e2.getX());
+ break;
+ }
+
+ float deltaX = Math.abs(e1.getX() - e2.getX());
+ float deltaY = Math.abs(e1.getY() - e2.getY());
+
+ if (deltaX < mTouchSlop && deltaY < mTouchSlop) {
+ return false;
+ }
+
+ if (deltaX > deltaY) {
+ e2.setLocation(e2.getX(), e1.getY());
+ } else if (deltaY > deltaX) {
+ e2.setLocation(e1.getX(), e2.getY());
+ }
+
+ float scaleX = mScreenWidth / mGesturePadWidth;
+ float scaleY = mScreenHeight / mGesturePadHeight;
+
+ float magnitudeX = deltaX * scaleX;
+ float magnitudeY = deltaY * scaleY;
+
+ float origX = mScreenWidth / 2;
+ float origY = mScreenHeight / 2;
+ float endX = 0.0f;
+ float endY = 0.0f;
+
+ if (e2.getY() > e1.getY()) {
+ if (DEBUG) Slog.d(TAG, "Detected down motion");
+ // Ensure selection does not occur
+ endX = origX + mTouchSlop + 5;
+ endY = origY + magnitudeY;
+ } else if (e2.getY() < e1.getY()) {
+ if (DEBUG) Slog.d(TAG, "Detected up motion");
+ endX = origX + mTouchSlop + 5;
+ endY = origY - magnitudeY;
+ } else if (e2.getX() > e1.getX()) {
+ if (DEBUG) Slog.d(TAG, "Detected left motion");
+ endX = origX + magnitudeX;
+ endY = origY + mTouchSlop + 5;
+ } else if (e2.getX() < e1.getX()) {
+ if (DEBUG) Slog.d(TAG, "Detected right motion");
+ endX = origX - magnitudeX;
+ endY = origY + mTouchSlop + 5;
+ } else {
+ return false;
+ }
+
+ sendSwipe(origX, origY, endX, endY);
+ return true;
+ }
+
+ private void sendSwipe(float x1, float y1, float x2, float y2) {
+ final long duration = 100;
+ long now = SystemClock.uptimeMillis();
+ final long startTime = now;
+ final long endTime = startTime + duration;
+ sendMotionEvent(MotionEvent.ACTION_DOWN, now, x1, y1, 1.0f);
+
+ while (now < endTime) {
+ long elapsedTime = now - startTime;
+ float alpha = (float) elapsedTime / duration;
+ sendMotionEvent(MotionEvent.ACTION_MOVE, now,
+ lerp(x1, x2, alpha), lerp(y1, y2, alpha), 1.0f);
+ now = SystemClock.uptimeMillis();
+ }
+ sendMotionEvent(MotionEvent.ACTION_UP, now, x2, y2, 1.0f);
+ }
+
+ private void sendMotionEvent(int action, long when, float x, float y,
+ float pressure) {
+ final float DEFAULT_SIZE = 1.0f;
+ final int DEFAULT_META_STATE = 0;
+ final float DEFAULT_PRECISION_X = 1.0f;
+ final float DEFAULT_PRECISION_Y = 1.0f;
+ final int DEFAULT_DEVICE_ID = 0;
+ final int DEFAULT_EDGE_FLAGS = 0;
+
+ MotionEvent e = MotionEvent.obtain(when, when, action, x, y, pressure,
+ DEFAULT_SIZE, DEFAULT_META_STATE, DEFAULT_PRECISION_X,
+ DEFAULT_PRECISION_Y, DEFAULT_DEVICE_ID, DEFAULT_EDGE_FLAGS);
+ e.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+ sendInputEvent(e);
+ }
+
+ private void sendInputEvent(InputEvent event) {
+ mInputManager.injectInputEvent(event,
+ InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH);
+ }
+
+ private static final float lerp(float a, float b, float alpha) {
+ return (b - a) * alpha + a;
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public void onShowPress(MotionEvent e) {
+ }
+
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
+ float distanceY) {
+ return false;
+ }
+
+ @Override
+ public void onLongPress(MotionEvent e) {
+ if (mLongPressPendingIntent != null) {
+ try {
+ mLongPressPendingIntent.send();
+ } catch (CanceledException e1) {
+ e1.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return generateSwipe(e1, e2);
+ }
+
+ @Override
+ public boolean onSingleTapConfirmed(MotionEvent e) {
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ if (mDoubleClickPendingIntent != null) {
+ try {
+ mDoubleClickPendingIntent.send();
+ return true;
+ } catch (CanceledException e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTapEvent(MotionEvent e) {
+ return false;
+ }
+
+ public void setOnLongPressPendingIntent(PendingIntent pendingIntent) {
+ mLongPressPendingIntent = pendingIntent;
+ }
+
+ public void setOnDoubleClickPendingIntent(PendingIntent pendingIntent) {
+ mDoubleClickPendingIntent = pendingIntent;
+ }
+}
diff --git a/services/core/java/com/android/server/gesture/GestureService.java b/services/core/java/com/android/server/gesture/GestureService.java
new file mode 100644
index 0000000..1a01e41
--- /dev/null
+++ b/services/core/java/com/android/server/gesture/GestureService.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2013 The CyanogenMod Project (Jens Doll)
+ *
+ * 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.server.gesture;
+
+import android.Manifest;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.service.gesture.IGestureService;
+import android.util.Slog;
+
+import com.android.server.input.InputManagerService;
+
+/**
+ * A system service to track gesture sensor gestures. This service is
+ * responsible for creating input events from motion events generated by
+ * gesture sensor input hardware:
+ * <li>Installing an input filter to listen for gesture sensor events</li>
+ * <li>Generating input events to be injected into the input stream</li>
+ */
+public class GestureService extends IGestureService.Stub {
+ public static final String TAG = "GestureService";
+ public static final boolean DEBUG = false;
+
+ private Context mContext;
+ private InputManagerService mInputManager;
+ private GestureInputFilter mInputFilter;
+
+ public GestureService(Context context, InputManagerService inputManager) {
+ mContext = context;
+ mInputManager = inputManager;
+ }
+
+ // called by system server
+ public void systemReady() {
+ if (DEBUG) Slog.d(TAG, "Starting Gesture Sensor service");
+ mInputFilter = new GestureInputFilter(mContext);
+ mInputManager.registerSecondaryInputFilter(mInputFilter);
+ }
+
+ public void setOnLongPressPendingIntent(PendingIntent pendingIntent) {
+ mInputFilter.setOnLongPressPendingIntent(pendingIntent);
+ }
+
+ public void setOnDoubleClickPendingIntent(PendingIntent pendingIntent) {
+ mInputFilter.setOnDoubleClickPendingIntent(pendingIntent);
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index dd3b3a7..a689f55 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -61,6 +61,7 @@ import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.fingerprint.FingerprintService;
import com.android.server.hdmi.HdmiControlService;
+import com.android.server.gesture.GestureService;
import com.android.server.input.InputManagerService;
import com.android.server.job.JobSchedulerService;
import com.android.server.lights.LightsService;
@@ -544,6 +545,7 @@ public final class SystemServer {
LockSettingsService lockSettings = null;
AssetAtlasService atlas = null;
MediaRouterService mediaRouter = null;
+ GestureService gestureService = null;
// Bring up services needed for UI.
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
@@ -944,6 +946,17 @@ public final class SystemServer {
new GraphicsStatsService(context));
}
+ if (context.getResources().getBoolean(
+ com.android.internal.R.bool.config_enableGestureService)) {
+ try {
+ Slog.i(TAG, "Gesture Sensor Service");
+ gestureService = new GestureService(context, inputManager);
+ ServiceManager.addService("gesture", gestureService);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting Gesture Sensor Service", e);
+ }
+ }
+
if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_PRINTING)) {
mSystemServiceManager.startService(PRINT_MANAGER_SERVICE_CLASS);
}
@@ -1073,6 +1086,14 @@ public final class SystemServer {
reportWtf("making Display Manager Service ready", e);
}
+ if (gestureService != null) {
+ try {
+ gestureService.systemReady();
+ } catch (Throwable e) {
+ reportWtf("making Gesture Sensor Service ready", e);
+ }
+ }
+
// These are needed to propagate to the runnable below.
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;