diff options
author | Ethan Chen <intervigil@gmail.com> | 2013-10-08 15:20:02 -0700 |
---|---|---|
committer | Steve Kondik <steve@cyngn.com> | 2015-10-18 00:51:44 -0700 |
commit | fca49c41c379336a345237c60d2577a3c5d20171 (patch) | |
tree | 292854cc71c8b3fcd061b63cf15d9a6c5f52bd93 | |
parent | a45a56b204c320c04176f71881e85e36357fc27f (diff) | |
download | frameworks_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.mk | 1 | ||||
-rw-r--r-- | cmds/input/src/com/android/commands/input/Input.java | 1 | ||||
-rw-r--r-- | core/java/android/service/gesture/IGestureService.aidl | 11 | ||||
-rw-r--r-- | core/java/android/view/InputDevice.java | 13 | ||||
-rwxr-xr-x | core/res/res/values/config.xml | 3 | ||||
-rwxr-xr-x | core/res/res/values/symbols.xml | 3 | ||||
-rw-r--r-- | services/core/java/com/android/server/gesture/GestureInputFilter.java | 333 | ||||
-rw-r--r-- | services/core/java/com/android/server/gesture/GestureService.java | 63 | ||||
-rw-r--r-- | services/java/com/android/server/SystemServer.java | 21 |
9 files changed, 449 insertions, 0 deletions
@@ -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; |