diff options
-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", |