Merge changes from topic "SensorManager"

* changes:
  Add feature for inputdevice sensor manager.
  Add motion sensor configuration for Sony DS4.
This commit is contained in:
TreeHugger Robot
2020-12-17 20:26:17 +00:00
committed by Android (Google) Code Review
27 changed files with 2060 additions and 16 deletions

View File

@@ -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();

View File

@@ -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;
/**
@@ -922,6 +923,30 @@ public final class Sensor {
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.
*/

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}
}
}

View File

@@ -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>
@@ -290,6 +292,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.
*
@@ -1151,6 +1165,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.
@@ -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
*/
@@ -1364,6 +1458,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.
*/

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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();
@@ -821,6 +829,26 @@ public final class InputDevice implements Parcelable {
return mVibratorManager;
}
/**
* 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.
@@ -868,6 +896,15 @@ public final class InputDevice implements Parcelable {
return mHasButtonUnderPad;
}
/**
* 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.
@@ -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(" (");

View File

@@ -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");

View File

@@ -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()));
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;
@@ -2244,6 +2355,46 @@ public class InputManagerService extends IInputManager.Stub
mWindowManagerCallbacks.notifyWindowResponsive(token);
}
// 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) {
@@ -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;

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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 */);
}
}