summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt1
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl5
-rwxr-xr-xcore/java/android/hardware/input/InputManager.java52
-rw-r--r--core/java/android/os/NullVibrator.java55
-rwxr-xr-xcore/java/android/view/InputDevice.java38
-rw-r--r--core/jni/android_view_InputDevice.cpp4
-rw-r--r--include/androidfw/InputDevice.h4
-rw-r--r--libs/androidfw/InputDevice.cpp2
-rw-r--r--services/input/EventHub.cpp66
-rw-r--r--services/input/EventHub.h14
-rw-r--r--services/input/InputReader.cpp172
-rw-r--r--services/input/InputReader.h51
-rw-r--r--services/input/tests/InputReader_test.cpp6
-rw-r--r--services/java/com/android/server/input/InputManagerService.java90
-rw-r--r--services/jni/com_android_server_input_InputManagerService.cpp36
15 files changed, 589 insertions, 7 deletions
diff --git a/api/current.txt b/api/current.txt
index e2285e8..e43b0aa 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22327,6 +22327,7 @@ package android.view {
method public java.util.List<android.view.InputDevice.MotionRange> getMotionRanges();
method public java.lang.String getName();
method public int getSources();
+ method public android.os.Vibrator getVibrator();
method public boolean isVirtual();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index ca8321f..3137947 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -18,6 +18,7 @@ package android.hardware.input;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.IInputDevicesChangedListener;
+import android.os.IBinder;
import android.view.InputDevice;
import android.view.InputEvent;
@@ -46,4 +47,8 @@ interface IInputManager {
// Registers an input devices changed listener.
void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
+
+ // Input device vibrator control.
+ void vibrate(int deviceId, in long[] pattern, int repeat, IBinder token);
+ void cancelVibrate(int deviceId, IBinder token);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 35c49a1..b39b823 100755
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -19,12 +19,14 @@ package android.hardware.input;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.content.Context;
+import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.Vibrator;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.Log;
@@ -587,6 +589,15 @@ public final class InputManager {
}
/**
+ * Gets a vibrator service associated with an input device, assuming it has one.
+ * @return The vibrator, never null.
+ * @hide
+ */
+ public Vibrator getInputDeviceVibrator(int deviceId) {
+ return new InputDeviceVibrator(deviceId);
+ }
+
+ /**
* Listens for changes in input devices.
*/
public interface InputDeviceListener {
@@ -645,4 +656,45 @@ public final class InputManager {
}
}
}
+
+ private final class InputDeviceVibrator extends Vibrator {
+ private final int mDeviceId;
+ private final Binder mToken;
+
+ public InputDeviceVibrator(int deviceId) {
+ mDeviceId = deviceId;
+ mToken = new Binder();
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ return true;
+ }
+
+ @Override
+ public void vibrate(long milliseconds) {
+ vibrate(new long[] { 0, milliseconds}, -1);
+ }
+
+ @Override
+ public void vibrate(long[] pattern, int repeat) {
+ if (repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ try {
+ mIm.vibrate(mDeviceId, pattern, repeat, mToken);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to vibrate.", ex);
+ }
+ }
+
+ @Override
+ public void cancel() {
+ try {
+ mIm.cancelVibrate(mDeviceId, mToken);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to cancel vibration.", ex);
+ }
+ }
+ }
}
diff --git a/core/java/android/os/NullVibrator.java b/core/java/android/os/NullVibrator.java
new file mode 100644
index 0000000..8de4e06
--- /dev/null
+++ b/core/java/android/os/NullVibrator.java
@@ -0,0 +1,55 @@
+/*
+ * 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 android.os;
+
+import android.util.Log;
+
+/**
+ * Vibrator implementation that does nothing.
+ *
+ * @hide
+ */
+public class NullVibrator extends Vibrator {
+ private static final NullVibrator sInstance = new NullVibrator();
+
+ private NullVibrator() {
+ }
+
+ public static NullVibrator getInstance() {
+ return sInstance;
+ }
+
+ @Override
+ public boolean hasVibrator() {
+ return false;
+ }
+
+ @Override
+ public void vibrate(long milliseconds) {
+ }
+
+ @Override
+ public void vibrate(long[] pattern, int repeat) {
+ if (repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ }
+
+ @Override
+ public void cancel() {
+ }
+}
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 4ebb679..4848a7a 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,9 +16,12 @@
package android.view;
+import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Vibrator;
+import android.os.NullVibrator;
import java.util.ArrayList;
import java.util.List;
@@ -46,8 +49,11 @@ public final class InputDevice implements Parcelable {
private final int mSources;
private final int mKeyboardType;
private final KeyCharacterMap mKeyCharacterMap;
+ private final boolean mHasVibrator;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
+ private Vibrator mVibrator; // guarded by mMotionRanges during initialization
+
/**
* A mask for input source classes.
*
@@ -304,7 +310,7 @@ public final class InputDevice implements Parcelable {
// Called by native code.
private InputDevice(int id, int generation, String name, String descriptor, int sources,
- int keyboardType, KeyCharacterMap keyCharacterMap) {
+ int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator) {
mId = id;
mGeneration = generation;
mName = name;
@@ -312,6 +318,7 @@ public final class InputDevice implements Parcelable {
mSources = sources;
mKeyboardType = keyboardType;
mKeyCharacterMap = keyCharacterMap;
+ mHasVibrator = hasVibrator;
}
private InputDevice(Parcel in) {
@@ -322,6 +329,7 @@ public final class InputDevice implements Parcelable {
mSources = in.readInt();
mKeyboardType = in.readInt();
mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in);
+ mHasVibrator = in.readInt() != 0;
for (;;) {
int axis = in.readInt();
@@ -522,6 +530,31 @@ public final class InputDevice implements Parcelable {
}
/**
+ * Gets the vibrator service associated with the device, if there is one.
+ * Even if the device does not have a vibrator, the result is never null.
+ * Use {@link Vibrator#hasVibrator} to determine whether a vibrator is
+ * present.
+ *
+ * Note that the vibrator associated with the device may be different from
+ * the system vibrator. To obtain an instance of the system vibrator instead, call
+ * {@link Context#getSystemService} with {@link Context#VIBRATOR_SERVICE} as argument.
+ *
+ * @return The vibrator service associated with the device, never null.
+ */
+ public Vibrator getVibrator() {
+ synchronized (mMotionRanges) {
+ if (mVibrator == null) {
+ if (mHasVibrator) {
+ mVibrator = InputManager.getInstance().getInputDeviceVibrator(mId);
+ } else {
+ mVibrator = NullVibrator.getInstance();
+ }
+ }
+ return mVibrator;
+ }
+ }
+
+ /**
* Provides information about the range of values for a particular {@link MotionEvent} axis.
*
* @see InputDevice#getMotionRange(int)
@@ -617,6 +650,7 @@ public final class InputDevice implements Parcelable {
out.writeInt(mSources);
out.writeInt(mKeyboardType);
mKeyCharacterMap.writeToParcel(out, flags);
+ out.writeInt(mHasVibrator ? 1 : 0);
final int numRanges = mMotionRanges.size();
for (int i = 0; i < numRanges; i++) {
@@ -657,6 +691,8 @@ public final class InputDevice implements Parcelable {
}
description.append("\n");
+ description.append(" Has Vibrator: ").append(mHasVibrator).append("\n");
+
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index e8a3a3b..5cb172b 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -57,7 +57,7 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi
gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(),
nameObj.get(), descriptorObj.get(),
deviceInfo.getSources(), deviceInfo.getKeyboardType(),
- kcmObj.get()));
+ kcmObj.get(), deviceInfo.hasVibrator()));
const Vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
for (size_t i = 0; i < ranges.size(); i++) {
@@ -87,7 +87,7 @@ int register_android_view_InputDevice(JNIEnv* env)
gInputDeviceClassInfo.clazz = jclass(env->NewGlobalRef(gInputDeviceClassInfo.clazz));
GET_METHOD_ID(gInputDeviceClassInfo.ctor, gInputDeviceClassInfo.clazz,
- "<init>", "(IILjava/lang/String;Ljava/lang/String;IILandroid/view/KeyCharacterMap;)V");
+ "<init>", "(IILjava/lang/String;Ljava/lang/String;IILandroid/view/KeyCharacterMap;Z)V");
GET_METHOD_ID(gInputDeviceClassInfo.addMotionRange, gInputDeviceClassInfo.clazz,
"addMotionRange", "(IIFFFF)V");
diff --git a/include/androidfw/InputDevice.h b/include/androidfw/InputDevice.h
index 2eac544..38203af 100644
--- a/include/androidfw/InputDevice.h
+++ b/include/androidfw/InputDevice.h
@@ -93,6 +93,9 @@ public:
return mKeyCharacterMap;
}
+ inline void setVibrator(bool hasVibrator) { mHasVibrator = hasVibrator; }
+ inline bool hasVibrator() const { return mHasVibrator; }
+
inline const Vector<MotionRange>& getMotionRanges() const {
return mMotionRanges;
}
@@ -105,6 +108,7 @@ private:
uint32_t mSources;
int32_t mKeyboardType;
sp<KeyCharacterMap> mKeyCharacterMap;
+ bool mHasVibrator;
Vector<MotionRange> mMotionRanges;
};
diff --git a/libs/androidfw/InputDevice.cpp b/libs/androidfw/InputDevice.cpp
index 6bb06a9..d6c49f7 100644
--- a/libs/androidfw/InputDevice.cpp
+++ b/libs/androidfw/InputDevice.cpp
@@ -136,6 +136,7 @@ InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) :
mSources(other.mSources),
mKeyboardType(other.mKeyboardType),
mKeyCharacterMap(other.mKeyCharacterMap),
+ mHasVibrator(other.mHasVibrator),
mMotionRanges(other.mMotionRanges) {
}
@@ -150,6 +151,7 @@ void InputDeviceInfo::initialize(int32_t id, int32_t generation,
mDescriptor = descriptor;
mSources = 0;
mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+ mHasVibrator = false;
mMotionRanges.clear();
}
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index fbffc94..c0eb1b9 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -161,12 +161,14 @@ EventHub::Device::Device(int fd, int32_t id, const String8& path,
const InputDeviceIdentifier& identifier) :
next(NULL),
fd(fd), id(id), path(path), identifier(identifier),
- classes(0), configuration(NULL), virtualKeyMap(NULL) {
+ classes(0), configuration(NULL), virtualKeyMap(NULL),
+ ffEffectPlaying(false), ffEffectId(-1) {
memset(keyBitmask, 0, sizeof(keyBitmask));
memset(absBitmask, 0, sizeof(absBitmask));
memset(relBitmask, 0, sizeof(relBitmask));
memset(swBitmask, 0, sizeof(swBitmask));
memset(ledBitmask, 0, sizeof(ledBitmask));
+ memset(ffBitmask, 0, sizeof(ffBitmask));
memset(propBitmask, 0, sizeof(propBitmask));
}
@@ -534,6 +536,62 @@ sp<KeyCharacterMap> EventHub::getKeyCharacterMap(int32_t deviceId) const {
return NULL;
}
+void EventHub::vibrate(int32_t deviceId, nsecs_t duration) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual()) {
+ ff_effect effect;
+ memset(&effect, 0, sizeof(effect));
+ effect.type = FF_RUMBLE;
+ effect.id = device->ffEffectId;
+ effect.u.rumble.strong_magnitude = 0xc000;
+ effect.u.rumble.weak_magnitude = 0xc000;
+ effect.replay.length = (duration + 999999LL) / 1000000LL;
+ effect.replay.delay = 0;
+ if (ioctl(device->fd, EVIOCSFF, &effect)) {
+ ALOGW("Could not upload force feedback effect to device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ device->ffEffectId = effect.id;
+
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_FF;
+ ev.code = device->ffEffectId;
+ ev.value = 1;
+ if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+ ALOGW("Could not start force feedback effect on device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ device->ffEffectPlaying = true;
+ }
+}
+
+void EventHub::cancelVibrate(int32_t deviceId) {
+ AutoMutex _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ if (device && !device->isVirtual()) {
+ if (device->ffEffectPlaying) {
+ device->ffEffectPlaying = false;
+
+ struct input_event ev;
+ ev.time.tv_sec = 0;
+ ev.time.tv_usec = 0;
+ ev.type = EV_FF;
+ ev.code = device->ffEffectId;
+ ev.value = 0;
+ if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) {
+ ALOGW("Could not stop force feedback effect on device %s due to error %d.",
+ device->identifier.name.string(), errno);
+ return;
+ }
+ }
+ }
+}
+
EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const {
if (deviceId == BUILT_IN_KEYBOARD_ID) {
deviceId = mBuiltInKeyboardId;
@@ -949,6 +1007,7 @@ status_t EventHub::openDeviceLocked(const char *devicePath) {
ioctl(fd, EVIOCGBIT(EV_REL, sizeof(device->relBitmask)), device->relBitmask);
ioctl(fd, EVIOCGBIT(EV_SW, sizeof(device->swBitmask)), device->swBitmask);
ioctl(fd, EVIOCGBIT(EV_LED, sizeof(device->ledBitmask)), device->ledBitmask);
+ ioctl(fd, EVIOCGBIT(EV_FF, sizeof(device->ffBitmask)), device->ffBitmask);
ioctl(fd, EVIOCGPROP(sizeof(device->propBitmask)), device->propBitmask);
// See if this is a keyboard. Ignore everything in the button range except for
@@ -1010,6 +1069,11 @@ status_t EventHub::openDeviceLocked(const char *devicePath) {
}
}
+ // Check whether this device supports the vibrator.
+ if (test_bit(FF_RUMBLE, device->ffBitmask)) {
+ device->classes |= INPUT_DEVICE_CLASS_VIBRATOR;
+ }
+
// Configure virtual keys.
if ((device->classes & INPUT_DEVICE_CLASS_TOUCH)) {
// Load the virtual keys for the touch screen, if any.
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index 88159e7..51d2bac 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -113,6 +113,9 @@ enum {
/* The input device is a joystick (implies gamepad, has joystick absolute axes). */
INPUT_DEVICE_CLASS_JOYSTICK = 0x00000100,
+ /* The input device has a vibrator (supports FF_RUMBLE). */
+ INPUT_DEVICE_CLASS_VIBRATOR = 0x00000200,
+
/* The input device is virtual (not a real device, not part of UI configuration). */
INPUT_DEVICE_CLASS_VIRTUAL = 0x40000000,
@@ -219,6 +222,10 @@ public:
virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const = 0;
+ /* Control the vibrator. */
+ virtual void vibrate(int32_t deviceId, nsecs_t duration) = 0;
+ virtual void cancelVibrate(int32_t deviceId) = 0;
+
/* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
virtual void requestReopenDevices() = 0;
@@ -277,6 +284,9 @@ public:
virtual sp<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const;
+ virtual void vibrate(int32_t deviceId, nsecs_t duration);
+ virtual void cancelVibrate(int32_t deviceId);
+
virtual void requestReopenDevices();
virtual void wake();
@@ -303,6 +313,7 @@ private:
uint8_t relBitmask[(REL_MAX + 1) / 8];
uint8_t swBitmask[(SW_MAX + 1) / 8];
uint8_t ledBitmask[(LED_MAX + 1) / 8];
+ uint8_t ffBitmask[(FF_MAX + 1) / 8];
uint8_t propBitmask[(INPUT_PROP_MAX + 1) / 8];
String8 configurationFile;
@@ -310,6 +321,9 @@ private:
VirtualKeyMap* virtualKeyMap;
KeyMap keyMap;
+ bool ffEffectPlaying;
+ int16_t ffEffectId; // initially -1
+
Device(int fd, int32_t id, const String8& path, const InputDeviceIdentifier& identifier);
~Device();
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 71eba52..8c37fbb 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -36,6 +36,9 @@
// Log debug messages about gesture detection.
#define DEBUG_GESTURES 0
+// Log debug messages about the vibrator.
+#define DEBUG_VIBRATOR 0
+
#include "InputReader.h"
#include <cutils/log.h>
@@ -273,9 +276,7 @@ void InputReader::loopOnce() {
mConfigurationChangesToRefresh = 0;
timeoutMillis = 0;
refreshConfigurationLocked(changes);
- }
-
- if (timeoutMillis < 0 && mNextTimeout != LLONG_MAX) {
+ } else if (mNextTimeout != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
}
@@ -426,6 +427,11 @@ InputDevice* InputReader::createDeviceLocked(int32_t deviceId,
device->addMapper(new SwitchInputMapper(device));
}
+ // Vibrator-like devices.
+ if (classes & INPUT_DEVICE_CLASS_VIBRATOR) {
+ device->addMapper(new VibratorInputMapper(device));
+ }
+
// Keyboard-like devices.
uint32_t keyboardSource = 0;
int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
@@ -594,6 +600,7 @@ void InputReader::fadePointerLocked() {
void InputReader::requestTimeoutAtTimeLocked(nsecs_t when) {
if (when < mNextTimeout) {
mNextTimeout = when;
+ mEventHub->wake();
}
}
@@ -721,6 +728,27 @@ void InputReader::requestRefreshConfiguration(uint32_t changes) {
}
}
+void InputReader::vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token) {
+ AutoMutex _l(mLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ device->vibrate(pattern, patternSize, repeat, token);
+ }
+}
+
+void InputReader::cancelVibrate(int32_t deviceId, int32_t token) {
+ AutoMutex _l(mLock);
+
+ ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
+ if (deviceIndex >= 0) {
+ InputDevice* device = mDevices.valueAt(deviceIndex);
+ device->cancelVibrate(token);
+ }
+}
+
void InputReader::dump(String8& dump) {
AutoMutex _l(mLock);
@@ -1054,6 +1082,23 @@ bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
return result;
}
+void InputDevice::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->vibrate(pattern, patternSize, repeat, token);
+ }
+}
+
+void InputDevice::cancelVibrate(int32_t token) {
+ size_t numMappers = mMappers.size();
+ for (size_t i = 0; i < numMappers; i++) {
+ InputMapper* mapper = mMappers[i];
+ mapper->cancelVibrate(token);
+ }
+}
+
int32_t InputDevice::getMetaState() {
int32_t result = 0;
size_t numMappers = mMappers.size();
@@ -1739,6 +1784,13 @@ bool InputMapper::markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
return false;
}
+void InputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+}
+
+void InputMapper::cancelVibrate(int32_t token) {
+}
+
int32_t InputMapper::getMetaState() {
return 0;
}
@@ -1796,6 +1848,120 @@ int32_t SwitchInputMapper::getSwitchState(uint32_t sourceMask, int32_t switchCod
}
+// --- VibratorInputMapper ---
+
+VibratorInputMapper::VibratorInputMapper(InputDevice* device) :
+ InputMapper(device), mVibrating(false) {
+}
+
+VibratorInputMapper::~VibratorInputMapper() {
+}
+
+uint32_t VibratorInputMapper::getSources() {
+ return 0;
+}
+
+void VibratorInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ info->setVibrator(true);
+}
+
+void VibratorInputMapper::process(const RawEvent* rawEvent) {
+ // TODO: Handle FF_STATUS, although it does not seem to be widely supported.
+}
+
+void VibratorInputMapper::vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token) {
+#if DEBUG_VIBRATOR
+ String8 patternStr;
+ for (size_t i = 0; i < patternSize; i++) {
+ if (i != 0) {
+ patternStr.append(", ");
+ }
+ patternStr.appendFormat("%lld", pattern[i]);
+ }
+ ALOGD("vibrate: deviceId=%d, pattern=[%s], repeat=%ld, token=%d",
+ getDeviceId(), patternStr.string(), repeat, token);
+#endif
+
+ mVibrating = true;
+ memcpy(mPattern, pattern, patternSize * sizeof(nsecs_t));
+ mPatternSize = patternSize;
+ mRepeat = repeat;
+ mToken = token;
+ mIndex = -1;
+
+ nextStep();
+}
+
+void VibratorInputMapper::cancelVibrate(int32_t token) {
+#if DEBUG_VIBRATOR
+ ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
+#endif
+
+ if (mVibrating && mToken == token) {
+ stopVibrating();
+ }
+}
+
+void VibratorInputMapper::timeoutExpired(nsecs_t when) {
+ if (mVibrating) {
+ if (when >= mNextStepTime) {
+ nextStep();
+ } else {
+ getContext()->requestTimeoutAtTime(mNextStepTime);
+ }
+ }
+}
+
+void VibratorInputMapper::nextStep() {
+ mIndex += 1;
+ if (size_t(mIndex) >= mPatternSize) {
+ if (mRepeat < 0) {
+ // We are done.
+ stopVibrating();
+ return;
+ }
+ mIndex = mRepeat;
+ }
+
+ bool vibratorOn = mIndex & 1;
+ nsecs_t duration = mPattern[mIndex];
+ if (vibratorOn) {
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: sending vibrate deviceId=%d, duration=%lld",
+ getDeviceId(), duration);
+#endif
+ getEventHub()->vibrate(getDeviceId(), duration);
+ } else {
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+ getEventHub()->cancelVibrate(getDeviceId());
+ }
+ nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
+ mNextStepTime = now + duration;
+ getContext()->requestTimeoutAtTime(mNextStepTime);
+#if DEBUG_VIBRATOR
+ ALOGD("nextStep: scheduled timeout in %0.3fms", duration * 0.000001f);
+#endif
+}
+
+void VibratorInputMapper::stopVibrating() {
+ mVibrating = false;
+#if DEBUG_VIBRATOR
+ ALOGD("stopVibrating: sending cancel vibrate deviceId=%d", getDeviceId());
+#endif
+ getEventHub()->cancelVibrate(getDeviceId());
+}
+
+void VibratorInputMapper::dump(String8& dump) {
+ dump.append(INDENT2 "Vibrator Input Mapper:\n");
+ dump.appendFormat(INDENT3 "Vibrating: %s\n", toString(mVibrating));
+}
+
+
// --- KeyboardInputMapper ---
KeyboardInputMapper::KeyboardInputMapper(InputDevice* device,
diff --git a/services/input/InputReader.h b/services/input/InputReader.h
index d29776d..ed57596 100644
--- a/services/input/InputReader.h
+++ b/services/input/InputReader.h
@@ -33,6 +33,14 @@
#include <stddef.h>
#include <unistd.h>
+// Maximum supported size of a vibration pattern.
+// Must be at least 2.
+#define MAX_VIBRATE_PATTERN_SIZE 100
+
+// Maximum allowable delay value in a vibration pattern before
+// which the delay will be truncated.
+#define MAX_VIBRATE_PATTERN_DELAY_NSECS (1000000 * 1000000000LL)
+
namespace android {
class InputDevice;
@@ -267,6 +275,11 @@ public:
* The changes flag is a bitfield that indicates what has changed and whether
* the input devices must all be reopened. */
virtual void requestRefreshConfiguration(uint32_t changes) = 0;
+
+ /* Controls the vibrator of a particular input device. */
+ virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token) = 0;
+ virtual void cancelVibrate(int32_t deviceId, int32_t token) = 0;
};
@@ -334,6 +347,10 @@ public:
virtual void requestRefreshConfiguration(uint32_t changes);
+ virtual void vibrate(int32_t deviceId, const nsecs_t* pattern, size_t patternSize,
+ ssize_t repeat, int32_t token);
+ virtual void cancelVibrate(int32_t deviceId, int32_t token);
+
protected:
// These members are protected so they can be instrumented by test cases.
virtual InputDevice* createDeviceLocked(int32_t deviceId,
@@ -466,6 +483,8 @@ public:
int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
const int32_t* keyCodes, uint8_t* outFlags);
+ void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat, int32_t token);
+ void cancelVibrate(int32_t token);
int32_t getMetaState();
@@ -848,6 +867,9 @@ public:
virtual int32_t getSwitchState(uint32_t sourceMask, int32_t switchCode);
virtual bool markSupportedKeyCodes(uint32_t sourceMask, size_t numCodes,
const int32_t* keyCodes, uint8_t* outFlags);
+ virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token);
+ virtual void cancelVibrate(int32_t token);
virtual int32_t getMetaState();
@@ -880,6 +902,35 @@ private:
};
+class VibratorInputMapper : public InputMapper {
+public:
+ VibratorInputMapper(InputDevice* device);
+ virtual ~VibratorInputMapper();
+
+ virtual uint32_t getSources();
+ virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo);
+ virtual void process(const RawEvent* rawEvent);
+
+ virtual void vibrate(const nsecs_t* pattern, size_t patternSize, ssize_t repeat,
+ int32_t token);
+ virtual void cancelVibrate(int32_t token);
+ virtual void timeoutExpired(nsecs_t when);
+ virtual void dump(String8& dump);
+
+private:
+ bool mVibrating;
+ nsecs_t mPattern[MAX_VIBRATE_PATTERN_SIZE];
+ size_t mPatternSize;
+ ssize_t mRepeat;
+ int32_t mToken;
+ ssize_t mIndex;
+ nsecs_t mNextStepTime;
+
+ void nextStep();
+ void stopVibrating();
+};
+
+
class KeyboardInputMapper : public InputMapper {
public:
KeyboardInputMapper(InputDevice* device, uint32_t source, int32_t keyboardType);
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index e59af4e..94d4189 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -646,6 +646,12 @@ private:
return NULL;
}
+ virtual void vibrate(int32_t deviceId, nsecs_t duration) {
+ }
+
+ virtual void cancelVibrate(int32_t deviceId) {
+ }
+
virtual bool isExternal(int32_t deviceId) const {
return false;
}
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index ce7671f..e819432 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -105,6 +105,12 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
mTempInputDevicesChangedListenersToNotify =
new ArrayList<InputDevicesChangedListenerRecord>(); // handler thread only
+ // State for vibrator tokens.
+ private Object mVibratorLock = new Object();
+ private HashMap<IBinder, VibratorToken> mVibratorTokens =
+ new HashMap<IBinder, VibratorToken>();
+ private int mNextVibratorTokenValue;
+
// State for the currently installed input filter.
final Object mInputFilterLock = new Object();
InputFilter mInputFilter; // guarded by mInputFilterLock
@@ -142,6 +148,9 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
InputChannel fromChannel, InputChannel toChannel);
private static native void nativeSetPointerSpeed(int ptr, int speed);
private static native void nativeSetShowTouches(int ptr, boolean enabled);
+ private static native void nativeVibrate(int ptr, int deviceId, long[] pattern,
+ int repeat, int token);
+ private static native void nativeCancelVibrate(int ptr, int deviceId, int token);
private static native String nativeDump(int ptr);
private static native void nativeMonitor(int ptr);
@@ -792,6 +801,65 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
return result;
}
+ // Binder call
+ @Override
+ public void vibrate(int deviceId, long[] pattern, int repeat, IBinder token) {
+ if (repeat >= pattern.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+
+ VibratorToken v;
+ synchronized (mVibratorLock) {
+ v = mVibratorTokens.get(token);
+ if (v == null) {
+ v = new VibratorToken(deviceId, token, mNextVibratorTokenValue++);
+ try {
+ token.linkToDeath(v, 0);
+ } catch (RemoteException ex) {
+ // give up
+ throw new RuntimeException(ex);
+ }
+ mVibratorTokens.put(token, v);
+ }
+ }
+
+ synchronized (v) {
+ v.mVibrating = true;
+ nativeVibrate(mPtr, deviceId, pattern, repeat, v.mTokenValue);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void cancelVibrate(int deviceId, IBinder token) {
+ VibratorToken v;
+ synchronized (mVibratorLock) {
+ v = mVibratorTokens.get(token);
+ if (v == null || v.mDeviceId != deviceId) {
+ return; // nothing to cancel
+ }
+ }
+
+ cancelVibrateIfNeeded(v);
+ }
+
+ void onVibratorTokenDied(VibratorToken v) {
+ synchronized (mVibratorLock) {
+ mVibratorTokens.remove(v.mToken);
+ }
+
+ cancelVibrateIfNeeded(v);
+ }
+
+ private void cancelVibrateIfNeeded(VibratorToken v) {
+ synchronized (v) {
+ if (v.mVibrating) {
+ nativeCancelVibrate(mPtr, v.mDeviceId, v.mTokenValue);
+ v.mVibrating = false;
+ }
+ }
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission("android.permission.DUMP")
@@ -1108,4 +1176,26 @@ public class InputManagerService extends IInputManager.Stub implements Watchdog.
}
}
}
+
+ private final class VibratorToken implements DeathRecipient {
+ public final int mDeviceId;
+ public final IBinder mToken;
+ public final int mTokenValue;
+
+ public boolean mVibrating;
+
+ public VibratorToken(int deviceId, IBinder token, int tokenValue) {
+ mDeviceId = deviceId;
+ mToken = token;
+ mTokenValue = tokenValue;
+ }
+
+ @Override
+ public void binderDied() {
+ if (DEBUG) {
+ Slog.d(TAG, "Vibrator token died.");
+ }
+ onVibratorTokenDied(this);
+ }
+ }
}
diff --git a/services/jni/com_android_server_input_InputManagerService.cpp b/services/jni/com_android_server_input_InputManagerService.cpp
index f1536fd..3795074 100644
--- a/services/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/jni/com_android_server_input_InputManagerService.cpp
@@ -1226,6 +1226,38 @@ static void nativeSetShowTouches(JNIEnv* env,
im->setShowTouches(enabled);
}
+static void nativeVibrate(JNIEnv* env,
+ jclass clazz, jint ptr, jint deviceId, jlongArray patternObj,
+ jint repeat, jint token) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ size_t patternSize = env->GetArrayLength(patternObj);
+ if (patternSize > MAX_VIBRATE_PATTERN_SIZE) {
+ ALOGI("Skipped requested vibration because the pattern size is %d "
+ "which is more than the maximum supported size of %d.",
+ patternSize, MAX_VIBRATE_PATTERN_SIZE);
+ return; // limit to reasonable size
+ }
+
+ jlong* patternMillis = static_cast<jlong*>(env->GetPrimitiveArrayCritical(
+ patternObj, NULL));
+ nsecs_t pattern[patternSize];
+ for (size_t i = 0; i < patternSize; i++) {
+ pattern[i] = max(jlong(0), min(patternMillis[i],
+ MAX_VIBRATE_PATTERN_DELAY_NSECS / 1000000LL)) * 1000000LL;
+ }
+ env->ReleasePrimitiveArrayCritical(patternObj, patternMillis, JNI_ABORT);
+
+ im->getInputManager()->getReader()->vibrate(deviceId, pattern, patternSize, repeat, token);
+}
+
+static void nativeCancelVibrate(JNIEnv* env,
+ jclass clazz, jint ptr, jint deviceId, jint token) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+
+ im->getInputManager()->getReader()->cancelVibrate(deviceId, token);
+}
+
static jstring nativeDump(JNIEnv* env, jclass clazz, jint ptr) {
NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
@@ -1287,6 +1319,10 @@ static JNINativeMethod gInputManagerMethods[] = {
(void*) nativeSetPointerSpeed },
{ "nativeSetShowTouches", "(IZ)V",
(void*) nativeSetShowTouches },
+ { "nativeVibrate", "(II[JII)V",
+ (void*) nativeVibrate },
+ { "nativeCancelVibrate", "(III)V",
+ (void*) nativeCancelVibrate },
{ "nativeDump", "(I)Ljava/lang/String;",
(void*) nativeDump },
{ "nativeMonitor", "(I)V",