diff options
author | Jeff Brown <jeffbrown@google.com> | 2012-04-13 04:09:27 -0700 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2012-04-13 17:01:15 -0700 |
commit | a47425a13c19f95057df78b8bb65bb25657e8753 (patch) | |
tree | 675c0d6bf611f2427bb3d11315d410bf9087b20a | |
parent | c2346134bb519a54d50655cbef940fc3fdec60a9 (diff) | |
download | frameworks_base-a47425a13c19f95057df78b8bb65bb25657e8753.zip frameworks_base-a47425a13c19f95057df78b8bb65bb25657e8753.tar.gz frameworks_base-a47425a13c19f95057df78b8bb65bb25657e8753.tar.bz2 |
Add support for input devices that have vibrators.
Added a getVibrator() method to InputDevice which returns a Vibrator
associated with that input device. Its uses the same API as the
system vibrator which makes it easy for applications to be modified
to use one or the other.
Bug: 6334179
Change-Id: Ifc7f13dbcb778670f3f1c07ccc562334e6109d2e
-rw-r--r-- | api/current.txt | 1 | ||||
-rw-r--r-- | core/java/android/hardware/input/IInputManager.aidl | 5 | ||||
-rwxr-xr-x | core/java/android/hardware/input/InputManager.java | 52 | ||||
-rw-r--r-- | core/java/android/os/NullVibrator.java | 55 | ||||
-rwxr-xr-x | core/java/android/view/InputDevice.java | 38 | ||||
-rw-r--r-- | core/jni/android_view_InputDevice.cpp | 4 | ||||
-rw-r--r-- | include/androidfw/InputDevice.h | 4 | ||||
-rw-r--r-- | libs/androidfw/InputDevice.cpp | 2 | ||||
-rw-r--r-- | services/input/EventHub.cpp | 66 | ||||
-rw-r--r-- | services/input/EventHub.h | 14 | ||||
-rw-r--r-- | services/input/InputReader.cpp | 172 | ||||
-rw-r--r-- | services/input/InputReader.h | 51 | ||||
-rw-r--r-- | services/input/tests/InputReader_test.cpp | 6 | ||||
-rw-r--r-- | services/java/com/android/server/input/InputManagerService.java | 90 | ||||
-rw-r--r-- | services/jni/com_android_server_input_InputManagerService.cpp | 36 |
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", |