summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/companion/java/com/android/server/companion/virtual/SensorController.java235
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java105
-rw-r--r--services/core/java/com/android/server/sensors/SensorManagerInternal.java50
-rw-r--r--services/core/java/com/android/server/sensors/SensorService.java42
-rw-r--r--services/core/jni/com_android_server_sensor_SensorService.cpp187
-rw-r--r--services/proguard.flags1
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java140
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java49
8 files changed, 756 insertions, 53 deletions
diff --git a/services/companion/java/com/android/server/companion/virtual/SensorController.java b/services/companion/java/com/android/server/companion/virtual/SensorController.java
new file mode 100644
index 000000000000..ec7e993ec30e
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/virtual/SensorController.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2022 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 com.android.server.companion.virtual;
+
+import android.annotation.NonNull;
+import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+
+/** Controls virtual sensors, including their lifecycle and sensor event dispatch. */
+public class SensorController {
+
+ private static final String TAG = "SensorController";
+
+ private final Object mLock;
+ private final int mVirtualDeviceId;
+ @GuardedBy("mLock")
+ private final Map<IBinder, SensorDescriptor> mSensorDescriptors = new ArrayMap<>();
+
+ private final SensorManagerInternal mSensorManagerInternal;
+
+ public SensorController(@NonNull Object lock, int virtualDeviceId) {
+ mLock = lock;
+ mVirtualDeviceId = virtualDeviceId;
+ mSensorManagerInternal = LocalServices.getService(SensorManagerInternal.class);
+ }
+
+ void close() {
+ synchronized (mLock) {
+ final Iterator<Map.Entry<IBinder, SensorDescriptor>> iterator =
+ mSensorDescriptors.entrySet().iterator();
+ if (iterator.hasNext()) {
+ final Map.Entry<IBinder, SensorDescriptor> entry = iterator.next();
+ final IBinder token = entry.getKey();
+ final SensorDescriptor sensorDescriptor = entry.getValue();
+ iterator.remove();
+ closeSensorDescriptorLocked(token, sensorDescriptor);
+ }
+ }
+ }
+
+ void createSensor(@NonNull IBinder deviceToken, @NonNull VirtualSensorConfig config) {
+ Objects.requireNonNull(deviceToken);
+ Objects.requireNonNull(config);
+ try {
+ createSensorInternal(deviceToken, config);
+ } catch (SensorCreationException e) {
+ throw new RuntimeException(
+ "Failed to create virtual sensor '" + config.getName() + "'.", e);
+ }
+ }
+
+ private void createSensorInternal(IBinder deviceToken, VirtualSensorConfig config)
+ throws SensorCreationException {
+ final SensorManagerInternal.RuntimeSensorStateChangeCallback runtimeSensorCallback =
+ (enabled, samplingPeriodMicros, batchReportLatencyMicros) -> {
+ IVirtualSensorStateChangeCallback callback = config.getStateChangeCallback();
+ if (callback != null) {
+ try {
+ callback.onStateChanged(
+ enabled, samplingPeriodMicros, batchReportLatencyMicros);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to call sensor callback.", e);
+ }
+ }
+ };
+
+ final int handle = mSensorManagerInternal.createRuntimeSensor(mVirtualDeviceId,
+ config.getType(), config.getName(),
+ config.getVendor() == null ? "" : config.getVendor(),
+ runtimeSensorCallback);
+ if (handle <= 0) {
+ throw new SensorCreationException("Received an invalid virtual sensor handle.");
+ }
+
+ // The handle is valid from here, so ensure that all failures clean it up.
+ final BinderDeathRecipient binderDeathRecipient;
+ try {
+ binderDeathRecipient = new BinderDeathRecipient(deviceToken);
+ deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
+ } catch (RemoteException e) {
+ mSensorManagerInternal.removeRuntimeSensor(handle);
+ throw new SensorCreationException("Client died before sensor could be created.", e);
+ }
+
+ synchronized (mLock) {
+ SensorDescriptor sensorDescriptor = new SensorDescriptor(
+ handle, config.getType(), config.getName(), binderDeathRecipient);
+ mSensorDescriptors.put(deviceToken, sensorDescriptor);
+ }
+ }
+
+ boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+ Objects.requireNonNull(token);
+ Objects.requireNonNull(event);
+ synchronized (mLock) {
+ final SensorDescriptor sensorDescriptor = mSensorDescriptors.get(token);
+ if (sensorDescriptor == null) {
+ throw new IllegalArgumentException("Could not send sensor event for given token");
+ }
+ return mSensorManagerInternal.sendSensorEvent(
+ sensorDescriptor.getHandle(), sensorDescriptor.getType(),
+ event.getTimestampNanos(), event.getValues());
+ }
+ }
+
+ void unregisterSensor(@NonNull IBinder token) {
+ Objects.requireNonNull(token);
+ synchronized (mLock) {
+ final SensorDescriptor sensorDescriptor = mSensorDescriptors.remove(token);
+ if (sensorDescriptor == null) {
+ throw new IllegalArgumentException("Could not unregister sensor for given token");
+ }
+ closeSensorDescriptorLocked(token, sensorDescriptor);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void closeSensorDescriptorLocked(IBinder token, SensorDescriptor sensorDescriptor) {
+ token.unlinkToDeath(sensorDescriptor.getDeathRecipient(), /* flags= */ 0);
+ final int handle = sensorDescriptor.getHandle();
+ mSensorManagerInternal.removeRuntimeSensor(handle);
+ }
+
+
+ void dump(@NonNull PrintWriter fout) {
+ fout.println(" SensorController: ");
+ synchronized (mLock) {
+ fout.println(" Active descriptors: ");
+ for (SensorDescriptor sensorDescriptor : mSensorDescriptors.values()) {
+ fout.println(" handle: " + sensorDescriptor.getHandle());
+ fout.println(" type: " + sensorDescriptor.getType());
+ fout.println(" name: " + sensorDescriptor.getName());
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void addSensorForTesting(IBinder deviceToken, int handle, int type, String name) {
+ synchronized (mLock) {
+ mSensorDescriptors.put(deviceToken,
+ new SensorDescriptor(handle, type, name, () -> {}));
+ }
+ }
+
+ @VisibleForTesting
+ Map<IBinder, SensorDescriptor> getSensorDescriptors() {
+ synchronized (mLock) {
+ return mSensorDescriptors;
+ }
+ }
+
+ @VisibleForTesting
+ static final class SensorDescriptor {
+
+ private final int mHandle;
+ private final IBinder.DeathRecipient mDeathRecipient;
+ private final int mType;
+ private final String mName;
+
+ SensorDescriptor(int handle, int type, String name, IBinder.DeathRecipient deathRecipient) {
+ mHandle = handle;
+ mDeathRecipient = deathRecipient;
+ mType = type;
+ mName = name;
+ }
+ public int getHandle() {
+ return mHandle;
+ }
+ public int getType() {
+ return mType;
+ }
+ public String getName() {
+ return mName;
+ }
+ public IBinder.DeathRecipient getDeathRecipient() {
+ return mDeathRecipient;
+ }
+ }
+
+ private final class BinderDeathRecipient implements IBinder.DeathRecipient {
+ private final IBinder mDeviceToken;
+
+ BinderDeathRecipient(IBinder deviceToken) {
+ mDeviceToken = deviceToken;
+ }
+
+ @Override
+ public void binderDied() {
+ // All callers are expected to call {@link VirtualDevice#unregisterSensor} before
+ // quitting, which removes this death recipient. If this is invoked, the remote end
+ // died, or they disposed of the object without properly unregistering.
+ Slog.e(TAG, "Virtual sensor controller binder died");
+ unregisterSensor(mDeviceToken);
+ }
+ }
+
+ /** An internal exception that is thrown to indicate an error when opening a virtual sensor. */
+ private static class SensorCreationException extends Exception {
+ SensorCreationException(String message) {
+ super(message);
+ }
+ SensorCreationException(String message, Exception cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 019f582d4790..798ae0ea3230 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -77,6 +77,7 @@ import com.android.server.companion.virtual.audio.VirtualAudioController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -99,6 +100,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private final int mOwnerUid;
private final int mDeviceId;
private final InputController mInputController;
+ private final SensorController mSensorController;
private VirtualAudioController mVirtualAudioController;
@VisibleForTesting
final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
@@ -161,6 +163,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
ownerUid,
deviceId,
/* inputController= */ null,
+ /* sensorController= */ null,
listener,
pendingTrampolineCallback,
activityListener,
@@ -176,6 +179,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
int ownerUid,
int deviceId,
InputController inputController,
+ SensorController sensorController,
OnDeviceCloseListener listener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
@@ -199,6 +203,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
} else {
mInputController = inputController;
}
+ if (sensorController == null) {
+ mSensorController = new SensorController(mVirtualDeviceLock, mDeviceId);
+ } else {
+ mSensorController = sensorController;
+ }
mListener = listener;
try {
token.linkToDeath(this, 0);
@@ -320,11 +329,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
mListener.onClose(mAssociationInfo.getId());
mAppToken.unlinkToDeath(this, 0);
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.close();
+ mSensorController.close();
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -404,12 +414,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
+ "this virtual device");
}
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createDpad(deviceName, vendorId, productId, deviceToken,
displayId);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -431,12 +441,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
+ "this virtual device");
}
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createKeyboard(deviceName, vendorId, productId, deviceToken,
displayId);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -458,11 +468,11 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
+ "virtual device");
}
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createMouse(deviceName, vendorId, productId, deviceToken, displayId);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -492,12 +502,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
+ screenSize);
}
- final long token = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.createTouchscreen(deviceName, vendorId, productId,
deviceToken, displayId, screenSize);
} finally {
- Binder.restoreCallingIdentity(token);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -507,92 +517,92 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to unregister this input device");
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
mInputController.unregisterInputDevice(token);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public int getInputDeviceId(IBinder token) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.getInputDeviceId(token);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendDpadKeyEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendKeyEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendButtonEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendTouchEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendRelativeEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.sendScrollEvent(token, event);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
public PointF getCursorPosition(IBinder token) {
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
return mInputController.getCursorPosition(token);
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -602,7 +612,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
"Permission required to unregister this input device");
- final long binderToken = Binder.clearCallingIdentity();
+ final long ident = Binder.clearCallingIdentity();
try {
synchronized (mVirtualDeviceLock) {
mDefaultShowPointerIcon = showPointerIcon;
@@ -611,7 +621,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
}
} finally {
- Binder.restoreCallingIdentity(binderToken);
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -619,15 +629,43 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
public void createVirtualSensor(
@NonNull IBinder deviceToken,
@NonNull VirtualSensorConfig config) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to create a virtual sensor");
+ Objects.requireNonNull(config);
+ Objects.requireNonNull(deviceToken);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mSensorController.createSensor(deviceToken, config);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
@Override // Binder call
- public void unregisterSensor(IBinder token) {
+ public void unregisterSensor(@NonNull IBinder token) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to unregister a virtual sensor");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mSensorController.unregisterSensor(token);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
@Override // Binder call
- public boolean sendSensorEvent(IBinder token, VirtualSensorEvent event) {
- return true;
+ public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
+ "Permission required to send a virtual sensor event");
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mSensorController.sendSensorEvent(token, event);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
@Override
@@ -643,6 +681,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
}
mInputController.dump(fout);
+ mSensorController.dump(fout);
}
GenericWindowPolicyController createWindowPolicyController() {
diff --git a/services/core/java/com/android/server/sensors/SensorManagerInternal.java b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
index fbb6644934f1..f17e5e73ceb0 100644
--- a/services/core/java/com/android/server/sensors/SensorManagerInternal.java
+++ b/services/core/java/com/android/server/sensors/SensorManagerInternal.java
@@ -43,6 +43,43 @@ public abstract class SensorManagerInternal {
public abstract void removeProximityActiveListener(@NonNull ProximityActiveListener listener);
/**
+ * Creates a sensor that is registered at runtime by the system with the sensor service.
+ *
+ * The runtime sensors created here are different from the
+ * <a href="https://source.android.com/docs/core/interaction/sensors/sensors-hal2#dynamic-sensors">
+ * dynamic sensor support in the HAL</a>. These sensors have no HAL dependency and correspond to
+ * sensors that belong to an external (virtual) device.
+ *
+ * @param deviceId The identifier of the device this sensor is associated with.
+ * @param type The generic type of the sensor.
+ * @param name The name of the sensor.
+ * @param vendor The vendor string of the sensor.
+ * @param callback The callback to get notified when the sensor listeners have changed.
+ * @return The sensor handle.
+ */
+ public abstract int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+ @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback);
+
+ /**
+ * Unregisters the sensor with the given handle from the framework.
+ */
+ public abstract void removeRuntimeSensor(int handle);
+
+ /**
+ * Sends an event for the runtime sensor with the given handle to the framework.
+ *
+ * Only relevant for sending runtime sensor events. @see #createRuntimeSensor.
+ *
+ * @param handle The sensor handle.
+ * @param type The type of the sensor.
+ * @param timestampNanos When the event occurred.
+ * @param values The values of the event.
+ * @return Whether the event injection was successful.
+ */
+ public abstract boolean sendSensorEvent(int handle, int type, long timestampNanos,
+ @NonNull float[] values);
+
+ /**
* Listener for proximity sensor state changes.
*/
public interface ProximityActiveListener {
@@ -52,4 +89,17 @@ public abstract class SensorManagerInternal {
*/
void onProximityActive(boolean isActive);
}
+
+ /**
+ * Callback for runtime sensor state changes. Only relevant to sensors created via
+ * {@link #createRuntimeSensor}, i.e. the dynamic sensors created via the dynamic sensor HAL are
+ * not covered.
+ */
+ public interface RuntimeSensorStateChangeCallback {
+ /**
+ * Invoked when the listeners of the runtime sensor have changed.
+ */
+ void onStateChanged(boolean enabled, int samplingPeriodMicros,
+ int batchReportLatencyMicros);
+ }
}
diff --git a/services/core/java/com/android/server/sensors/SensorService.java b/services/core/java/com/android/server/sensors/SensorService.java
index 8fe2d52f7160..d8e3bddd6432 100644
--- a/services/core/java/com/android/server/sensors/SensorService.java
+++ b/services/core/java/com/android/server/sensors/SensorService.java
@@ -29,7 +29,9 @@ import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.utils.TimingsTraceAndSlog;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -40,6 +42,8 @@ public class SensorService extends SystemService {
private final ArrayMap<ProximityActiveListener, ProximityListenerProxy> mProximityListeners =
new ArrayMap<>();
@GuardedBy("mLock")
+ private final Set<Integer> mRuntimeSensorHandles = new HashSet<>();
+ @GuardedBy("mLock")
private Future<?> mSensorServiceStart;
@GuardedBy("mLock")
private long mPtr;
@@ -51,6 +55,12 @@ public class SensorService extends SystemService {
private static native void registerProximityActiveListenerNative(long ptr);
private static native void unregisterProximityActiveListenerNative(long ptr);
+ private static native int registerRuntimeSensorNative(long ptr, int deviceId, int type,
+ String name, String vendor,
+ SensorManagerInternal.RuntimeSensorStateChangeCallback callback);
+ private static native void unregisterRuntimeSensorNative(long ptr, int handle);
+ private static native boolean sendRuntimeSensorEventNative(long ptr, int handle, int type,
+ long timestampNanos, float[] values);
public SensorService(Context ctx) {
super(ctx);
@@ -85,6 +95,38 @@ public class SensorService extends SystemService {
class LocalService extends SensorManagerInternal {
@Override
+ public int createRuntimeSensor(int deviceId, int type, @NonNull String name,
+ @NonNull String vendor, @NonNull RuntimeSensorStateChangeCallback callback) {
+ synchronized (mLock) {
+ int handle = registerRuntimeSensorNative(mPtr, deviceId, type, name, vendor,
+ callback);
+ mRuntimeSensorHandles.add(handle);
+ return handle;
+ }
+ }
+
+ @Override
+ public void removeRuntimeSensor(int handle) {
+ synchronized (mLock) {
+ if (mRuntimeSensorHandles.contains(handle)) {
+ mRuntimeSensorHandles.remove(handle);
+ unregisterRuntimeSensorNative(mPtr, handle);
+ }
+ }
+ }
+
+ @Override
+ public boolean sendSensorEvent(int handle, int type, long timestampNanos,
+ @NonNull float[] values) {
+ synchronized (mLock) {
+ if (!mRuntimeSensorHandles.contains(handle)) {
+ return false;
+ }
+ return sendRuntimeSensorEventNative(mPtr, handle, type, timestampNanos, values);
+ }
+ }
+
+ @Override
public void addProximityActiveListener(@NonNull Executor executor,
@NonNull ProximityActiveListener listener) {
Objects.requireNonNull(executor, "executor must not be null");
diff --git a/services/core/jni/com_android_server_sensor_SensorService.cpp b/services/core/jni/com_android_server_sensor_SensorService.cpp
index 63b7dfbc2a3b..10d8b42c2979 100644
--- a/services/core/jni/com_android_server_sensor_SensorService.cpp
+++ b/services/core/jni/com_android_server_sensor_SensorService.cpp
@@ -22,6 +22,7 @@
#include <cutils/properties.h>
#include <jni.h>
#include <sensorservice/SensorService.h>
+#include <string.h>
#include <utils/Log.h>
#include <utils/misc.h>
@@ -30,10 +31,14 @@
#define PROXIMITY_ACTIVE_CLASS \
"com/android/server/sensors/SensorManagerInternal$ProximityActiveListener"
+#define RUNTIME_SENSOR_CALLBACK_CLASS \
+ "com/android/server/sensors/SensorManagerInternal$RuntimeSensorStateChangeCallback"
+
namespace android {
static JavaVM* sJvm = nullptr;
static jmethodID sMethodIdOnProximityActive;
+static jmethodID sMethodIdOnStateChanged;
class NativeSensorService {
public:
@@ -41,6 +46,11 @@ public:
void registerProximityActiveListener();
void unregisterProximityActiveListener();
+ jint registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name, jstring vendor,
+ jobject callback);
+ void unregisterRuntimeSensor(jint handle);
+ jboolean sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type, jlong timestamp,
+ jfloatArray values);
private:
sp<SensorService> mService;
@@ -56,6 +66,18 @@ private:
jobject mListener;
};
sp<ProximityActiveListenerDelegate> mProximityActiveListenerDelegate;
+
+ class RuntimeSensorCallbackDelegate : public SensorService::RuntimeSensorStateChangeCallback {
+ public:
+ RuntimeSensorCallbackDelegate(JNIEnv* env, jobject callback);
+ ~RuntimeSensorCallbackDelegate();
+
+ void onStateChanged(bool enabled, int64_t samplingPeriodNs,
+ int64_t batchReportLatencyNs) override;
+
+ private:
+ jobject mCallback;
+ };
};
NativeSensorService::NativeSensorService(JNIEnv* env, jobject listener)
@@ -85,6 +107,109 @@ void NativeSensorService::unregisterProximityActiveListener() {
mService->removeProximityActiveListener(mProximityActiveListenerDelegate);
}
+jint NativeSensorService::registerRuntimeSensor(JNIEnv* env, jint deviceId, jint type, jstring name,
+ jstring vendor, jobject callback) {
+ if (mService == nullptr) {
+ ALOGD("Dropping registerRuntimeSensor, sensor service not available.");
+ return -1;
+ }
+
+ sensor_t sensor{
+ .name = env->GetStringUTFChars(name, 0),
+ .vendor = env->GetStringUTFChars(vendor, 0),
+ .version = sizeof(sensor_t),
+ .type = type,
+ };
+
+ sp<RuntimeSensorCallbackDelegate> callbackDelegate(
+ new RuntimeSensorCallbackDelegate(env, callback));
+ return mService->registerRuntimeSensor(sensor, deviceId, callbackDelegate);
+}
+
+void NativeSensorService::unregisterRuntimeSensor(jint handle) {
+ if (mService == nullptr) {
+ ALOGD("Dropping unregisterProximityActiveListener, sensor service not available.");
+ return;
+ }
+
+ mService->unregisterRuntimeSensor(handle);
+}
+
+jboolean NativeSensorService::sendRuntimeSensorEvent(JNIEnv* env, jint handle, jint type,
+ jlong timestamp, jfloatArray values) {
+ if (mService == nullptr) {
+ ALOGD("Dropping sendRuntimeSensorEvent, sensor service not available.");
+ return false;
+ }
+ if (values == nullptr) {
+ ALOGD("Dropping sendRuntimeSensorEvent, no values.");
+ return false;
+ }
+
+ sensors_event_t event{
+ .version = sizeof(sensors_event_t),
+ .timestamp = timestamp,
+ .sensor = handle,
+ .type = type,
+ };
+
+ int valuesLength = env->GetArrayLength(values);
+ jfloat* sensorValues = env->GetFloatArrayElements(values, nullptr);
+
+ switch (type) {
+ case SENSOR_TYPE_ACCELEROMETER:
+ case SENSOR_TYPE_MAGNETIC_FIELD:
+ case SENSOR_TYPE_ORIENTATION:
+ case SENSOR_TYPE_GYROSCOPE:
+ case SENSOR_TYPE_GRAVITY:
+ case SENSOR_TYPE_LINEAR_ACCELERATION: {
+ if (valuesLength != 3) {
+ ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+ return false;
+ }
+ event.acceleration.x = sensorValues[0];
+ event.acceleration.y = sensorValues[1];
+ event.acceleration.z = sensorValues[2];
+ break;
+ }
+ case SENSOR_TYPE_DEVICE_ORIENTATION:
+ case SENSOR_TYPE_LIGHT:
+ case SENSOR_TYPE_PRESSURE:
+ case SENSOR_TYPE_TEMPERATURE:
+ case SENSOR_TYPE_PROXIMITY:
+ case SENSOR_TYPE_RELATIVE_HUMIDITY:
+ case SENSOR_TYPE_AMBIENT_TEMPERATURE:
+ case SENSOR_TYPE_SIGNIFICANT_MOTION:
+ case SENSOR_TYPE_STEP_DETECTOR:
+ case SENSOR_TYPE_TILT_DETECTOR:
+ case SENSOR_TYPE_WAKE_GESTURE:
+ case SENSOR_TYPE_GLANCE_GESTURE:
+ case SENSOR_TYPE_PICK_UP_GESTURE:
+ case SENSOR_TYPE_WRIST_TILT_GESTURE:
+ case SENSOR_TYPE_STATIONARY_DETECT:
+ case SENSOR_TYPE_MOTION_DETECT:
+ case SENSOR_TYPE_HEART_BEAT:
+ case SENSOR_TYPE_LOW_LATENCY_OFFBODY_DETECT: {
+ if (valuesLength != 1) {
+ ALOGD("Dropping sendRuntimeSensorEvent, wrong number of values.");
+ return false;
+ }
+ event.data[0] = sensorValues[0];
+ break;
+ }
+ default: {
+ if (valuesLength > 16) {
+ ALOGD("Dropping sendRuntimeSensorEvent, number of values exceeds the maximum.");
+ return false;
+ }
+ memcpy(event.data, sensorValues, valuesLength * sizeof(float));
+ }
+ }
+
+ status_t err = mService->sendRuntimeSensorEvent(event);
+ return err == OK;
+}
+
NativeSensorService::ProximityActiveListenerDelegate::ProximityActiveListenerDelegate(
JNIEnv* env, jobject listener)
: mListener(env->NewGlobalRef(listener)) {}
@@ -98,6 +223,22 @@ void NativeSensorService::ProximityActiveListenerDelegate::onProximityActive(boo
jniEnv->CallVoidMethod(mListener, sMethodIdOnProximityActive, static_cast<jboolean>(isActive));
}
+NativeSensorService::RuntimeSensorCallbackDelegate::RuntimeSensorCallbackDelegate(JNIEnv* env,
+ jobject callback)
+ : mCallback(env->NewGlobalRef(callback)) {}
+
+NativeSensorService::RuntimeSensorCallbackDelegate::~RuntimeSensorCallbackDelegate() {
+ AndroidRuntime::getJNIEnv()->DeleteGlobalRef(mCallback);
+}
+
+void NativeSensorService::RuntimeSensorCallbackDelegate::onStateChanged(
+ bool enabled, int64_t samplingPeriodNs, int64_t batchReportLatencyNs) {
+ auto jniEnv = GetOrAttachJNIEnvironment(sJvm);
+ jniEnv->CallVoidMethod(mCallback, sMethodIdOnStateChanged, static_cast<jboolean>(enabled),
+ static_cast<jint>(ns2us(samplingPeriodNs)),
+ static_cast<jint>(ns2us(batchReportLatencyNs)));
+}
+
static jlong startSensorServiceNative(JNIEnv* env, jclass, jobject listener) {
NativeSensorService* service = new NativeSensorService(env, listener);
return reinterpret_cast<jlong>(service);
@@ -113,26 +254,46 @@ static void unregisterProximityActiveListenerNative(JNIEnv* env, jclass, jlong p
service->unregisterProximityActiveListener();
}
-static const JNINativeMethod methods[] = {
- {
- "startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
- reinterpret_cast<void*>(startSensorServiceNative)
- },
- {
- "registerProximityActiveListenerNative", "(J)V",
- reinterpret_cast<void*>(registerProximityActiveListenerNative)
- },
- {
- "unregisterProximityActiveListenerNative", "(J)V",
- reinterpret_cast<void*>(unregisterProximityActiveListenerNative)
- },
+static jint registerRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint deviceId, jint type,
+ jstring name, jstring vendor, jobject callback) {
+ auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+ return service->registerRuntimeSensor(env, deviceId, type, name, vendor, callback);
+}
+
+static void unregisterRuntimeSensorNative(JNIEnv* env, jclass, jlong ptr, jint handle) {
+ auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+ service->unregisterRuntimeSensor(handle);
+}
+
+static jboolean sendRuntimeSensorEventNative(JNIEnv* env, jclass, jlong ptr, jint handle, jint type,
+ jlong timestamp, jfloatArray values) {
+ auto* service = reinterpret_cast<NativeSensorService*>(ptr);
+ return service->sendRuntimeSensorEvent(env, handle, type, timestamp, values);
+}
+static const JNINativeMethod methods[] = {
+ {"startSensorServiceNative", "(L" PROXIMITY_ACTIVE_CLASS ";)J",
+ reinterpret_cast<void*>(startSensorServiceNative)},
+ {"registerProximityActiveListenerNative", "(J)V",
+ reinterpret_cast<void*>(registerProximityActiveListenerNative)},
+ {"unregisterProximityActiveListenerNative", "(J)V",
+ reinterpret_cast<void*>(unregisterProximityActiveListenerNative)},
+ {"registerRuntimeSensorNative",
+ "(JIILjava/lang/String;Ljava/lang/String;L" RUNTIME_SENSOR_CALLBACK_CLASS ";)I",
+ reinterpret_cast<void*>(registerRuntimeSensorNative)},
+ {"unregisterRuntimeSensorNative", "(JI)V",
+ reinterpret_cast<void*>(unregisterRuntimeSensorNative)},
+ {"sendRuntimeSensorEventNative", "(JIIJ[F)Z",
+ reinterpret_cast<void*>(sendRuntimeSensorEventNative)},
};
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env) {
sJvm = vm;
jclass listenerClass = FindClassOrDie(env, PROXIMITY_ACTIVE_CLASS);
sMethodIdOnProximityActive = GetMethodIDOrDie(env, listenerClass, "onProximityActive", "(Z)V");
+ jclass runtimeSensorCallbackClass = FindClassOrDie(env, RUNTIME_SENSOR_CALLBACK_CLASS);
+ sMethodIdOnStateChanged =
+ GetMethodIDOrDie(env, runtimeSensorCallbackClass, "onStateChanged", "(ZII)V");
return jniRegisterNativeMethods(env, "com/android/server/sensors/SensorService", methods,
NELEM(methods));
}
diff --git a/services/proguard.flags b/services/proguard.flags
index 27fe5056b8b4..6cdf11c3c685 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -88,6 +88,7 @@
-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.GnssPowerStats { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.location.gnss.hal.GnssNative { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.pm.PackageManagerShellCommandDataLoader { *; }
+-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$RuntimeSensorStateChangeCallback { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorManagerInternal$ProximityActiveListener { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.sensors.SensorService { *; }
-keep,allowoptimization,allowaccessmodification class com.android.server.soundtrigger_middleware.SoundTriggerMiddlewareImpl$AudioSessionProvider$AudioSession { *; }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
new file mode 100644
index 000000000000..ef8a49f95a49
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/SensorControllerTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2022 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 com.android.server.companion.virtual;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.hardware.Sensor;
+import android.os.Binder;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.server.LocalServices;
+import com.android.server.sensors.SensorManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@Presubmit
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class SensorControllerTest {
+
+ private static final int VIRTUAL_DEVICE_ID = 42;
+ private static final String VIRTUAL_SENSOR_NAME = "VirtualAccelerometer";
+ private static final int SENSOR_HANDLE = 7;
+
+ @Mock
+ private SensorManagerInternal mSensorManagerInternalMock;
+ private SensorController mSensorController;
+ private VirtualSensorEvent mSensorEvent;
+ private VirtualSensorConfig mVirtualSensorConfig;
+ private IBinder mSensorToken;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(SensorManagerInternal.class);
+ LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
+ mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
+ mSensorEvent = new VirtualSensorEvent.Builder(new float[] { 1f, 2f, 3f}).build();
+ mVirtualSensorConfig =
+ new VirtualSensorConfig.Builder(Sensor.TYPE_ACCELEROMETER, VIRTUAL_SENSOR_NAME)
+ .build();
+ mSensorToken = new Binder("sensorToken");
+ }
+
+ @Test
+ public void createSensor_invalidHandle_throwsException() {
+ doReturn(/* handle= */0).when(mSensorManagerInternalMock).createRuntimeSensor(
+ anyInt(), anyInt(), anyString(), anyString(), any());
+
+ Throwable thrown = assertThrows(
+ RuntimeException.class,
+ () -> mSensorController.createSensor(mSensorToken, mVirtualSensorConfig));
+
+ assertThat(thrown.getCause().getMessage())
+ .contains("Received an invalid virtual sensor handle");
+ }
+
+ @Test
+ public void createSensor_success() {
+ doCreateSensorSuccessfully();
+
+ assertThat(mSensorController.getSensorDescriptors()).isNotEmpty();
+ }
+
+ @Test
+ public void sendSensorEvent_invalidToken_throwsException() {
+ doCreateSensorSuccessfully();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mSensorController.sendSensorEvent(
+ new Binder("invalidSensorToken"), mSensorEvent));
+ }
+
+ @Test
+ public void sendSensorEvent_success() {
+ doCreateSensorSuccessfully();
+
+ mSensorController.sendSensorEvent(mSensorToken, mSensorEvent);
+ verify(mSensorManagerInternalMock).sendSensorEvent(
+ SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, mSensorEvent.getTimestampNanos(),
+ mSensorEvent.getValues());
+ }
+
+ @Test
+ public void unregisterSensor_invalidToken_throwsException() {
+ doCreateSensorSuccessfully();
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mSensorController.unregisterSensor(new Binder("invalidSensorToken")));
+ }
+
+ @Test
+ public void unregisterSensor_success() {
+ doCreateSensorSuccessfully();
+
+ mSensorController.unregisterSensor(mSensorToken);
+ verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+ assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+ }
+
+ private void doCreateSensorSuccessfully() {
+ doReturn(SENSOR_HANDLE).when(mSensorManagerInternalMock).createRuntimeSensor(
+ anyInt(), anyInt(), anyString(), anyString(), any());
+ mSensorController.createSensor(mSensorToken, mVirtualSensorConfig);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 09dc367cceb4..41700ab1f04e 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -51,6 +51,7 @@ import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
+import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.content.Context;
import android.content.ContextWrapper;
@@ -58,6 +59,7 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
+import android.hardware.Sensor;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
import android.hardware.input.VirtualKeyEvent;
@@ -88,6 +90,7 @@ import androidx.test.InstrumentationRegistry;
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.server.LocalServices;
import com.android.server.input.InputManagerInternal;
+import com.android.server.sensors.SensorManagerInternal;
import org.junit.Before;
import org.junit.Test;
@@ -125,16 +128,19 @@ public class VirtualDeviceManagerServiceTest {
private static final int VENDOR_ID = 5;
private static final String UNIQUE_ID = "uniqueid";
private static final String PHYS = "phys";
- private static final int DEVICE_ID = 42;
+ private static final int DEVICE_ID = 53;
private static final int HEIGHT = 1800;
private static final int WIDTH = 900;
+ private static final int SENSOR_HANDLE = 64;
private static final Binder BINDER = new Binder("binder");
private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
+ private static final int VIRTUAL_DEVICE_ID = 42;
private Context mContext;
private InputManagerMockHelper mInputManagerMockHelper;
private VirtualDeviceImpl mDeviceImpl;
private InputController mInputController;
+ private SensorController mSensorController;
private AssociationInfo mAssociationInfo;
private VirtualDeviceManagerService mVdms;
private VirtualDeviceManagerInternal mLocalService;
@@ -149,6 +155,8 @@ public class VirtualDeviceManagerServiceTest {
@Mock
private InputManagerInternal mInputManagerInternalMock;
@Mock
+ private SensorManagerInternal mSensorManagerInternalMock;
+ @Mock
private IVirtualDeviceActivityListener mActivityListener;
@Mock
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@@ -205,6 +213,9 @@ public class VirtualDeviceManagerServiceTest {
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
+ LocalServices.removeServiceForTest(SensorManagerInternal.class);
+ LocalServices.addService(SensorManagerInternal.class, mSensorManagerInternalMock);
+
final DisplayInfo displayInfo = new DisplayInfo();
displayInfo.uniqueId = UNIQUE_ID;
doReturn(displayInfo).when(mDisplayManagerInternalMock).getDisplayInfo(anyInt());
@@ -229,6 +240,7 @@ public class VirtualDeviceManagerServiceTest {
mInputController = new InputController(new Object(), mNativeWrapperMock,
new Handler(TestableLooper.get(this).getLooper()),
mContext.getSystemService(WindowManager.class), threadVerifier);
+ mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
mAssociationInfo = new AssociationInfo(1, 0, null,
MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
@@ -241,9 +253,9 @@ public class VirtualDeviceManagerServiceTest {
.setBlockedActivities(getBlockedActivities())
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
- mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
- mActivityListener, mRunningAppsChangedCallback, params);
+ mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+ mInputController, mSensorController, (int associationId) -> {},
+ mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(mDeviceImpl);
}
@@ -285,9 +297,9 @@ public class VirtualDeviceManagerServiceTest {
.addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
.build();
mDeviceImpl = new VirtualDeviceImpl(mContext,
- mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1,
- mInputController, (int associationId) -> {}, mPendingTrampolineCallback,
- mActivityListener, mRunningAppsChangedCallback, params);
+ mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+ mInputController, mSensorController, (int associationId) -> {},
+ mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
mVdms.addVirtualDevice(mDeviceImpl);
assertThat(
@@ -552,6 +564,18 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void createVirtualSensor_noPermission_failsSecurityException() {
+ doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
+ eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
+ assertThrows(
+ SecurityException.class,
+ () -> mDeviceImpl.createVirtualSensor(
+ BINDER,
+ new VirtualSensorConfig.Builder(
+ Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build()));
+ }
+
+ @Test
public void onAudioSessionStarting_noPermission_failsSecurityException() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
@@ -655,6 +679,17 @@ public class VirtualDeviceManagerServiceTest {
}
@Test
+ public void close_cleanSensorController() {
+ mSensorController.addSensorForTesting(
+ BINDER, SENSOR_HANDLE, Sensor.TYPE_ACCELEROMETER, DEVICE_NAME);
+
+ mDeviceImpl.close();
+
+ assertThat(mSensorController.getSensorDescriptors()).isEmpty();
+ verify(mSensorManagerInternalMock).removeRuntimeSensor(SENSOR_HANDLE);
+ }
+
+ @Test
public void sendKeyEvent_noFd() {
assertThrows(
IllegalArgumentException.class,