diff options
| author | 2020-09-10 12:13:43 -0700 | |
|---|---|---|
| committer | 2020-12-16 15:41:32 -0800 | |
| commit | 1d29b4c0878cccf3c1040baf11bb10474b5b0a41 (patch) | |
| tree | 486b7138ba9b2fdcfc4d23854c10fd76d1741991 | |
| parent | 95adf19d4c8b0e41d5e733c854211cecb000c0c1 (diff) | |
Add feature for inputdevice sensor manager.
Add support of sensor manager in InputDevice and allow user to obtain
SensorManager object to query supported sensor types and register sensor
event listeners.
Bug: 161634265
Test: atest InputDeviceSensorManagerTest
Change-Id: Ia7a04a0fcf7e026c49a4fb1a9009b132ff84cf7c
16 files changed, 1918 insertions, 16 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 4984804dab39..29073d6fcaf0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -46677,6 +46677,7 @@ package android.view { method public java.util.List<android.view.InputDevice.MotionRange> getMotionRanges(); method public String getName(); method public int getProductId(); + method @NonNull public android.hardware.SensorManager getSensorManager(); method public int getSources(); method public int getVendorId(); method public android.os.Vibrator getVibrator(); diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index e913986ae792..f7c4c2c5ca15 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -19,6 +19,7 @@ package android.hardware; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; +import android.hardware.input.InputSensorInfo; import android.os.Build; /** @@ -923,6 +924,30 @@ public final class Sensor { } /** + * Construct a sensor object from SensorInfo of an input device. + * This is only used for constructing an input device sensor object. + * @hide + */ + public Sensor(InputSensorInfo sensorInfo) { + this.mName = sensorInfo.getName(); + this.mVendor = sensorInfo.getVendor(); + this.mVersion = sensorInfo.getVersion(); + this.mHandle = sensorInfo.getHandle(); + this.mType = sensorInfo.getType(); + this.mMaxRange = sensorInfo.getMaxRange(); + this.mResolution = sensorInfo.getResolution(); + this.mPower = sensorInfo.getPower(); + this.mMinDelay = sensorInfo.getMinDelay(); + this.mFifoReservedEventCount = sensorInfo.getFifoReservedEventCount(); + this.mFifoMaxEventCount = sensorInfo.getFifoMaxEventCount(); + this.mStringType = sensorInfo.getStringType(); + this.mRequiredPermission = sensorInfo.getRequiredPermission(); + this.mMaxDelay = sensorInfo.getMaxDelay(); + this.mFlags = sensorInfo.getFlags(); + this.mId = sensorInfo.getId(); + } + + /** * @return name string of the sensor. */ public String getName() { diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java index 236fab0275cf..232f23429720 100644 --- a/core/java/android/hardware/SensorEvent.java +++ b/core/java/android/hardware/SensorEvent.java @@ -16,6 +16,7 @@ package android.hardware; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; /** @@ -667,4 +668,16 @@ public class SensorEvent { SensorEvent(int valueSize) { values = new float[valueSize]; } + + /** + * Construct a sensor event object by sensor object, accuracy, timestamp and values. + * This is only used for constructing an input device sensor event object. + * @hide + */ + public SensorEvent(@NonNull Sensor sensor, int accuracy, long timestamp, float[] values) { + this.sensor = sensor; + this.accuracy = accuracy; + this.timestamp = timestamp; + this.values = values; + } } diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index 3f3c3bf4e2df..b39df4d2b6bf 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -23,6 +23,8 @@ import android.hardware.input.IInputDevicesChangedListener; import android.hardware.input.ITabletModeChangedListener; import android.hardware.input.TouchCalibration; import android.os.CombinedVibrationEffect; +import android.hardware.input.IInputSensorEventListener; +import android.hardware.input.InputSensorInfo; import android.os.IBinder; import android.os.VibrationEffect; import android.view.InputDevice; @@ -105,4 +107,17 @@ interface IInputManager { // Remove the runtime association between the input port and the display port. Any existing // static association for the cleared input port will be restored. void removePortAssociation(in String inputPort); + + InputSensorInfo[] getSensorList(int deviceId); + + boolean registerSensorListener(IInputSensorEventListener listener); + + void unregisterSensorListener(IInputSensorEventListener listener); + + boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs, + int maxBatchReportLatencyUs); + + void disableSensor(int deviceId, int sensorType); + + boolean flushSensor(int deviceId, int sensorType); } diff --git a/core/java/android/hardware/input/IInputSensorEventListener.aidl b/core/java/android/hardware/input/IInputSensorEventListener.aidl new file mode 100644 index 000000000000..508f59000e6e --- /dev/null +++ b/core/java/android/hardware/input/IInputSensorEventListener.aidl @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 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.hardware.input; + +/** @hide */ +interface IInputSensorEventListener { + /* Called when there is a new sensor event. */ + oneway void onInputSensorChanged(int deviceId, int sensorId, int accuracy, long timestamp, + in float[] values); + + /* Called when the accuracy of the registered sensor has changed. */ + oneway void onInputSensorAccuracyChanged(int deviceId, int sensorId, int accuracy); +} diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java new file mode 100644 index 000000000000..56c2cddcbf15 --- /dev/null +++ b/core/java/android/hardware/input/InputDeviceSensorManager.java @@ -0,0 +1,661 @@ +/* + * Copyright (C) 2020 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.hardware.input; + +import android.annotation.NonNull; +import android.hardware.HardwareBuffer; +import android.hardware.Sensor; +import android.hardware.SensorAdditionalInfo; +import android.hardware.SensorDirectChannel; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.hardware.TriggerEventListener; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.MemoryFile; +import android.os.Message; +import android.os.RemoteException; +import android.util.Slog; +import android.view.InputDevice; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.SomeArgs; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Sensor manager implementation that communicates with the input device + * sensors. + * @hide + */ +public class InputDeviceSensorManager implements InputManager.InputDeviceListener { + private static final String TAG = "InputDeviceSensorManager"; + private static final boolean DEBUG = false; + + private static final int MSG_SENSOR_ACCURACY_CHANGED = 1; + private static final int MSG_SENSOR_CHANGED = 2; + + private InputManager mInputManager; + + // sensor map from device id to sensor list + @GuardedBy("mInputSensorLock") + private final Map<Integer, List<Sensor>> mSensors = new HashMap<>(); + + private final Object mInputSensorLock = new Object(); + private InputSensorEventListener mInputServiceSensorListener; + @GuardedBy("mInputSensorLock") + private final ArrayList<InputSensorEventListenerDelegate> mInputSensorEventListeners = + new ArrayList<InputSensorEventListenerDelegate>(); + private HandlerThread mSensorThread = null; + private Handler mSensorHandler = null; + + public InputDeviceSensorManager(InputManager inputManager) { + mInputManager = inputManager; + + mSensorThread = new HandlerThread("SensorThread"); + mSensorThread.start(); + mSensorHandler = new Handler(mSensorThread.getLooper()); + + // Register the input device listener + mInputManager.registerInputDeviceListener(this, mSensorHandler); + // Initialize the sensor list + initializeSensors(); + } + + /* + * Get SensorManager object for specific input device + * + * @param deviceId Input device ID + * @return SensorManager object for input device + */ + SensorManager getSensorManager(int deviceId) { + return new InputSensorManager(deviceId); + } + + /* + * Update input device sensor info for specified input device ID. + */ + private void updateInputDeviceSensorInfoLocked(int deviceId) { + final InputDevice inputDevice = InputDevice.getDevice(deviceId); + if (inputDevice.hasSensor()) { + final InputSensorInfo[] sensorInfos = + mInputManager.getSensorList(deviceId); + populateSensorsForInputDeviceLocked(deviceId, sensorInfos); + } + } + + @Override + public void onInputDeviceAdded(int deviceId) { + synchronized (mInputSensorLock) { + if (!mSensors.containsKey(deviceId)) { + updateInputDeviceSensorInfoLocked(deviceId); + } else { + Slog.e(TAG, "Received 'device added' notification for device " + deviceId + + ", but it is already in the list"); + } + } + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + synchronized (mInputSensorLock) { + mSensors.remove(deviceId); + } + } + + @Override + public void onInputDeviceChanged(int deviceId) { + synchronized (mInputSensorLock) { + if (mSensors.containsKey(deviceId)) { + mSensors.remove(deviceId); + } + updateInputDeviceSensorInfoLocked(deviceId); + } + } + + private static boolean sensorEquals(@NonNull Sensor lhs, @NonNull Sensor rhs) { + if (lhs.getType() == rhs.getType() && lhs.getId() == rhs.getId()) { + return true; + } + return false; + } + + private void populateSensorsForInputDeviceLocked(int deviceId, InputSensorInfo[] sensorInfos) { + List<Sensor> sensors = new ArrayList<Sensor>(); + for (int i = 0; i < sensorInfos.length; i++) { + Sensor sensor = new Sensor(sensorInfos[i]); + if (DEBUG) { + Slog.d(TAG, "Device " + deviceId + " sensor " + sensor.getStringType() + " added"); + } + sensors.add(sensor); + } + mSensors.put(deviceId, sensors); + } + + private void initializeSensors() { + synchronized (mInputSensorLock) { + mSensors.clear(); + int[] deviceIds = mInputManager.getInputDeviceIds(); + for (int i = 0; i < deviceIds.length; i++) { + final int deviceId = deviceIds[i]; + updateInputDeviceSensorInfoLocked(deviceId); + } + } + } + + /** + * Get a sensor object for input device, with specific sensor type. + * @param deviceId The input devicd ID + * @param sensorType The sensor type + * @return The sensor object if exists or null + */ + @GuardedBy("mInputSensorLock") + private Sensor getInputDeviceSensorLocked(int deviceId, int sensorType) { + List<Sensor> sensors = mSensors.get(deviceId); + for (Sensor sensor : sensors) { + if (sensor.getType() == sensorType) { + return sensor; + } + } + return null; + } + + @GuardedBy("mInputSensorLock") + private int findSensorEventListenerLocked(SensorEventListener listener) { + for (int i = 0; i < mInputSensorEventListeners.size(); i++) { + if (mInputSensorEventListeners.get(i).getListener() == listener) { + return i; + } + } + return Integer.MIN_VALUE; + } + + private void onInputSensorChanged(int deviceId, int sensorType, int accuracy, long timestamp, + float[] values) { + if (DEBUG) { + Slog.d(TAG, "Sensor changed: deviceId =" + deviceId + + " timestamp=" + timestamp + " sensorType=" + sensorType); + } + synchronized (mInputSensorLock) { + SensorEvent event = createSensorEvent( + InputDevice.getDevice(deviceId), sensorType, accuracy, timestamp, values); + if (event == null) { + Slog.wtf(TAG, "Failed to create SensorEvent."); + return; + } + for (int i = 0; i < mInputSensorEventListeners.size(); i++) { + InputSensorEventListenerDelegate listener = + mInputSensorEventListeners.get(i); + if (listener.hasSensorRegistered(deviceId, sensorType)) { + listener.sendSensorChanged(event); + } + } + } + } + + private void onInputSensorAccuracyChanged(int deviceId, int sensorType, int accuracy) { + if (DEBUG) { + Slog.d(TAG, "Sensor accuracy changed: " + + "accuracy=" + accuracy + ", sensorType=" + sensorType); + } + synchronized (mInputSensorLock) { + for (int i = 0; i < mInputSensorEventListeners.size(); i++) { + InputSensorEventListenerDelegate listener = + mInputSensorEventListeners.get(i); + if (listener.hasSensorRegistered(deviceId, sensorType)) { + listener.sendSensorAccuracyChanged(deviceId, sensorType, accuracy); + } + } + } + } + + private final class InputSensorEventListener extends IInputSensorEventListener.Stub { + @Override + public void onInputSensorChanged(int deviceId, int sensorType, int accuracy, long timestamp, + float[] values) throws RemoteException { + InputDeviceSensorManager.this.onInputSensorChanged( + deviceId, sensorType, accuracy, timestamp, values); + } + + @Override + public void onInputSensorAccuracyChanged(int deviceId, int sensorType, int accuracy) + throws RemoteException { + InputDeviceSensorManager.this.onInputSensorAccuracyChanged(deviceId, sensorType, + accuracy); + } + + } + + private static final class InputSensorEventListenerDelegate extends Handler { + private final SensorEventListener mListener; + private final int mDelayUs; + private final int mMaxBatchReportLatencyUs; + private List<Sensor> mSensors = new ArrayList<Sensor>(); + + InputSensorEventListenerDelegate(SensorEventListener listener, Sensor sensor, + int delayUs, int maxBatchReportLatencyUs, Handler handler) { + super(handler != null ? handler.getLooper() : Looper.myLooper()); + mListener = listener; + mSensors.add(sensor); + mDelayUs = delayUs; + mMaxBatchReportLatencyUs = maxBatchReportLatencyUs; + } + + public List<Sensor> getSensors() { + return mSensors; + } + + public boolean isEmpty() { + return mSensors.isEmpty(); + } + + /** + * Remove sensor from sensor list for listener + */ + public void removeSensor(Sensor sensor) { + // If sensor is not specified the listener will be unregistered for all sensors + // and the sensor list is cleared. + if (sensor == null) { + mSensors.clear(); + } + for (Sensor s : mSensors) { + if (sensorEquals(s, sensor)) { + mSensors.remove(sensor); + } + } + } + + /** + * Add a sensor to listener's sensor list + */ + public void addSensor(@NonNull Sensor sensor) { + for (Sensor s : mSensors) { + if (sensorEquals(s, sensor)) { + Slog.w(TAG, "Adding sensor " + sensor + " already exist!"); + return; + } + } + mSensors.add(sensor); + } + + /** + * Check if the listener has been registered to the sensor + * @param deviceId The input device ID of the sensor + * @param sensorType The sensor type of the sensor + * @return true if specified sensor is registered for the listener. + */ + public boolean hasSensorRegistered(int deviceId, int sensorType) { + for (Sensor sensor : mSensors) { + if (sensor.getType() == sensorType && sensor.getId() == deviceId) { + return true; + } + } + return false; + } + + /** + * Get listener handle for the delegate + */ + public SensorEventListener getListener() { + return mListener; + } + + /** + * Send sensor changed message + */ + public void sendSensorChanged(SensorEvent event) { + SomeArgs args = SomeArgs.obtain(); + obtainMessage(MSG_SENSOR_CHANGED, event).sendToTarget(); + } + + /** + * Send sensor accuracy changed message + */ + public void sendSensorAccuracyChanged(int deviceId, int sensorType, int accuracy) { + SomeArgs args = SomeArgs.obtain(); + obtainMessage(MSG_SENSOR_ACCURACY_CHANGED, deviceId, sensorType, accuracy) + .sendToTarget(); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SENSOR_ACCURACY_CHANGED: { + final int deviceId = msg.arg1; + final int sensorType = msg.arg2; + final int accuracy = (int) msg.obj; + for (Sensor sensor : mSensors) { + if (sensor.getId() == deviceId && sensor.getType() == sensorType) { + mListener.onAccuracyChanged(sensor, accuracy); + } + } + break; + } + case MSG_SENSOR_CHANGED: { + SensorEvent event = (SensorEvent) msg.obj; + mListener.onSensorChanged(event); + break; + } + } + } + } + + /** + * Create SensorEvent object for input device, with specified device ID, sensor Type, + * sensor event timestamp, accuracy, and sensor values. + */ + private SensorEvent createSensorEvent(InputDevice inputDevice, int sensorType, int accuracy, + long timestamp, float[] values) { + synchronized (mInputSensorLock) { + Sensor sensor = getInputDeviceSensorLocked(inputDevice.getId(), sensorType); + if (sensor == null) { + Slog.wtf(TAG, "Can't get sensor type " + sensorType + " for input device " + + inputDevice); + } + SensorEvent event = new SensorEvent(sensor, accuracy, timestamp, values); + if (event == null) { + Slog.wtf(TAG, "Failed to create SensorEvent."); + } + return event; + } + } + + /** + * Return the default sensor object for input device, for specific sensor type. + */ + private Sensor getSensorForInputDevice(int deviceId, int type) { + synchronized (mInputSensorLock) { + for (Map.Entry<Integer, List<Sensor>> entry : mSensors.entrySet()) { + for (Sensor sensor : entry.getValue()) { + if (sensor.getId() == deviceId && sensor.getType() == type) { + if (DEBUG) { + Slog.d(TAG, "Device " + deviceId + " sensor " + sensor.getStringType()); + } + return sensor; + } + } + } + } + return null; + } + + /** + * Return list of sensors that belong to an input device, specified by input device ID. + */ + private List<Sensor> getFullSensorListForDevice(int deviceId) { + List<Sensor> sensors = new ArrayList<Sensor>(); + synchronized (mInputSensorLock) { + for (Map.Entry<Integer, List<Sensor>> entry : mSensors.entrySet()) { + for (Sensor sensor : entry.getValue()) { + if (sensor.getId() == deviceId) { + if (DEBUG) { + Slog.d(TAG, "Device " + deviceId + " sensor " + sensor.getStringType()); + } + sensors.add(sensor); + } + } + } + } + return sensors; + } + + private boolean registerListenerInternal(SensorEventListener listener, Sensor sensor, + int delayUs, int maxBatchReportLatencyUs, Handler handler) { + if (DEBUG) { + Slog.d(TAG, "registerListenerImpl listener=" + listener + " sensor=" + sensor + + " delayUs=" + delayUs + + " maxBatchReportLatencyUs=" + maxBatchReportLatencyUs); + } + if (listener == null) { + Slog.e(TAG, "listener is null"); + return false; + } + + if (sensor == null) { + Slog.e(TAG, "sensor is null"); + return false; + } + + // Trigger Sensors should use the requestTriggerSensor call. + if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) { + Slog.e(TAG, "Trigger Sensors should use the requestTriggerSensor."); + return false; + } + if (maxBatchReportLatencyUs < 0 || delayUs < 0) { + Slog.e(TAG, "maxBatchReportLatencyUs and delayUs should be non-negative"); + return false; + } + + if (getSensorForInputDevice(sensor.getId(), sensor.getType()) != null) { + synchronized (mInputSensorLock) { + final int deviceId = sensor.getId(); + InputDevice inputDevice = InputDevice.getDevice(deviceId); + if (!inputDevice.hasSensor()) { + Slog.e(TAG, "The device doesn't have the sensor:" + sensor); + return false; + } + if (!mInputManager.enableSensor(deviceId, sensor.getType(), delayUs, + maxBatchReportLatencyUs)) { + Slog.e(TAG, "Can't enable the sensor:" + sensor); + return false; + } + } + } + + synchronized (mInputSensorLock) { + // Register the InputManagerService sensor listener if not yet. + if (mInputServiceSensorListener == null) { + mInputServiceSensorListener = new InputSensorEventListener(); + if (!mInputManager.registerSensorListener(mInputServiceSensorListener)) { + Slog.e(TAG, "Failed registering the sensor listener"); + return false; + } + } + + int idx = findSensorEventListenerLocked(listener); + if (idx < 0) { + InputSensorEventListenerDelegate d = + new InputSensorEventListenerDelegate(listener, sensor, delayUs, + maxBatchReportLatencyUs, + handler == null ? mSensorHandler : handler); + mInputSensorEventListeners.add(d); + } else { + // The listener is already registered, see if it wants to listen to more sensors. + mInputSensorEventListeners.get(idx).addSensor(sensor); + } + } + + return true; + } + + private void unregisterListenerInternal(SensorEventListener listener, Sensor sensor) { + if (DEBUG) { + Slog.d(TAG, "unregisterListenerImpl listener=" + listener + " sensor=" + sensor); + } + if (listener == null) { // it's OK for the sensor to be null + throw new IllegalArgumentException("listener must not be null"); + } + synchronized (mInputSensorLock) { + int idx = findSensorEventListenerLocked(listener); + // Track the sensor types and the device Id the listener has registered. + final List<Sensor> sensorsRegistered; + if (idx >= 0) { + InputSensorEventListenerDelegate delegate = + mInputSensorEventListeners.get(idx); + sensorsRegistered = new ArrayList<Sensor>(delegate.getSensors()); + // Get the sensor types the listener is listening to + delegate.removeSensor(sensor); + if (delegate.isEmpty()) { + // If no sensors to listen, remove the listener delegate + mInputSensorEventListeners.remove(idx); + } + } else { + Slog.e(TAG, "Listener is not registered"); + return; + } + // If no delegation remains, unregister the listener to input service + if (mInputServiceSensorListener != null && mInputSensorEventListeners.size() == 0) { + mInputManager.unregisterSensorListener(mInputServiceSensorListener); + mInputServiceSensorListener = null; + } + // For each sensor type check if it is still in use by other listeners. + for (Sensor s : sensorsRegistered) { + final int deviceId = s.getId(); + final int sensorType = s.getType(); + // See if we can disable the sensor + boolean enableSensor = false; + for (int i = 0; i < mInputSensorEventListeners.size(); i++) { + InputSensorEventListenerDelegate delegate = + mInputSensorEventListeners.get(i); + if (delegate.hasSensorRegistered(deviceId, sensorType)) { + enableSensor = true; + Slog.w(TAG, "device " + deviceId + " still uses sensor " + sensorType); + break; + } + } + // Sensor is not listened, disable it. + if (!enableSensor) { + if (DEBUG) { + Slog.d(TAG, "device " + deviceId + " sensor " + sensorType + " disabled"); + } + mInputManager.disableSensor(deviceId, sensorType); + } + } + } + } + + private boolean flush(SensorEventListener listener) { + synchronized (mInputSensorLock) { + int idx = findSensorEventListenerLocked(listener); + if (idx < 0) { + return false; + } + for (Sensor sensor : mInputSensorEventListeners.get(idx).getSensors()) { + final int deviceId = sensor.getId(); + if (!mInputManager.flushSensor(deviceId, sensor.getType())) { + return false; + } + } + return true; + } + } + + /** + * Sensor Manager class associated with specific input device + */ + public class InputSensorManager extends SensorManager { + // Input device ID that the sensors belong to + final int mId; + + InputSensorManager(int deviceId) { + mId = deviceId; + } + + @Override + public Sensor getDefaultSensor(int type) { + return getSensorForInputDevice(mId, type); + } + + @Override + protected List<Sensor> getFullSensorList() { + return getFullSensorListForDevice(mId); + } + + @Override + protected List<Sensor> getFullDynamicSensorList() { + return new ArrayList<>(); + } + + @Override + protected boolean registerListenerImpl(SensorEventListener listener, Sensor sensor, + int delayUs, Handler handler, int maxBatchReportLatencyUs, int reservedFlags) { + return registerListenerInternal(listener, sensor, delayUs, + maxBatchReportLatencyUs, handler); + } + + @Override + protected void unregisterListenerImpl(SensorEventListener listener, Sensor sensor) { + unregisterListenerInternal(listener, sensor); + } + + @Override + protected boolean flushImpl(SensorEventListener listener) { + return flush(listener); + } + + @Override + protected SensorDirectChannel createDirectChannelImpl(MemoryFile memoryFile, + HardwareBuffer hardwareBuffer) { + return null; + } + + @Override + protected void destroyDirectChannelImpl(SensorDirectChannel channel) { + + } + + @Override + protected int configureDirectChannelImpl(SensorDirectChannel channel, Sensor s, int rate) { + return 0; + } + + @Override + protected void registerDynamicSensorCallbackImpl(DynamicSensorCallback callback, + Handler handler) { + + } + + @Override + protected void unregisterDynamicSensorCallbackImpl( + DynamicSensorCallback callback) { + + } + + @Override + protected boolean requestTriggerSensorImpl(TriggerEventListener listener, Sensor sensor) { + return true; + } + + @Override + protected boolean cancelTriggerSensorImpl(TriggerEventListener listener, Sensor sensor, + boolean disable) { + return true; + } + + @Override + protected boolean initDataInjectionImpl(boolean enable) { + return false; + } + + @Override + protected boolean injectSensorDataImpl(Sensor sensor, float[] values, int accuracy, + long timestamp) { + return false; + } + + @Override + protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) { + return false; + } + } + +} diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 40c7e9a892ff..300d99bbd689 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -28,6 +28,7 @@ import android.annotation.TestApi; import android.compat.annotation.ChangeId; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.hardware.SensorManager; import android.os.BlockUntrustedTouchesMode; import android.os.Build; import android.os.CombinedVibrationEffect; @@ -100,6 +101,7 @@ public final class InputManager { private TabletModeChangedListener mTabletModeChangedListener; private List<OnTabletModeChangedListenerDelegate> mOnTabletModeChangedListeners; + private InputDeviceSensorManager mInputDeviceSensorManager; /** * Broadcast Action: Query available keyboard layouts. * <p> @@ -291,6 +293,18 @@ public final class InputManager { } /** + * Clear the instance of the input manager. + * + * @hide + */ + @VisibleForTesting + public static void clearInstance() { + synchronized (InputManager.class) { + sInstance = null; + } + } + + /** * Gets an instance of the input manager. * * @return The input manager instance. @@ -1152,6 +1166,86 @@ public final class InputManager { } /** + * Get sensors information as list. + * + * @hide + */ + public InputSensorInfo[] getSensorList(int deviceId) { + try { + return mIm.getSensorList(deviceId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Enable input device sensor + * + * @hide + */ + public boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs, + int maxBatchReportLatencyUs) { + try { + return mIm.enableSensor(deviceId, sensorType, samplingPeriodUs, + maxBatchReportLatencyUs); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Enable input device sensor + * + * @hide + */ + public void disableSensor(int deviceId, int sensorType) { + try { + mIm.disableSensor(deviceId, sensorType); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Flush input device sensor + * + * @hide + */ + public boolean flushSensor(int deviceId, int sensorType) { + try { + return mIm.flushSensor(deviceId, sensorType); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Register input device sensor listener + * + * @hide + */ + public boolean registerSensorListener(IInputSensorEventListener listener) { + try { + return mIm.registerSensorListener(listener); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Unregister input device sensor listener + * + * @hide + */ + public void unregisterSensorListener(IInputSensorEventListener listener) { + try { + mIm.unregisterSensorListener(listener); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** * Add a runtime association between the input port and the display port. This overrides any * static associations. * @param inputPort The port of the input device. @@ -1289,7 +1383,7 @@ public final class InputManager { } /** - * Gets a vibrator service associated with an input device, assuming it has one. + * Gets a vibrator service associated with an input device, always create a new instance. * @return The vibrator, never null. * @hide */ @@ -1365,6 +1459,19 @@ public final class InputManager { } /** + * Gets a sensor manager service associated with an input device, always create a new instance. + * @return The sensor manager, never null. + * @hide + */ + @NonNull + public SensorManager getInputDeviceSensorManager(int deviceId) { + if (mInputDeviceSensorManager == null) { + mInputDeviceSensorManager = new InputDeviceSensorManager(this); + } + return mInputDeviceSensorManager.getSensorManager(deviceId); + } + + /** * Listens for changes in input devices. */ public interface InputDeviceListener { diff --git a/core/java/android/hardware/input/InputSensorInfo.aidl b/core/java/android/hardware/input/InputSensorInfo.aidl new file mode 100644 index 000000000000..d9a604ace8b1 --- /dev/null +++ b/core/java/android/hardware/input/InputSensorInfo.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2020 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.hardware.input; + +parcelable InputSensorInfo; diff --git a/core/java/android/hardware/input/InputSensorInfo.java b/core/java/android/hardware/input/InputSensorInfo.java new file mode 100644 index 000000000000..99b18792aa33 --- /dev/null +++ b/core/java/android/hardware/input/InputSensorInfo.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2020 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.hardware.input; + +import android.annotation.NonNull; +import android.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; +/** + * This class represents a motion sensor for input devices. + * + * @hide + */ +@DataClass( + genToString = true, + genHiddenConstructor = true, + genHiddenConstDefs = true) +public class InputSensorInfo implements Parcelable { + + private @NonNull String mName; + private @NonNull String mVendor; + private int mVersion; + private int mHandle; + private int mType; + private float mMaxRange; + private float mResolution; + private float mPower; + private int mMinDelay; + private int mFifoReservedEventCount; + private int mFifoMaxEventCount; + private @NonNull String mStringType; + private @NonNull String mRequiredPermission; + private int mMaxDelay; + private int mFlags; + private int mId; + + + + // Code below generated by codegen v1.0.20. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/InputSensorInfo.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Creates a new InputSensorInfo. + * + * @hide + */ + @DataClass.Generated.Member + public InputSensorInfo( + @NonNull String name, + @NonNull String vendor, + int version, + int handle, + int type, + float maxRange, + float resolution, + float power, + int minDelay, + int fifoReservedEventCount, + int fifoMaxEventCount, + @NonNull String stringType, + @NonNull String requiredPermission, + int maxDelay, + int flags, + int id) { + this.mName = name; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mName); + this.mVendor = vendor; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mVendor); + this.mVersion = version; + this.mHandle = handle; + this.mType = type; + this.mMaxRange = maxRange; + this.mResolution = resolution; + this.mPower = power; + this.mMinDelay = minDelay; + this.mFifoReservedEventCount = fifoReservedEventCount; + this.mFifoMaxEventCount = fifoMaxEventCount; + this.mStringType = stringType; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mStringType); + this.mRequiredPermission = requiredPermission; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRequiredPermission); + this.mMaxDelay = maxDelay; + this.mFlags = flags; + this.mId = id; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull String getName() { + return mName; + } + + @DataClass.Generated.Member + public @NonNull String getVendor() { + return mVendor; + } + + @DataClass.Generated.Member + public int getVersion() { + return mVersion; + } + + @DataClass.Generated.Member + public int getHandle() { + return mHandle; + } + + @DataClass.Generated.Member + public int getType() { + return mType; + } + + @DataClass.Generated.Member + public float getMaxRange() { + return mMaxRange; + } + + @DataClass.Generated.Member + public float getResolution() { + return mResolution; + } + + @DataClass.Generated.Member + public float getPower() { + return mPower; + } + + @DataClass.Generated.Member + public int getMinDelay() { + return mMinDelay; + } + + @DataClass.Generated.Member + public int getFifoReservedEventCount() { + return mFifoReservedEventCount; + } + + @DataClass.Generated.Member + public int getFifoMaxEventCount() { + return mFifoMaxEventCount; + } + + @DataClass.Generated.Member + public @NonNull String getStringType() { + return mStringType; + } + + @DataClass.Generated.Member + public @NonNull String getRequiredPermission() { + return mRequiredPermission; + } + + @DataClass.Generated.Member + public int getMaxDelay() { + return mMaxDelay; + } + + @DataClass.Generated.Member + public int getFlags() { + return mFlags; + } + + @DataClass.Generated.Member + public int getId() { + return mId; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "InputSensorInfo { " + + "name = " + mName + ", " + + "vendor = " + mVendor + ", " + + "version = " + mVersion + ", " + + "handle = " + mHandle + ", " + + "type = " + mType + ", " + + "maxRange = " + mMaxRange + ", " + + "resolution = " + mResolution + ", " + + "power = " + mPower + ", " + + "minDelay = " + mMinDelay + ", " + + "fifoReservedEventCount = " + mFifoReservedEventCount + ", " + + "fifoMaxEventCount = " + mFifoMaxEventCount + ", " + + "stringType = " + mStringType + ", " + + "requiredPermission = " + mRequiredPermission + ", " + + "maxDelay = " + mMaxDelay + ", " + + "flags = " + mFlags + ", " + + "id = " + mId + + " }"; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeString(mName); + dest.writeString(mVendor); + dest.writeInt(mVersion); + dest.writeInt(mHandle); + dest.writeInt(mType); + dest.writeFloat(mMaxRange); + dest.writeFloat(mResolution); + dest.writeFloat(mPower); + dest.writeInt(mMinDelay); + dest.writeInt(mFifoReservedEventCount); + dest.writeInt(mFifoMaxEventCount); + dest.writeString(mStringType); + dest.writeString(mRequiredPermission); + dest.writeInt(mMaxDelay); + dest.writeInt(mFlags); + dest.writeInt(mId); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected InputSensorInfo(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + String name = in.readString(); + String vendor = in.readString(); + int version = in.readInt(); + int handle = in.readInt(); + int type = in.readInt(); + float maxRange = in.readFloat(); + float resolution = in.readFloat(); + float power = in.readFloat(); + int minDelay = in.readInt(); + int fifoReservedEventCount = in.readInt(); + int fifoMaxEventCount = in.readInt(); + String stringType = in.readString(); + String requiredPermission = in.readString(); + int maxDelay = in.readInt(); + int flags = in.readInt(); + int id = in.readInt(); + + this.mName = name; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mName); + this.mVendor = vendor; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mVendor); + this.mVersion = version; + this.mHandle = handle; + this.mType = type; + this.mMaxRange = maxRange; + this.mResolution = resolution; + this.mPower = power; + this.mMinDelay = minDelay; + this.mFifoReservedEventCount = fifoReservedEventCount; + this.mFifoMaxEventCount = fifoMaxEventCount; + this.mStringType = stringType; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mStringType); + this.mRequiredPermission = requiredPermission; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mRequiredPermission); + this.mMaxDelay = maxDelay; + this.mFlags = flags; + this.mId = id; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<InputSensorInfo> CREATOR + = new Parcelable.Creator<InputSensorInfo>() { + @Override + public InputSensorInfo[] newArray(int size) { + return new InputSensorInfo[size]; + } + + @Override + public InputSensorInfo createFromParcel(@NonNull Parcel in) { + return new InputSensorInfo(in); + } + }; + + @DataClass.Generated( + time = 1605294854951L, + codegenVersion = "1.0.20", + sourceFile = "frameworks/base/core/java/android/hardware/input/InputSensorInfo.java", + inputSignatures = "private @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.NonNull java.lang.String mVendor\nprivate int mVersion\nprivate int mHandle\nprivate int mType\nprivate float mMaxRange\nprivate float mResolution\nprivate float mPower\nprivate int mMinDelay\nprivate int mFifoReservedEventCount\nprivate int mFifoMaxEventCount\nprivate @android.annotation.NonNull java.lang.String mStringType\nprivate @android.annotation.NonNull java.lang.String mRequiredPermission\nprivate int mMaxDelay\nprivate int mFlags\nprivate int mId\nclass InputSensorInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index df96dc332ff0..f4b90e1f7b44 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -22,6 +22,7 @@ import android.annotation.RequiresPermission; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.hardware.SensorManager; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.os.Build; @@ -71,13 +72,18 @@ public final class InputDevice implements Parcelable { private final boolean mHasVibrator; private final boolean mHasMicrophone; private final boolean mHasButtonUnderPad; + private final boolean mHasSensor; private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>(); + @GuardedBy("mMotionRanges") private Vibrator mVibrator; // guarded by mMotionRanges during initialization @GuardedBy("mMotionRanges") private VibratorManager mVibratorManager; + @GuardedBy("mMotionRanges") + private SensorManager mSensorManager; + /** * A mask for input source classes. * @@ -442,7 +448,7 @@ public final class InputDevice implements Parcelable { public InputDevice(int id, int generation, int controllerNumber, String name, int vendorId, int productId, String descriptor, boolean isExternal, int sources, int keyboardType, KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone, - boolean hasButtonUnderPad) { + boolean hasButtonUnderPad, boolean hasSensor) { mId = id; mGeneration = generation; mControllerNumber = controllerNumber; @@ -457,10 +463,12 @@ public final class InputDevice implements Parcelable { mHasVibrator = hasVibrator; mHasMicrophone = hasMicrophone; mHasButtonUnderPad = hasButtonUnderPad; + mHasSensor = hasSensor; mIdentifier = new InputDeviceIdentifier(descriptor, vendorId, productId); } private InputDevice(Parcel in) { + mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in); mId = in.readInt(); mGeneration = in.readInt(); mControllerNumber = in.readInt(); @@ -471,10 +479,10 @@ public final class InputDevice implements Parcelable { mIsExternal = in.readInt() != 0; mSources = in.readInt(); mKeyboardType = in.readInt(); - mKeyCharacterMap = KeyCharacterMap.CREATOR.createFromParcel(in); mHasVibrator = in.readInt() != 0; mHasMicrophone = in.readInt() != 0; mHasButtonUnderPad = in.readInt() != 0; + mHasSensor = in.readInt() != 0; mIdentifier = new InputDeviceIdentifier(mDescriptor, mVendorId, mProductId); int numRanges = in.readInt(); @@ -822,6 +830,26 @@ public final class InputDevice implements Parcelable { } /** + * Gets the sensor manager service associated with the input device. + * Even if the device does not have a sensor, the result is never null. + * Use {@link SensorManager#getSensorList} to get a full list of all supported sensors. + * + * Note that the sensors associated with the device may be different from + * the system sensors, as typically they are builtin sensors physically attached to + * input devices. + * + * @return The sensor manager service associated with the device, never null. + */ + public @NonNull SensorManager getSensorManager() { + synchronized (mMotionRanges) { + if (mSensorManager == null) { + mSensorManager = InputManager.getInstance().getInputDeviceSensorManager(mId); + } + } + return mSensorManager; + } + + /** * Returns true if input device is enabled. * @return Whether the input device is enabled. */ @@ -869,6 +897,15 @@ public final class InputDevice implements Parcelable { } /** + * Reports whether the device has a sensor. + * @return Whether the device has a sensor. + * @hide + */ + public boolean hasSensor() { + return mHasSensor; + } + + /** * Sets the current pointer type. * @param pointerType the type of the pointer icon. * @hide @@ -999,6 +1036,7 @@ public final class InputDevice implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { + mKeyCharacterMap.writeToParcel(out, flags); out.writeInt(mId); out.writeInt(mGeneration); out.writeInt(mControllerNumber); @@ -1009,10 +1047,10 @@ public final class InputDevice implements Parcelable { out.writeInt(mIsExternal ? 1 : 0); out.writeInt(mSources); out.writeInt(mKeyboardType); - mKeyCharacterMap.writeToParcel(out, flags); out.writeInt(mHasVibrator ? 1 : 0); out.writeInt(mHasMicrophone ? 1 : 0); out.writeInt(mHasButtonUnderPad ? 1 : 0); + out.writeInt(mHasSensor ? 1 : 0); final int numRanges = mMotionRanges.size(); out.writeInt(numRanges); @@ -1057,6 +1095,8 @@ public final class InputDevice implements Parcelable { description.append(" Has Vibrator: ").append(mHasVibrator).append("\n"); + description.append(" Has Sensor: ").append(mHasSensor).append("\n"); + description.append(" Has mic: ").append(mHasMicrophone).append("\n"); description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" ("); diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp index 9f4e3e516ada..4eaa016df6f2 100644 --- a/core/jni/android_view_InputDevice.cpp +++ b/core/jni/android_view_InputDevice.cpp @@ -60,13 +60,17 @@ jobject android_view_InputDevice_create(JNIEnv* env, const InputDeviceInfo& devi // Not sure why, but JNI is complaining when I pass this through directly. jboolean hasMic = deviceInfo.hasMic() ? JNI_TRUE : JNI_FALSE; - ScopedLocalRef<jobject> inputDeviceObj(env, env->NewObject(gInputDeviceClassInfo.clazz, - gInputDeviceClassInfo.ctor, deviceInfo.getId(), deviceInfo.getGeneration(), - deviceInfo.getControllerNumber(), nameObj.get(), - static_cast<int32_t>(ident.vendor), static_cast<int32_t>(ident.product), - descriptorObj.get(), deviceInfo.isExternal(), deviceInfo.getSources(), - deviceInfo.getKeyboardType(), kcmObj.get(), deviceInfo.hasVibrator(), - hasMic, deviceInfo.hasButtonUnderPad())); + ScopedLocalRef<jobject> + inputDeviceObj(env, + env->NewObject(gInputDeviceClassInfo.clazz, gInputDeviceClassInfo.ctor, + deviceInfo.getId(), deviceInfo.getGeneration(), + deviceInfo.getControllerNumber(), nameObj.get(), + static_cast<int32_t>(ident.vendor), + static_cast<int32_t>(ident.product), descriptorObj.get(), + deviceInfo.isExternal(), deviceInfo.getSources(), + deviceInfo.getKeyboardType(), kcmObj.get(), + deviceInfo.hasVibrator(), hasMic, + deviceInfo.hasButtonUnderPad(), deviceInfo.hasSensor())); const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges(); for (const InputDeviceInfo::MotionRange& range: ranges) { @@ -87,7 +91,8 @@ int register_android_view_InputDevice(JNIEnv* env) gInputDeviceClassInfo.clazz = MakeGlobalRefOrDie(env, gInputDeviceClassInfo.clazz); gInputDeviceClassInfo.ctor = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "<init>", - "(IIILjava/lang/String;IILjava/lang/String;ZIILandroid/view/KeyCharacterMap;ZZZ)V"); + "(IIILjava/lang/String;IILjava/lang/" + "String;ZIILandroid/view/KeyCharacterMap;ZZZZ)V"); gInputDeviceClassInfo.addMotionRange = GetMethodIDOrDie(env, gInputDeviceClassInfo.clazz, "addMotionRange", "(IIFFFFF)V"); diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java new file mode 100644 index 000000000000..b319886ae466 --- /dev/null +++ b/core/tests/coretests/src/android/hardware/input/InputDeviceSensorManagerTest.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2020 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.hardware.input; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.fail; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.ContextWrapper; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; +import android.view.InputDevice; + +import androidx.test.InstrumentationRegistry; + +import com.android.internal.annotations.GuardedBy; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.MockitoRule; + +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * Tests for {@link InputDeviceSensorManager}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:InputDeviceSensorManagerTest + */ +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class InputDeviceSensorManagerTest { + private static final String TAG = "InputDeviceSensorManagerTest"; + + private static final int DEVICE_ID = 1000; + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + private TestLooper mTestLooper; + private ContextWrapper mContextSpy; + private InputManager mInputManager; + private InputDeviceSensorManager mSensorManager; + private IInputSensorEventListener mIInputSensorEventListener; + private final Object mLock = new Object(); + + @Mock private IInputManager mIInputManagerMock; + + @Before + public void setUp() throws Exception { + mTestLooper = new TestLooper(); + mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext())); + InputManager inputManager = InputManager.resetInstance(mIInputManagerMock); + + when(mContextSpy.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager); + + when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID}); + + when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn( + createInputDeviceWithSensor(DEVICE_ID)); + + when(mIInputManagerMock.getSensorList(eq(DEVICE_ID))).thenReturn(new InputSensorInfo[] { + createInputSensorInfo(DEVICE_ID, Sensor.TYPE_ACCELEROMETER), + createInputSensorInfo(DEVICE_ID, Sensor.TYPE_GYROSCOPE)}); + + when(mIInputManagerMock.enableSensor(eq(DEVICE_ID), anyInt(), anyInt(), anyInt())) + .thenReturn(true); + + when(mIInputManagerMock.registerSensorListener(any())).thenReturn(true); + + mInputManager = mContextSpy.getSystemService(InputManager.class); + } + + @After + public void tearDown() { + InputManager.clearInstance(); + } + + private class InputTestSensorEventListener implements SensorEventListener { + @GuardedBy("mLock") + private final BlockingQueue<SensorEvent> mEvents = new LinkedBlockingQueue<>(); + InputTestSensorEventListener() { + super(); + } + + public SensorEvent waitForSensorEvent() { + try { + return mEvents.poll(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + fail("unexpectedly interrupted while waiting for SensorEvent"); + return null; + } + } + + @Override + public void onSensorChanged(SensorEvent event) { + synchronized (mLock) { + try { + mEvents.put(event); + } catch (InterruptedException ex) { + fail("interrupted while adding a SensorEvent to the queue"); + } + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + } + } + + private InputDevice createInputDeviceWithSensor(int id) { + InputDevice d = new InputDevice(id, 0 /* generation */, 0 /* controllerNumber */, "name", + 0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */, + 0 /* sources */, 0 /* keyboardType */, null /* keyCharacterMap */, + false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */, + true /* hasSensor */); + assertTrue(d.hasSensor()); + return d; + } + + private InputSensorInfo createInputSensorInfo(int id, int type) { + InputSensorInfo info = new InputSensorInfo("name", "vendor", 0 /* version */, + 0 /* handle */, type, 100.0f /*maxRange */, 0.02f /* resolution */, + 0.8f /* power */, 1000 /* minDelay */, 0 /* fifoReservedEventCount */, + 0 /* fifoMaxEventCount */, "" /* stringType */, "" /* requiredPermission */, + 0 /* maxDelay */, 0 /* flags */, id); + return info; + } + + private InputDevice getSensorDevice(int[] deviceIds) { + for (int i = 0; i < deviceIds.length; i++) { + InputDevice device = mInputManager.getInputDevice(deviceIds[i]); + if (device.hasSensor()) { + return device; + } + } + return null; + } + + @Test + public void getInputDeviceSensors_withExpectedType() throws Exception { + InputDevice device = getSensorDevice(mInputManager.getInputDeviceIds()); + assertTrue(device != null); + + SensorManager sensorManager = device.getSensorManager(); + List<Sensor> accelList = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); + verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + assertEquals(1, accelList.size()); + assertEquals(DEVICE_ID, accelList.get(0).getId()); + assertEquals(Sensor.TYPE_ACCELEROMETER, accelList.get(0).getType()); + + List<Sensor> gyroList = sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE); + verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + assertEquals(1, gyroList.size()); + assertEquals(DEVICE_ID, gyroList.get(0).getId()); + assertEquals(Sensor.TYPE_GYROSCOPE, gyroList.get(0).getType()); + + } + + @Test + public void getInputDeviceSensors_withUnexpectedType() throws Exception { + InputDevice device = getSensorDevice(mInputManager.getInputDeviceIds()); + + assertTrue(device != null); + SensorManager sensorManager = device.getSensorManager(); + + List<Sensor> gameRotationList = sensorManager.getSensorList( + Sensor.TYPE_GAME_ROTATION_VECTOR); + verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + assertEquals(0, gameRotationList.size()); + + List<Sensor> gravityList = sensorManager.getSensorList(Sensor.TYPE_GRAVITY); + verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + assertEquals(0, gravityList.size()); + } + + @Test + public void testInputDeviceSensorListener() throws Exception { + InputDevice device = getSensorDevice(mInputManager.getInputDeviceIds()); + assertTrue(device != null); + + SensorManager sensorManager = device.getSensorManager(); + Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + assertEquals(Sensor.TYPE_ACCELEROMETER, sensor.getType()); + + doAnswer(invocation -> { + mIInputSensorEventListener = invocation.getArgument(0); + assertNotNull(mIInputSensorEventListener); + return true; + }).when(mIInputManagerMock).registerSensorListener(any()); + + InputTestSensorEventListener listener = new InputTestSensorEventListener(); + assertTrue(sensorManager.registerListener(listener, sensor, + SensorManager.SENSOR_DELAY_NORMAL)); + verify(mIInputManagerMock).registerSensorListener(any()); + verify(mIInputManagerMock).enableSensor(eq(DEVICE_ID), eq(sensor.getType()), + anyInt(), anyInt()); + + float[] values = new float[] {0.12f, 9.8f, 0.2f}; + mIInputSensorEventListener.onInputSensorChanged(DEVICE_ID, Sensor.TYPE_ACCELEROMETER, + SensorManager.SENSOR_STATUS_ACCURACY_HIGH, /* timestamp */ 0x1234abcd, values); + + SensorEvent event = listener.waitForSensorEvent(); + assertNotNull(event); + assertEquals(0x1234abcd, event.timestamp); + assertEquals(values.length, event.values.length); + for (int i = 0; i < values.length; i++) { + assertEquals(values[i], event.values[i], 0.001f); + } + + sensorManager.unregisterListener(listener); + verify(mIInputManagerMock).disableSensor(eq(DEVICE_ID), eq(sensor.getType())); + } + +} diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 2c925148ff68..387fd3a67a2b 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -41,11 +41,13 @@ import android.hardware.display.DisplayManager; import android.hardware.display.DisplayViewport; import android.hardware.input.IInputDevicesChangedListener; import android.hardware.input.IInputManager; +import android.hardware.input.IInputSensorEventListener; import android.hardware.input.ITabletModeChangedListener; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; import android.hardware.input.InputManagerInternal.LidSwitchCallback; +import android.hardware.input.InputSensorInfo; import android.hardware.input.KeyboardLayout; import android.hardware.input.TouchCalibration; import android.media.AudioManager; @@ -117,6 +119,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -181,12 +184,24 @@ public class InputManagerService extends IInputManager.Stub private final List<TabletModeChangedListenerRecord> mTempTabletModeChangedListenersToNotify = new ArrayList<>(); + private final Object mSensorEventLock = new Object(); + // List of currently registered sensor event listeners by process id + @GuardedBy("mSensorEventLock") + private final SparseArray<SensorEventListenerRecord> mSensorEventListeners = + new SparseArray<>(); + private final List<SensorEventListenerRecord> mSensorEventListenersToNotify = + new ArrayList<>(); + private final List<SensorEventListenerRecord> mSensorAccuracyListenersToNotify = + new ArrayList<>(); + // Persistent data store. Must be locked each time during use. private final PersistentDataStore mDataStore = new PersistentDataStore(); // List of currently registered input devices changed listeners by process id. private Object mInputDevicesLock = new Object(); + @GuardedBy("mInputDevicesLock") private boolean mInputDevicesChangedPending; // guarded by mInputDevicesLock + @GuardedBy("mInputDevicesLock") private InputDevice[] mInputDevices = new InputDevice[0]; private final SparseArray<InputDevicesChangedListenerRecord> mInputDevicesChangedListeners = new SparseArray<InputDevicesChangedListenerRecord>(); // guarded by mInputDevicesLock @@ -290,6 +305,11 @@ public class InputManagerService extends IInputManager.Stub private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId); private static native void nativeNotifyPortAssociationsChanged(long ptr); private static native void nativeSetMotionClassifierEnabled(long ptr, boolean enabled); + private static native InputSensorInfo[] nativeGetSensorList(long ptr, int deviceId); + private static native boolean nativeFlushSensor(long ptr, int deviceId, int sensorType); + private static native boolean nativeEnableSensor(long ptr, int deviceId, int sensorType, + int samplingPeriodUs, int maxBatchReportLatencyUs); + private static native void nativeDisableSensor(long ptr, int deviceId, int sensorType); // Maximum number of milliseconds to wait for input event injection. private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000; @@ -2044,6 +2064,97 @@ public class InputManagerService extends IInputManager.Stub nativeNotifyPortAssociationsChanged(mPtr); } + @Override // Binder call + public InputSensorInfo[] getSensorList(int deviceId) { + InputSensorInfo[] sensors = nativeGetSensorList(mPtr, deviceId); + return sensors; + } + + @Override // Binder call + public boolean registerSensorListener(IInputSensorEventListener listener) { + if (DEBUG) { + Slog.d(TAG, "registerSensorListener: listener=" + listener + " callingPid=" + + Binder.getCallingPid()); + } + if (listener == null) { + Slog.e(TAG, "listener must not be null"); + return false; + } + + synchronized (mInputDevicesLock) { + int callingPid = Binder.getCallingPid(); + if (mSensorEventListeners.get(callingPid) != null) { + Slog.e(TAG, "The calling process " + callingPid + " has already " + + "registered an InputSensorEventListener."); + return false; + } + + SensorEventListenerRecord record = + new SensorEventListenerRecord(callingPid, listener); + try { + IBinder binder = listener.asBinder(); + binder.linkToDeath(record, 0); + } catch (RemoteException ex) { + // give up + throw new RuntimeException(ex); + } + + mSensorEventListeners.put(callingPid, record); + } + return true; + } + + @Override // Binder call + public void unregisterSensorListener(IInputSensorEventListener listener) { + if (DEBUG) { + Slog.d(TAG, "unregisterSensorListener: listener=" + listener + " callingPid=" + + Binder.getCallingPid()); + } + + if (listener == null) { + throw new IllegalArgumentException("listener must not be null"); + } + + synchronized (mInputDevicesLock) { + int callingPid = Binder.getCallingPid(); + if (mSensorEventListeners.get(callingPid) != null) { + SensorEventListenerRecord record = mSensorEventListeners.get(callingPid); + if (record.getListener().asBinder() != listener.asBinder()) { + throw new IllegalArgumentException("listener is not registered"); + } + mSensorEventListeners.remove(callingPid); + } + } + } + + @Override // Binder call + public boolean flushSensor(int deviceId, int sensorType) { + synchronized (mInputDevicesLock) { + int callingPid = Binder.getCallingPid(); + SensorEventListenerRecord listener = mSensorEventListeners.get(callingPid); + if (listener != null) { + return nativeFlushSensor(mPtr, deviceId, sensorType); + } + return false; + } + } + + @Override // Binder call + public boolean enableSensor(int deviceId, int sensorType, int samplingPeriodUs, + int maxBatchReportLatencyUs) { + synchronized (mInputDevicesLock) { + return nativeEnableSensor(mPtr, deviceId, sensorType, samplingPeriodUs, + maxBatchReportLatencyUs); + } + } + + @Override // Binder call + public void disableSensor(int deviceId, int sensorType) { + synchronized (mInputDevicesLock) { + nativeDisableSensor(mPtr, deviceId, sensorType); + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; @@ -2241,6 +2352,46 @@ public class InputManagerService extends IInputManager.Stub } // Native callback. + private void notifySensorEvent(int deviceId, int sensorType, int accuracy, long timestamp, + float[] values) { + if (DEBUG) { + Slog.d(TAG, "notifySensorEvent: deviceId=" + deviceId + " sensorType=" + + sensorType + " values=" + Arrays.toString(values)); + } + mSensorEventListenersToNotify.clear(); + final int numListeners; + synchronized (mSensorEventLock) { + numListeners = mSensorEventListeners.size(); + for (int i = 0; i < numListeners; i++) { + mSensorEventListenersToNotify.add( + mSensorEventListeners.valueAt(i)); + } + } + for (int i = 0; i < numListeners; i++) { + mSensorEventListenersToNotify.get(i).notifySensorEvent(deviceId, sensorType, + accuracy, timestamp, values); + } + mSensorEventListenersToNotify.clear(); + } + + // Native callback. + private void notifySensorAccuracy(int deviceId, int sensorType, int accuracy) { + mSensorAccuracyListenersToNotify.clear(); + final int numListeners; + synchronized (mSensorEventLock) { + numListeners = mSensorEventListeners.size(); + for (int i = 0; i < numListeners; i++) { + mSensorAccuracyListenersToNotify.add(mSensorEventListeners.valueAt(i)); + } + } + for (int i = 0; i < numListeners; i++) { + mSensorAccuracyListenersToNotify.get(i).notifySensorAccuracy( + deviceId, sensorType, accuracy); + } + mSensorAccuracyListenersToNotify.clear(); + } + + // Native callback. final boolean filterInputEvent(InputEvent event, int policyFlags) { synchronized (mInputFilterLock) { if (mInputFilter != null) { @@ -2781,6 +2932,56 @@ public class InputManagerService extends IInputManager.Stub } } + private void onSensorEventListenerDied(int pid) { + synchronized (mSensorEventLock) { + mSensorEventListeners.remove(pid); + } + } + + private final class SensorEventListenerRecord implements DeathRecipient { + private final int mPid; + private final IInputSensorEventListener mListener; + + SensorEventListenerRecord(int pid, IInputSensorEventListener listener) { + mPid = pid; + mListener = listener; + } + + @Override + public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "Sensor event listener for pid " + mPid + " died."); + } + onSensorEventListenerDied(mPid); + } + + public IInputSensorEventListener getListener() { + return mListener; + } + + public void notifySensorEvent(int deviceId, int sensorType, int accuracy, long timestamp, + float[] values) { + try { + mListener.onInputSensorChanged(deviceId, sensorType, accuracy, timestamp, + values); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + + " that sensor event notified, assuming it died.", ex); + binderDied(); + } + } + + public void notifySensorAccuracy(int deviceId, int sensorType, int accuracy) { + try { + mListener.onInputSensorAccuracyChanged(deviceId, sensorType, accuracy); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to notify process " + mPid + + " that sensor accuracy notified, assuming it died.", ex); + binderDied(); + } + } + } + private final class VibratorToken implements DeathRecipient { public final int mDeviceId; public final IBinder mToken; diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 27634bfb51ec..793d9fe607d9 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -62,11 +62,11 @@ #include <nativehelper/ScopedPrimitiveArray.h> #include <nativehelper/ScopedUtfChars.h> -#include "com_android_server_power_PowerManagerService.h" +#include "android_hardware_display_DisplayViewport.h" #include "android_hardware_input_InputApplicationHandle.h" #include "android_hardware_input_InputWindowHandle.h" -#include "android_hardware_display_DisplayViewport.h" #include "android_util_Binder.h" +#include "com_android_server_power_PowerManagerService.h" #include <vector> @@ -101,6 +101,8 @@ static struct { jmethodID notifyConnectionUnresponsive; jmethodID notifyConnectionResponsive; jmethodID notifyFocusChanged; + jmethodID notifySensorEvent; + jmethodID notifySensorAccuracy; jmethodID notifyUntrustedTouch; jmethodID filterInputEvent; jmethodID interceptKeyBeforeQueueing; @@ -157,6 +159,29 @@ static struct { jmethodID size; } gSparseArrayClassInfo; +struct InputSensorInfoOffsets { + jclass clazz; + // fields + jfieldID name; + jfieldID vendor; + jfieldID version; + jfieldID handle; + jfieldID maxRange; + jfieldID resolution; + jfieldID power; + jfieldID minDelay; + jfieldID fifoReservedEventCount; + jfieldID fifoMaxEventCount; + jfieldID stringType; + jfieldID requiredPermission; + jfieldID maxDelay; + jfieldID flags; + jfieldID type; + jfieldID id; + // methods + jmethodID init; +} gInputSensorInfo; + // --- Global functions --- template<typename T> @@ -267,6 +292,11 @@ public: void notifyConnectionResponsive(const sp<IBinder>& token) override; void notifyInputChannelBroken(const sp<IBinder>& token) override; void notifyFocusChanged(const sp<IBinder>& oldToken, const sp<IBinder>& newToken) override; + void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType, + InputDeviceSensorAccuracy accuracy, nsecs_t timestamp, + const std::vector<float>& values) override; + void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType, + InputDeviceSensorAccuracy accuracy) override; void notifyUntrustedTouch(const std::string& obscuringPackage) override; bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override; void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override; @@ -820,6 +850,35 @@ void NativeInputManager::notifyFocusChanged(const sp<IBinder>& oldToken, checkAndClearExceptionFromCallback(env, "notifyFocusChanged"); } +void NativeInputManager::notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType, + InputDeviceSensorAccuracy accuracy, nsecs_t timestamp, + const std::vector<float>& values) { +#if DEBUG_INPUT_DISPATCHER_POLICY + ALOGD("notifySensorEvent"); +#endif + ATRACE_CALL(); + JNIEnv* env = jniEnv(); + ScopedLocalFrame localFrame(env); + jfloatArray arr = env->NewFloatArray(values.size()); + env->SetFloatArrayRegion(arr, 0, values.size(), values.data()); + env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifySensorEvent, deviceId, + static_cast<jint>(sensorType), accuracy, timestamp, arr); + checkAndClearExceptionFromCallback(env, "notifySensorEvent"); +} + +void NativeInputManager::notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType, + InputDeviceSensorAccuracy accuracy) { +#if DEBUG_INPUT_DISPATCHER_POLICY + ALOGD("notifySensorAccuracy"); +#endif + ATRACE_CALL(); + JNIEnv* env = jniEnv(); + ScopedLocalFrame localFrame(env); + env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifySensorAccuracy, deviceId, + static_cast<jint>(sensorType), accuracy); + checkAndClearExceptionFromCallback(env, "notifySensorAccuracy"); +} + void NativeInputManager::getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) { ATRACE_CALL(); JNIEnv* env = jniEnv(); @@ -1911,6 +1970,111 @@ static void nativeSetMotionClassifierEnabled(JNIEnv* /* env */, jclass /* clazz im->setMotionClassifierEnabled(enabled); } +static jobject createInputSensorInfo(JNIEnv* env, jstring name, jstring vendor, jint version, + jint handle, jint type, jfloat maxRange, jfloat resolution, + jfloat power, jfloat minDelay, jint fifoReservedEventCount, + jint fifoMaxEventCount, jstring stringType, + jstring requiredPermission, jint maxDelay, jint flags, + jint id) { + // SensorInfo sensorInfo = new Sensor(); + jobject sensorInfo = env->NewObject(gInputSensorInfo.clazz, gInputSensorInfo.init, ""); + + if (sensorInfo != NULL) { + env->SetObjectField(sensorInfo, gInputSensorInfo.name, name); + env->SetObjectField(sensorInfo, gInputSensorInfo.vendor, vendor); + env->SetIntField(sensorInfo, gInputSensorInfo.version, version); + env->SetIntField(sensorInfo, gInputSensorInfo.handle, handle); + env->SetFloatField(sensorInfo, gInputSensorInfo.maxRange, maxRange); + env->SetFloatField(sensorInfo, gInputSensorInfo.resolution, resolution); + env->SetFloatField(sensorInfo, gInputSensorInfo.power, power); + env->SetIntField(sensorInfo, gInputSensorInfo.minDelay, minDelay); + env->SetIntField(sensorInfo, gInputSensorInfo.fifoReservedEventCount, + fifoReservedEventCount); + env->SetIntField(sensorInfo, gInputSensorInfo.fifoMaxEventCount, fifoMaxEventCount); + env->SetObjectField(sensorInfo, gInputSensorInfo.requiredPermission, requiredPermission); + env->SetIntField(sensorInfo, gInputSensorInfo.maxDelay, maxDelay); + env->SetIntField(sensorInfo, gInputSensorInfo.flags, flags); + env->SetObjectField(sensorInfo, gInputSensorInfo.stringType, stringType); + env->SetIntField(sensorInfo, gInputSensorInfo.type, type); + env->SetIntField(sensorInfo, gInputSensorInfo.id, id); + } + return sensorInfo; +} + +static jobjectArray nativeGetSensorList(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + std::vector<InputDeviceInfo> devices = im->getInputManager()->getReader()->getInputDevices(); + // Find the input device by deviceId + auto it = std::find_if(devices.begin(), devices.end(), + [deviceId](InputDeviceInfo& info) { return info.getId() == deviceId; }); + + if (it == devices.end()) { + // Return an array of size 0 + return env->NewObjectArray(0, gInputSensorInfo.clazz, nullptr); + } + + std::vector<InputDeviceSensorType> types = it->getSensorTypes(); + jobjectArray arr = env->NewObjectArray(types.size(), gInputSensorInfo.clazz, nullptr); + for (int i = 0; i < types.size(); i++) { + const InputDeviceSensorInfo* sensorInfo = it->getSensorInfo(types[i]); + if (sensorInfo == nullptr) { + ALOGW("Failed to get input device %d sensor info for type %s", deviceId, + NamedEnum::string(types[i]).c_str()); + continue; + } + + jobject info = + createInputSensorInfo(env, env->NewStringUTF(sensorInfo->name.c_str()), + env->NewStringUTF(sensorInfo->vendor.c_str()), + (jint)sensorInfo->version, 0 /* handle */, + (jint)sensorInfo->type, (jfloat)sensorInfo->maxRange, + (jfloat)sensorInfo->resolution, (jfloat)sensorInfo->power, + (jfloat)sensorInfo->minDelay, + (jint)sensorInfo->fifoReservedEventCount, + (jint)sensorInfo->fifoMaxEventCount, + env->NewStringUTF(sensorInfo->stringType.c_str()), + env->NewStringUTF("") /* requiredPermission */, + (jint)sensorInfo->maxDelay, (jint)sensorInfo->flags, + (jint)sensorInfo->id); + env->SetObjectArrayElement(arr, i, info); + env->DeleteLocalRef(info); + } + return arr; +} + +static jboolean nativeEnableSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId, + jint sensorType, jint samplingPeriodUs, + jint maxBatchReportLatencyUs) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + + return im->getInputManager() + ->getReader() + ->enableSensor(deviceId, static_cast<InputDeviceSensorType>(sensorType), + std::chrono::microseconds(samplingPeriodUs), + std::chrono::microseconds(maxBatchReportLatencyUs)); + return true; +} + +static void nativeDisableSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId, + jint sensorType) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + + im->getInputManager()->getReader()->disableSensor(deviceId, + static_cast<InputDeviceSensorType>( + sensorType)); +} + +static jboolean nativeFlushSensor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId, + jint sensorType) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + + im->getInputManager()->getReader()->flushSensor(deviceId, + static_cast<InputDeviceSensorType>(sensorType)); + return im->getInputManager()->getDispatcher()->flushSensor(deviceId, + static_cast<InputDeviceSensorType>( + sensorType)); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -1976,6 +2140,11 @@ static const JNINativeMethod gInputManagerMethods[] = { {"nativeCanDispatchToDisplay", "(JII)Z", (void*)nativeCanDispatchToDisplay}, {"nativeNotifyPortAssociationsChanged", "(J)V", (void*)nativeNotifyPortAssociationsChanged}, {"nativeSetMotionClassifierEnabled", "(JZ)V", (void*)nativeSetMotionClassifierEnabled}, + {"nativeGetSensorList", "(JI)[Landroid/hardware/input/InputSensorInfo;", + (void*)nativeGetSensorList}, + {"nativeEnableSensor", "(JIIII)Z", (void*)nativeEnableSensor}, + {"nativeDisableSensor", "(JII)V", (void*)nativeDisableSensor}, + {"nativeFlushSensor", "(JII)Z", (void*)nativeFlushSensor}, }; #define FIND_CLASS(var, className) \ @@ -2021,6 +2190,10 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.notifyFocusChanged, clazz, "notifyFocusChanged", "(Landroid/os/IBinder;Landroid/os/IBinder;)V"); + GET_METHOD_ID(gServiceClassInfo.notifySensorEvent, clazz, "notifySensorEvent", "(IIIJ[F)V"); + + GET_METHOD_ID(gServiceClassInfo.notifySensorAccuracy, clazz, "notifySensorAccuracy", "(III)V"); + GET_METHOD_ID(gServiceClassInfo.notifyUntrustedTouch, clazz, "notifyUntrustedTouch", "(Ljava/lang/String;)V"); @@ -2145,6 +2318,33 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gSparseArrayClassInfo.valueAt, gSparseArrayClassInfo.clazz, "valueAt", "(I)Ljava/lang/Object;"); GET_METHOD_ID(gSparseArrayClassInfo.size, gSparseArrayClassInfo.clazz, "size", "()I"); + // InputSensorInfo + // android.hardware.input.InputDeviceSensorInfo + FIND_CLASS(clazz, "android/hardware/input/InputSensorInfo"); + gInputSensorInfo.clazz = reinterpret_cast<jclass>(env->NewGlobalRef(clazz)); + + GET_FIELD_ID(gInputSensorInfo.name, gInputSensorInfo.clazz, "mName", "Ljava/lang/String;"); + GET_FIELD_ID(gInputSensorInfo.vendor, gInputSensorInfo.clazz, "mVendor", "Ljava/lang/String;"); + GET_FIELD_ID(gInputSensorInfo.version, gInputSensorInfo.clazz, "mVersion", "I"); + GET_FIELD_ID(gInputSensorInfo.handle, gInputSensorInfo.clazz, "mHandle", "I"); + GET_FIELD_ID(gInputSensorInfo.maxRange, gInputSensorInfo.clazz, "mMaxRange", "F"); + GET_FIELD_ID(gInputSensorInfo.resolution, gInputSensorInfo.clazz, "mResolution", "F"); + GET_FIELD_ID(gInputSensorInfo.power, gInputSensorInfo.clazz, "mPower", "F"); + GET_FIELD_ID(gInputSensorInfo.minDelay, gInputSensorInfo.clazz, "mMinDelay", "I"); + GET_FIELD_ID(gInputSensorInfo.fifoReservedEventCount, gInputSensorInfo.clazz, + "mFifoReservedEventCount", "I"); + GET_FIELD_ID(gInputSensorInfo.fifoMaxEventCount, gInputSensorInfo.clazz, "mFifoMaxEventCount", + "I"); + GET_FIELD_ID(gInputSensorInfo.stringType, gInputSensorInfo.clazz, "mStringType", + "Ljava/lang/String;"); + GET_FIELD_ID(gInputSensorInfo.requiredPermission, gInputSensorInfo.clazz, "mRequiredPermission", + "Ljava/lang/String;"); + GET_FIELD_ID(gInputSensorInfo.maxDelay, gInputSensorInfo.clazz, "mMaxDelay", "I"); + GET_FIELD_ID(gInputSensorInfo.flags, gInputSensorInfo.clazz, "mFlags", "I"); + GET_FIELD_ID(gInputSensorInfo.type, gInputSensorInfo.clazz, "mType", "I"); + GET_FIELD_ID(gInputSensorInfo.id, gInputSensorInfo.clazz, "mId", "I"); + + GET_METHOD_ID(gInputSensorInfo.init, gInputSensorInfo.clazz, "<init>", "()V"); return 0; } diff --git a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java index 68adacd8478f..32ca7b58c48c 100644 --- a/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/VibratorServiceTest.java @@ -172,6 +172,7 @@ public class VibratorServiceTest { @After public void tearDown() throws Exception { + InputManager.clearInstance(); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.removeServiceForTest(PowerManagerInternal.class); } @@ -838,7 +839,7 @@ public class VibratorServiceTest { private InputDevice createInputDeviceWithVibrator(int id) { return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0, - null, /* hasVibrator= */ true, false, false); + null, /* hasVibrator= */ true, false, false, false /* hasSensor */); } private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { diff --git a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java index fa8e36741bcc..ac93ff691925 100644 --- a/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java +++ b/services/tests/servicestests/src/com/android/server/vibrator/InputDeviceDelegateTest.java @@ -44,6 +44,7 @@ import android.view.InputDevice; import androidx.test.InstrumentationRegistry; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -89,6 +90,11 @@ public class InputDeviceDelegateTest { mContextSpy, new Handler(mTestLooper.getLooper())); } + @After + public void tearDown() throws Exception { + InputManager.clearInstance(); + } + @Test public void onInputDeviceAdded_withSettingsDisabled_ignoresNewDevice() throws Exception { when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]); @@ -286,6 +292,6 @@ public class InputDeviceDelegateTest { private InputDevice createInputDevice(int id, boolean hasVibrator) { return new InputDevice(id, 0, 0, "name", 0, 0, "description", false, 0, 0, - null, hasVibrator, false, false); + null, hasVibrator, false, false, false /* hasSensor */); } } |