diff options
| author | 2021-02-03 17:10:55 -0800 | |
|---|---|---|
| committer | 2021-02-05 09:38:18 -0800 | |
| commit | f3a1533aa74759d37aa084cc598c077faf42fe7c (patch) | |
| tree | c295b8675b20fb7ba7d464ffb6136bebd63842d2 | |
| parent | 277cfb354dc76da33cb7c2cfaa914cedf73e130c (diff) | |
Add vibrator state listener support for input device vibrator
Extend Vibrator state and listener support to InputDevice vibrator.
InputDevice users can use the Vibrator listener API to register listener
to vibrator for state change.
Bug: 161634264
Test: atest InputDeviceVibratorTest
Change-Id: I991d3e79ad1734b5c6c99b802aab030bd1597daf
5 files changed, 230 insertions, 7 deletions
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index c69c47f80d01..eaa38f3e862c 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -26,6 +26,7 @@ import android.os.CombinedVibrationEffect; import android.hardware.input.IInputSensorEventListener; import android.hardware.input.InputSensorInfo; import android.os.IBinder; +import android.os.IVibratorStateListener; import android.os.VibrationEffect; import android.view.InputDevice; import android.view.InputEvent; @@ -92,6 +93,8 @@ interface IInputManager { void cancelVibrate(int deviceId, IBinder token); int[] getVibratorIds(int deviceId); boolean isVibrating(int deviceId); + boolean registerVibratorStateListener(int deviceId, in IVibratorStateListener listener); + boolean unregisterVibratorStateListener(int deviceId, in IVibratorStateListener listener); // Input device battery query. int getBatteryStatus(int deviceId); diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java index c60d6ce46fdb..f4d8a65d54c6 100644 --- a/core/java/android/hardware/input/InputDeviceVibrator.java +++ b/core/java/android/hardware/input/InputDeviceVibrator.java @@ -18,10 +18,18 @@ package android.hardware.input; import android.annotation.CallbackExecutor; import android.annotation.NonNull; +import android.app.ActivityThread; +import android.content.Context; import android.os.Binder; +import android.os.IVibratorStateListener; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.Vibrator; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; import java.util.concurrent.Executor; @@ -29,12 +37,18 @@ import java.util.concurrent.Executor; * Vibrator implementation that communicates with the input device vibrators. */ final class InputDeviceVibrator extends Vibrator { + private static final String TAG = "InputDeviceVibrator"; + // mDeviceId represents InputDevice ID the vibrator belongs to private final int mDeviceId; private final int mVibratorId; private final Binder mToken; private final InputManager mInputManager; + @GuardedBy("mDelegates") + private final ArrayMap<OnVibratorStateChangedListener, + OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>(); + InputDeviceVibrator(InputManager inputManager, int deviceId, int vibratorId) { mInputManager = inputManager; mDeviceId = deviceId; @@ -42,6 +56,23 @@ final class InputDeviceVibrator extends Vibrator { mToken = new Binder(); } + private class OnVibratorStateChangedListenerDelegate extends + IVibratorStateListener.Stub { + private final Executor mExecutor; + private final OnVibratorStateChangedListener mListener; + + OnVibratorStateChangedListenerDelegate(@NonNull OnVibratorStateChangedListener listener, + @NonNull Executor executor) { + mExecutor = executor; + mListener = listener; + } + + @Override + public void onVibrating(boolean isVibrating) { + mExecutor.execute(() -> mListener.onVibratorStateChanged(isVibrating)); + } + } + @Override public boolean hasVibrator() { return true; @@ -52,25 +83,73 @@ final class InputDeviceVibrator extends Vibrator { return mInputManager.isVibrating(mDeviceId); } - /* TODO: b/161634264 Support Vibrator listener API in input devices */ + /** + * Adds a listener for vibrator state changes. Callbacks will be executed on the main thread. + * If the listener was previously added and not removed, this call will be ignored. + * + * @param listener listener to be added + */ @Override public void addVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { - throw new UnsupportedOperationException( - "addVibratorStateListener not supported in InputDeviceVibrator"); + Preconditions.checkNotNull(listener); + Context context = ActivityThread.currentApplication(); + addVibratorStateListener(context.getMainExecutor(), listener); } + /** + * Adds a listener for vibrator state change. If the listener was previously added and not + * removed, this call will be ignored. + * + * @param listener Listener to be added. + * @param executor The {@link Executor} on which the listener's callbacks will be executed on. + */ @Override public void addVibratorStateListener( @NonNull @CallbackExecutor Executor executor, @NonNull OnVibratorStateChangedListener listener) { - throw new UnsupportedOperationException( - "addVibratorStateListener not supported in InputDeviceVibrator"); + Preconditions.checkNotNull(listener); + Preconditions.checkNotNull(executor); + + synchronized (mDelegates) { + // If listener is already registered, reject and return. + if (mDelegates.containsKey(listener)) { + Log.w(TAG, "Listener already registered."); + return; + } + + final OnVibratorStateChangedListenerDelegate delegate = + new OnVibratorStateChangedListenerDelegate(listener, executor); + if (!mInputManager.registerVibratorStateListener(mDeviceId, delegate)) { + Log.w(TAG, "Failed to register vibrate state listener"); + return; + } + mDelegates.put(listener, delegate); + + } } + /** + * Removes the listener for vibrator state changes. If the listener was not previously + * registered, this call will do nothing. + * + * @param listener Listener to be removed. + */ @Override public void removeVibratorStateListener(@NonNull OnVibratorStateChangedListener listener) { - throw new UnsupportedOperationException( - "removeVibratorStateListener not supported in InputDeviceVibrator"); + Preconditions.checkNotNull(listener); + + synchronized (mDelegates) { + // Check if the listener is registered, otherwise will return. + if (mDelegates.containsKey(listener)) { + final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener); + + if (!mInputManager.unregisterVibratorStateListener(mDeviceId, delegate)) { + Log.w(TAG, "Failed to unregister vibrate state listener"); + return; + } + mDelegates.remove(listener); + } + } } @Override diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 185c59d8ccfc..8a01c660ebd0 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -35,6 +35,7 @@ import android.os.Build; import android.os.CombinedVibrationEffect; import android.os.Handler; import android.os.IBinder; +import android.os.IVibratorStateListener; import android.os.InputEventInjectionSync; import android.os.Looper; import android.os.Message; @@ -1484,6 +1485,32 @@ public final class InputManager { } /** + * Register input device vibrator state listener + * + * @hide + */ + public boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) { + try { + return mIm.registerVibratorStateListener(deviceId, listener); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Unregister input device vibrator state listener + * + * @hide + */ + public boolean unregisterVibratorStateListener(int deviceId, IVibratorStateListener listener) { + try { + return mIm.unregisterVibratorStateListener(deviceId, listener); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** * Gets a sensor manager service associated with an input device, always create a new instance. * @return The sensor manager, never null. * @hide diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 2e4200c1f7d9..5ca255c6cdbe 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -57,6 +57,7 @@ import android.os.CombinedVibrationEffect; import android.os.Environment; import android.os.Handler; import android.os.IBinder; +import android.os.IVibratorStateListener; import android.os.InputEventInjectionResult; import android.os.InputEventInjectionSync; import android.os.LocaleList; @@ -64,6 +65,7 @@ import android.os.Looper; import android.os.Message; import android.os.MessageQueue; import android.os.Process; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ShellCallback; @@ -77,6 +79,7 @@ import android.util.ArrayMap; import android.util.Log; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.Display; import android.view.IInputFilter; import android.view.IInputFilterHost; @@ -100,6 +103,7 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.Preconditions; import com.android.internal.util.XmlUtils; import com.android.server.DisplayThread; import com.android.server.LocalServices; @@ -221,6 +225,14 @@ public class InputManagerService extends IInputManager.Stub private Map<IBinder, VibratorToken> mVibratorTokens = new ArrayMap<IBinder, VibratorToken>(); private int mNextVibratorTokenValue; + // List of currently registered vibrator state changed listeners by device id. + @GuardedBy("mVibratorLock") + private final SparseArray<RemoteCallbackList<IVibratorStateListener>> mVibratorStateListeners = + new SparseArray<RemoteCallbackList<IVibratorStateListener>>(); + // List of vibrator states by device id. + @GuardedBy("mVibratorLock") + private final SparseBooleanArray mIsVibrating = new SparseBooleanArray(); + // State for lid switch // Lock for the lid switch state. Held when triggering callbacks to guarantee lid switch events // are delivered in order. For ex, when a new lid switch callback is registered the lock is held @@ -2008,6 +2020,92 @@ public class InputManagerService extends IInputManager.Stub } } + // Native callback. + private void notifyVibratorState(int deviceId, boolean isOn) { + if (DEBUG) { + Slog.d(TAG, "notifyVibratorState: deviceId=" + deviceId + " isOn=" + isOn); + } + synchronized (mVibratorLock) { + mIsVibrating.put(deviceId, isOn); + notifyVibratorStateListenersLocked(deviceId); + } + } + + @GuardedBy("mVibratorLock") + private void notifyVibratorStateListenersLocked(int deviceId) { + if (!mVibratorStateListeners.contains(deviceId)) { + if (DEBUG) { + Slog.v(TAG, "Device " + deviceId + " doesn't have vibrator state listener."); + } + return; + } + RemoteCallbackList<IVibratorStateListener> listeners = + mVibratorStateListeners.get(deviceId); + final int length = listeners.beginBroadcast(); + try { + for (int i = 0; i < length; i++) { + notifyVibratorStateListenerLocked(deviceId, listeners.getBroadcastItem(i)); + } + } finally { + listeners.finishBroadcast(); + } + } + + @GuardedBy("mVibratorLock") + private void notifyVibratorStateListenerLocked(int deviceId, IVibratorStateListener listener) { + try { + listener.onVibrating(mIsVibrating.get(deviceId)); + } catch (RemoteException | RuntimeException e) { + Slog.e(TAG, "Vibrator state listener failed to call", e); + } + } + + @Override // Binder call + public boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) { + Preconditions.checkNotNull(listener, "listener must not be null"); + + RemoteCallbackList<IVibratorStateListener> listeners; + synchronized (mVibratorLock) { + if (!mVibratorStateListeners.contains(deviceId)) { + listeners = new RemoteCallbackList<>(); + mVibratorStateListeners.put(deviceId, listeners); + } else { + listeners = mVibratorStateListeners.get(deviceId); + } + + final long token = Binder.clearCallingIdentity(); + try { + if (!listeners.register(listener)) { + Slog.e(TAG, "Could not register vibrator state listener " + listener); + return false; + } + // Notify its callback after new client registered. + notifyVibratorStateListenerLocked(deviceId, listener); + return true; + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + + @Override // Binder call + public boolean unregisterVibratorStateListener(int deviceId, IVibratorStateListener listener) { + synchronized (mVibratorLock) { + final long token = Binder.clearCallingIdentity(); + try { + if (!mVibratorStateListeners.contains(deviceId)) { + Slog.w(TAG, "Vibrator state listener " + deviceId + " doesn't exist"); + return false; + } + RemoteCallbackList<IVibratorStateListener> listeners = + mVibratorStateListeners.get(deviceId); + return listeners.unregister(listener); + } finally { + Binder.restoreCallingIdentity(token); + } + } + } + // Binder call @Override public int getBatteryStatus(int deviceId) { diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 5b587e9859d8..643503d18bed 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -106,6 +106,7 @@ static struct { jmethodID notifyFocusChanged; jmethodID notifySensorEvent; jmethodID notifySensorAccuracy; + jmethodID notifyVibratorState; jmethodID notifyUntrustedTouch; jmethodID filterInputEvent; jmethodID interceptKeyBeforeQueueing; @@ -305,6 +306,7 @@ public: const std::vector<float>& values) override; void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType, InputDeviceSensorAccuracy accuracy) override; + void notifyVibratorState(int32_t deviceId, bool isOn) override; void notifyUntrustedTouch(const std::string& obscuringPackage) override; bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override; void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override; @@ -918,6 +920,18 @@ void NativeInputManager::notifySensorAccuracy(int32_t deviceId, InputDeviceSenso checkAndClearExceptionFromCallback(env, "notifySensorAccuracy"); } +void NativeInputManager::notifyVibratorState(int32_t deviceId, bool isOn) { +#if DEBUG_INPUT_DISPATCHER_POLICY + ALOGD("notifyVibratorState isOn:%d", isOn); +#endif + ATRACE_CALL(); + JNIEnv* env = jniEnv(); + ScopedLocalFrame localFrame(env); + env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyVibratorState, + static_cast<jint>(deviceId), static_cast<jboolean>(isOn)); + checkAndClearExceptionFromCallback(env, "notifyVibratorState"); +} + void NativeInputManager::getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -2248,6 +2262,8 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.notifySensorAccuracy, clazz, "notifySensorAccuracy", "(III)V"); + GET_METHOD_ID(gServiceClassInfo.notifyVibratorState, clazz, "notifyVibratorState", "(IZ)V"); + GET_METHOD_ID(gServiceClassInfo.notifyUntrustedTouch, clazz, "notifyUntrustedTouch", "(Ljava/lang/String;)V"); |