diff options
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"); |