diff options
27 files changed, 2060 insertions, 16 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index c2f88d7161c7..495e09767950 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -46680,6 +46680,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 dd71cce39134..9d20f6d72d03 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/data/keyboards/Generic.kl b/data/keyboards/Generic.kl index 6ac73b1972d4..bd0e56a39e03 100644 --- a/data/keyboards/Generic.kl +++ b/data/keyboards/Generic.kl @@ -445,3 +445,11 @@ led 0x07 MUTE led 0x08 MISC led 0x09 MAIL led 0x0a CHARGING + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/data/keyboards/Vendor_054c_Product_05c4.idc b/data/keyboards/Vendor_054c_Product_05c4.idc new file mode 100644 index 000000000000..2cb3f7b90fed --- /dev/null +++ b/data/keyboards/Vendor_054c_Product_05c4.idc @@ -0,0 +1,35 @@ +# 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. + +# +# Sony DS4 motion sensor configuration file. +# + +# reporting mode 0 - continuous +sensor.accelerometer.reportingMode = 0 +# The delay between sensor events corresponding to the lowest frequency in microsecond +sensor.accelerometer.maxDelay = 100000 +# The minimum delay allowed between two events in microsecond +sensor.accelerometer.minDelay = 5000 +# The power in mA used by this sensor while in use +sensor.accelerometer.power = 1.5 + +# reporting mode 0 - continuous +sensor.gyroscope.reportingMode = 0 +# The delay between sensor events corresponding to the lowest frequency in microsecond +sensor.gyroscope.maxDelay = 100000 +# The minimum delay allowed between two events in microsecond +sensor.gyroscope.minDelay = 5000 +# The power in mA used by this sensor while in use +sensor.gyroscope.power = 0.8 diff --git a/data/keyboards/Vendor_054c_Product_05c4.kl b/data/keyboards/Vendor_054c_Product_05c4.kl index cd7ab1faec2e..c8b4fc363f9f 100644 --- a/data/keyboards/Vendor_054c_Product_05c4.kl +++ b/data/keyboards/Vendor_054c_Product_05c4.kl @@ -68,3 +68,11 @@ key 0x13c BUTTON_MODE # and this button will be equivalent to left mouse button # Therefore, map it to KEYCODE_BUTTON_1 here to allow apps to still handle this on earlier versions key 0x13d BUTTON_1 + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/data/keyboards/Vendor_054c_Product_05c4_Version_8000.kl b/data/keyboards/Vendor_054c_Product_05c4_Version_8000.kl index 19fcb86eb403..a877c4cc755f 100644 --- a/data/keyboards/Vendor_054c_Product_05c4_Version_8000.kl +++ b/data/keyboards/Vendor_054c_Product_05c4_Version_8000.kl @@ -66,3 +66,11 @@ key 0x13c BUTTON_MODE # In kernel versions >= 4.10, the touchpad is a separate input device, # so the touchpad button click will not be covered by this layout. + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/data/keyboards/Vendor_054c_Product_05c4_Version_8100.kl b/data/keyboards/Vendor_054c_Product_05c4_Version_8100.kl index 19fcb86eb403..a877c4cc755f 100644 --- a/data/keyboards/Vendor_054c_Product_05c4_Version_8100.kl +++ b/data/keyboards/Vendor_054c_Product_05c4_Version_8100.kl @@ -66,3 +66,11 @@ key 0x13c BUTTON_MODE # In kernel versions >= 4.10, the touchpad is a separate input device, # so the touchpad button click will not be covered by this layout. + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/data/keyboards/Vendor_054c_Product_05c4_Version_8111.kl b/data/keyboards/Vendor_054c_Product_05c4_Version_8111.kl index d38bdec5dada..1473c4e535f1 100644 --- a/data/keyboards/Vendor_054c_Product_05c4_Version_8111.kl +++ b/data/keyboards/Vendor_054c_Product_05c4_Version_8111.kl @@ -66,3 +66,11 @@ key 0x13c BUTTON_MODE # In kernel versions >= 4.10, the touchpad is a separate input device, # so the touchpad button click will not be covered by this layout. + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/data/keyboards/Vendor_054c_Product_09cc.idc b/data/keyboards/Vendor_054c_Product_09cc.idc new file mode 100644 index 000000000000..2cb3f7b90fed --- /dev/null +++ b/data/keyboards/Vendor_054c_Product_09cc.idc @@ -0,0 +1,35 @@ +# 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. + +# +# Sony DS4 motion sensor configuration file. +# + +# reporting mode 0 - continuous +sensor.accelerometer.reportingMode = 0 +# The delay between sensor events corresponding to the lowest frequency in microsecond +sensor.accelerometer.maxDelay = 100000 +# The minimum delay allowed between two events in microsecond +sensor.accelerometer.minDelay = 5000 +# The power in mA used by this sensor while in use +sensor.accelerometer.power = 1.5 + +# reporting mode 0 - continuous +sensor.gyroscope.reportingMode = 0 +# The delay between sensor events corresponding to the lowest frequency in microsecond +sensor.gyroscope.maxDelay = 100000 +# The minimum delay allowed between two events in microsecond +sensor.gyroscope.minDelay = 5000 +# The power in mA used by this sensor while in use +sensor.gyroscope.power = 0.8 diff --git a/data/keyboards/Vendor_054c_Product_09cc.kl b/data/keyboards/Vendor_054c_Product_09cc.kl index cd7ab1faec2e..c8b4fc363f9f 100644 --- a/data/keyboards/Vendor_054c_Product_09cc.kl +++ b/data/keyboards/Vendor_054c_Product_09cc.kl @@ -68,3 +68,11 @@ key 0x13c BUTTON_MODE # and this button will be equivalent to left mouse button # Therefore, map it to KEYCODE_BUTTON_1 here to allow apps to still handle this on earlier versions key 0x13d BUTTON_1 + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/data/keyboards/Vendor_054c_Product_09cc_Version_8000.kl b/data/keyboards/Vendor_054c_Product_09cc_Version_8000.kl index 19fcb86eb403..a877c4cc755f 100644 --- a/data/keyboards/Vendor_054c_Product_09cc_Version_8000.kl +++ b/data/keyboards/Vendor_054c_Product_09cc_Version_8000.kl @@ -66,3 +66,11 @@ key 0x13c BUTTON_MODE # In kernel versions >= 4.10, the touchpad is a separate input device, # so the touchpad button click will not be covered by this layout. + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/data/keyboards/Vendor_054c_Product_09cc_Version_8100.kl b/data/keyboards/Vendor_054c_Product_09cc_Version_8100.kl index 19fcb86eb403..a877c4cc755f 100644 --- a/data/keyboards/Vendor_054c_Product_09cc_Version_8100.kl +++ b/data/keyboards/Vendor_054c_Product_09cc_Version_8100.kl @@ -66,3 +66,11 @@ key 0x13c BUTTON_MODE # In kernel versions >= 4.10, the touchpad is a separate input device, # so the touchpad button click will not be covered by this layout. + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/data/keyboards/Vendor_054c_Product_09cc_Version_8111.kl b/data/keyboards/Vendor_054c_Product_09cc_Version_8111.kl index d38bdec5dada..1473c4e535f1 100644 --- a/data/keyboards/Vendor_054c_Product_09cc_Version_8111.kl +++ b/data/keyboards/Vendor_054c_Product_09cc_Version_8111.kl @@ -66,3 +66,11 @@ key 0x13c BUTTON_MODE # In kernel versions >= 4.10, the touchpad is a separate input device, # so the touchpad button click will not be covered by this layout. + +# SENSORs +sensor 0x00 ACCELEROMETER X +sensor 0x01 ACCELEROMETER Y +sensor 0x02 ACCELEROMETER Z +sensor 0x03 GYROSCOPE X +sensor 0x04 GYROSCOPE Y +sensor 0x05 GYROSCOPE Z diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 122e282530a6..8b09c99d5200 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; @@ -184,12 +187,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 @@ -293,6 +308,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; @@ -2047,6 +2067,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; @@ -2245,6 +2356,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) { @@ -2776,6 +2927,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 */); } } |