Merge changes from topic "virtual-sensors"

* changes:
  Adjust the behavior of SensorManager for virtual device sensors.
  Virtual sensors lifecycle management.
  Virtual sensor API.
This commit is contained in:
Vladimir Komsiyski
2022-11-30 12:41:38 +00:00
committed by Android (Google) Code Review
24 changed files with 1869 additions and 111 deletions

View File

@@ -2964,6 +2964,7 @@ package android.companion.virtual {
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualMouse createVirtualMouse(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public android.hardware.input.VirtualTouchscreen createVirtualTouchscreen(@NonNull android.hardware.display.VirtualDisplay, @NonNull String, int, int);
method public int getDeviceId();
method @Nullable public android.companion.virtual.sensor.VirtualSensor getVirtualSensor(int, @NonNull String);
method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
method @NonNull @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -2981,6 +2982,7 @@ package android.companion.virtual {
method public int getLockState();
method @Nullable public String getName();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0
field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1
@@ -2997,6 +2999,7 @@ package android.companion.virtual {
public static final class VirtualDeviceParams.Builder {
ctor public VirtualDeviceParams.Builder();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addDevicePolicy(int, int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder addVirtualSensorConfig(@NonNull android.companion.virtual.sensor.VirtualSensorConfig);
method @NonNull public android.companion.virtual.VirtualDeviceParams build();
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
@@ -3054,6 +3057,50 @@ package android.companion.virtual.audio {
}
package android.companion.virtual.sensor {
public class VirtualSensor {
method @NonNull public String getName();
method public int getType();
method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
}
public static interface VirtualSensor.SensorStateChangeCallback {
method public void onStateChanged(boolean, @NonNull java.time.Duration, @NonNull java.time.Duration);
}
public final class VirtualSensorConfig implements android.os.Parcelable {
method public int describeContents();
method @NonNull public String getName();
method public int getType();
method @Nullable public String getVendor();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorConfig> CREATOR;
}
public static final class VirtualSensorConfig.Builder {
ctor public VirtualSensorConfig.Builder(int, @NonNull String);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig build();
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setStateChangeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensor.SensorStateChangeCallback);
method @NonNull public android.companion.virtual.sensor.VirtualSensorConfig.Builder setVendor(@Nullable String);
}
public final class VirtualSensorEvent implements android.os.Parcelable {
method public int describeContents();
method public long getTimestampNanos();
method @NonNull public float[] getValues();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.companion.virtual.sensor.VirtualSensorEvent> CREATOR;
}
public static final class VirtualSensorEvent.Builder {
ctor public VirtualSensorEvent.Builder(@NonNull float[]);
method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent build();
method @NonNull public android.companion.virtual.sensor.VirtualSensorEvent.Builder setTimestampNanos(long);
}
}
package android.content {
public class ApexEnvironment {

View File

@@ -19,6 +19,9 @@ package android.companion.virtual;
import android.app.PendingIntent;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.IVirtualSensorStateChangeCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.companion.virtual.sensor.VirtualSensorEvent;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.input.VirtualKeyEvent;
@@ -96,6 +99,24 @@ interface IVirtualDevice {
boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
/**
* Creates a virtual sensor, capable of injecting sensor events into the system.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config);
/**
* Removes the sensor corresponding to the given token from the system.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
void unregisterSensor(IBinder token);
/**
* Sends an event to the virtual sensor corresponding to the given token.
*/
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event);
/**
* Launches a pending intent on the given display that is owned by this virtual device.
*/

View File

@@ -22,12 +22,15 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.virtual.audio.VirtualAudioDevice;
import android.companion.virtual.audio.VirtualAudioDevice.AudioConfigurationChangeCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Point;
@@ -58,6 +61,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.IntConsumer;
@@ -89,6 +93,26 @@ public final class VirtualDeviceManager {
*/
public static final int INVALID_DEVICE_ID = -1;
/**
* Broadcast Action: A Virtual Device was removed.
*
* <p class="note">This is a protected intent that can only be sent by the system.</p>
*
* @hide
*/
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_VIRTUAL_DEVICE_REMOVED =
"android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED";
/**
* Int intent extra to be used with {@link #ACTION_VIRTUAL_DEVICE_REMOVED}.
* Contains the identifier of the virtual device, which was removed.
*
* @hide
*/
public static final String EXTRA_VIRTUAL_DEVICE_ID =
"android.companion.virtual.extra.VIRTUAL_DEVICE_ID";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(
@@ -251,7 +275,10 @@ public final class VirtualDeviceManager {
};
@Nullable
private VirtualAudioDevice mVirtualAudioDevice;
@NonNull
private List<VirtualSensor> mVirtualSensors = new ArrayList<>();
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
private VirtualDevice(
IVirtualDeviceManager service,
Context context,
@@ -265,6 +292,10 @@ public final class VirtualDeviceManager {
associationId,
params,
mActivityListenerBinder);
final List<VirtualSensorConfig> virtualSensorConfigs = params.getVirtualSensorConfigs();
for (int i = 0; i < virtualSensorConfigs.size(); ++i) {
mVirtualSensors.add(createVirtualSensor(virtualSensorConfigs.get(i)));
}
}
/**
@@ -278,6 +309,23 @@ public final class VirtualDeviceManager {
}
}
/**
* Returns this device's sensor with the given type and name, if any.
*
* @see VirtualDeviceParams.Builder#addVirtualSensorConfig
*
* @param type The type of the sensor.
* @param name The name of the sensor.
* @return The matching sensor if found, {@code null} otherwise.
*/
@Nullable
public VirtualSensor getVirtualSensor(int type, @NonNull String name) {
return mVirtualSensors.stream()
.filter(sensor -> sensor.getType() == type && sensor.getName().equals(name))
.findAny()
.orElse(null);
}
/**
* Launches a given pending intent on the give display ID.
*
@@ -438,6 +486,7 @@ public final class VirtualDeviceManager {
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void close() {
try {
// This also takes care of unregistering all virtual sensors.
mVirtualDevice.close();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -622,6 +671,28 @@ public final class VirtualDeviceManager {
}
}
/**
* Creates a virtual sensor, capable of injecting sensor events into the system. Only for
* internal use, since device sensors must remain valid for the entire lifetime of the
* device.
*
* @param config The configuration of the sensor.
* @hide
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
@NonNull
public VirtualSensor createVirtualSensor(@NonNull VirtualSensorConfig config) {
Objects.requireNonNull(config);
try {
final IBinder token = new Binder(
"android.hardware.sensor.VirtualSensor:" + config.getName());
mVirtualDevice.createVirtualSensor(token, config);
return new VirtualSensor(config.getType(), config.getName(), mVirtualDevice, token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Adds an activity listener to listen for events such as top activity change or virtual
* display task stack became empty.

View File

@@ -23,20 +23,22 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.SparseIntArray;
import com.android.internal.util.Preconditions;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -158,6 +160,7 @@ public final class VirtualDeviceParams implements Parcelable {
@Nullable private final String mName;
// Mapping of @PolicyType to @DevicePolicy
@NonNull private final SparseIntArray mDevicePolicies;
@NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
private VirtualDeviceParams(
@LockState int lockState,
@@ -169,24 +172,22 @@ public final class VirtualDeviceParams implements Parcelable {
@NonNull Set<ComponentName> blockedActivities,
@ActivityPolicy int defaultActivityPolicy,
@Nullable String name,
@NonNull SparseIntArray devicePolicies) {
Preconditions.checkNotNull(usersWithMatchingAccounts);
Preconditions.checkNotNull(allowedCrossTaskNavigations);
Preconditions.checkNotNull(blockedCrossTaskNavigations);
Preconditions.checkNotNull(allowedActivities);
Preconditions.checkNotNull(blockedActivities);
Preconditions.checkNotNull(devicePolicies);
@NonNull SparseIntArray devicePolicies,
@NonNull List<VirtualSensorConfig> virtualSensorConfigs) {
mLockState = lockState;
mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
mBlockedCrossTaskNavigations = new ArraySet<>(blockedCrossTaskNavigations);
mUsersWithMatchingAccounts =
new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
mAllowedCrossTaskNavigations =
new ArraySet<>(Objects.requireNonNull(allowedCrossTaskNavigations));
mBlockedCrossTaskNavigations =
new ArraySet<>(Objects.requireNonNull(blockedCrossTaskNavigations));
mDefaultNavigationPolicy = defaultNavigationPolicy;
mAllowedActivities = new ArraySet<>(allowedActivities);
mBlockedActivities = new ArraySet<>(blockedActivities);
mAllowedActivities = new ArraySet<>(Objects.requireNonNull(allowedActivities));
mBlockedActivities = new ArraySet<>(Objects.requireNonNull(blockedActivities));
mDefaultActivityPolicy = defaultActivityPolicy;
mName = name;
mDevicePolicies = devicePolicies;
mDevicePolicies = Objects.requireNonNull(devicePolicies);
mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
}
@SuppressWarnings("unchecked")
@@ -201,6 +202,8 @@ public final class VirtualDeviceParams implements Parcelable {
mDefaultActivityPolicy = parcel.readInt();
mName = parcel.readString8();
mDevicePolicies = parcel.readSparseIntArray();
mVirtualSensorConfigs = new ArrayList<>();
parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
}
/**
@@ -316,6 +319,15 @@ public final class VirtualDeviceParams implements Parcelable {
return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT);
}
/**
* Returns the configurations for all sensors that should be created for this device.
*
* @see Builder#addVirtualSensorConfig
*/
public @NonNull List<VirtualSensorConfig> getVirtualSensorConfigs() {
return mVirtualSensorConfigs;
}
@Override
public int describeContents() {
return 0;
@@ -333,6 +345,7 @@ public final class VirtualDeviceParams implements Parcelable {
dest.writeInt(mDefaultActivityPolicy);
dest.writeString8(mName);
dest.writeSparseIntArray(mDevicePolicies);
dest.writeTypedList(mVirtualSensorConfigs);
}
@Override
@@ -428,6 +441,7 @@ public final class VirtualDeviceParams implements Parcelable {
private boolean mDefaultActivityPolicyConfigured = false;
@Nullable private String mName;
@NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
@NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
/**
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -467,8 +481,7 @@ public final class VirtualDeviceParams implements Parcelable {
@NonNull
public Builder setUsersWithMatchingAccounts(
@NonNull Set<UserHandle> usersWithMatchingAccounts) {
Preconditions.checkNotNull(usersWithMatchingAccounts);
mUsersWithMatchingAccounts = usersWithMatchingAccounts;
mUsersWithMatchingAccounts = Objects.requireNonNull(usersWithMatchingAccounts);
return this;
}
@@ -491,7 +504,6 @@ public final class VirtualDeviceParams implements Parcelable {
@NonNull
public Builder setAllowedCrossTaskNavigations(
@NonNull Set<ComponentName> allowedCrossTaskNavigations) {
Preconditions.checkNotNull(allowedCrossTaskNavigations);
if (mDefaultNavigationPolicyConfigured
&& mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_BLOCKED) {
throw new IllegalArgumentException(
@@ -500,7 +512,7 @@ public final class VirtualDeviceParams implements Parcelable {
}
mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED;
mDefaultNavigationPolicyConfigured = true;
mAllowedCrossTaskNavigations = allowedCrossTaskNavigations;
mAllowedCrossTaskNavigations = Objects.requireNonNull(allowedCrossTaskNavigations);
return this;
}
@@ -523,7 +535,6 @@ public final class VirtualDeviceParams implements Parcelable {
@NonNull
public Builder setBlockedCrossTaskNavigations(
@NonNull Set<ComponentName> blockedCrossTaskNavigations) {
Preconditions.checkNotNull(blockedCrossTaskNavigations);
if (mDefaultNavigationPolicyConfigured
&& mDefaultNavigationPolicy != NAVIGATION_POLICY_DEFAULT_ALLOWED) {
throw new IllegalArgumentException(
@@ -532,7 +543,7 @@ public final class VirtualDeviceParams implements Parcelable {
}
mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
mDefaultNavigationPolicyConfigured = true;
mBlockedCrossTaskNavigations = blockedCrossTaskNavigations;
mBlockedCrossTaskNavigations = Objects.requireNonNull(blockedCrossTaskNavigations);
return this;
}
@@ -551,7 +562,6 @@ public final class VirtualDeviceParams implements Parcelable {
*/
@NonNull
public Builder setAllowedActivities(@NonNull Set<ComponentName> allowedActivities) {
Preconditions.checkNotNull(allowedActivities);
if (mDefaultActivityPolicyConfigured
&& mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_BLOCKED) {
throw new IllegalArgumentException(
@@ -559,7 +569,7 @@ public final class VirtualDeviceParams implements Parcelable {
}
mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED;
mDefaultActivityPolicyConfigured = true;
mAllowedActivities = allowedActivities;
mAllowedActivities = Objects.requireNonNull(allowedActivities);
return this;
}
@@ -578,7 +588,6 @@ public final class VirtualDeviceParams implements Parcelable {
*/
@NonNull
public Builder setBlockedActivities(@NonNull Set<ComponentName> blockedActivities) {
Preconditions.checkNotNull(blockedActivities);
if (mDefaultActivityPolicyConfigured
&& mDefaultActivityPolicy != ACTIVITY_POLICY_DEFAULT_ALLOWED) {
throw new IllegalArgumentException(
@@ -586,7 +595,7 @@ public final class VirtualDeviceParams implements Parcelable {
}
mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
mDefaultActivityPolicyConfigured = true;
mBlockedActivities = blockedActivities;
mBlockedActivities = Objects.requireNonNull(blockedActivities);
return this;
}
@@ -620,11 +629,50 @@ public final class VirtualDeviceParams implements Parcelable {
return this;
}
/**
* Adds a configuration for a sensor that should be created for this virtual device.
*
* Device sensors must remain valid for the entire lifetime of the device, hence they are
* created together with the device itself, and removed when the device is removed.
*
* Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_SENSORS}.
*
* @see android.companion.virtual.sensor.VirtualSensor
* @see #addDevicePolicy
*/
@NonNull
public Builder addVirtualSensorConfig(@NonNull VirtualSensorConfig virtualSensorConfig) {
mVirtualSensorConfigs.add(Objects.requireNonNull(virtualSensorConfig));
return this;
}
/**
* Builds the {@link VirtualDeviceParams} instance.
*
* @throws IllegalArgumentException if there's mismatch between policy definition and
* the passed parameters or if there are sensor configs with the same type and name.
*
*/
@NonNull
public VirtualDeviceParams build() {
if (!mVirtualSensorConfigs.isEmpty()
&& (mDevicePolicies.get(POLICY_TYPE_SENSORS, DEVICE_POLICY_DEFAULT)
!= DEVICE_POLICY_CUSTOM)) {
throw new IllegalArgumentException(
"DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
+ "virtual sensors.");
}
SparseArray<Set<String>> sensorNameByType = new SparseArray();
for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>());
if (!sensorNames.add(config.getName())) {
throw new IllegalArgumentException(
"Sensor names must be unique for a particular sensor type.");
}
sensorNameByType.put(config.getType(), sensorNames);
}
return new VirtualDeviceParams(
mLockState,
mUsersWithMatchingAccounts,
@@ -635,7 +683,8 @@ public final class VirtualDeviceParams implements Parcelable {
mBlockedActivities,
mDefaultActivityPolicy,
mName,
mDevicePolicies);
mDevicePolicies,
mVirtualSensorConfigs);
}
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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 android.companion.virtual.sensor;
/**
* Interface for notification of listener registration changes for a virtual sensor.
*
* @hide
*/
oneway interface IVirtualSensorStateChangeCallback {
/**
* Called when the registered listeners to a virtual sensor have changed.
*
* @param enabled Whether the sensor is enabled.
* @param samplingPeriodMicros The requested sensor's sampling period in microseconds.
* @param batchReportingLatencyMicros The requested maximum time interval in microseconds
* between the delivery of two batches of sensor events.
*/
void onStateChanged(boolean enabled, int samplingPeriodMicros, int batchReportLatencyMicros);
}

View File

@@ -0,0 +1,97 @@
/*
* 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 android.companion.virtual.sensor;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.companion.virtual.IVirtualDevice;
import android.os.IBinder;
import android.os.RemoteException;
import java.time.Duration;
/**
* Representation of a sensor on a remote device, capable of sending events, such as an
* accelerometer or a gyroscope.
*
* This registers the sensor device with the sensor framework as a runtime sensor.
*
* @hide
*/
@SystemApi
public class VirtualSensor {
/**
* Interface for notification of listener registration changes for a virtual sensor.
*/
public interface SensorStateChangeCallback {
/**
* Called when the registered listeners to a virtual sensor have changed.
*
* @param enabled Whether the sensor is enabled.
* @param samplingPeriod The requested sampling period of the sensor.
* @param batchReportLatency The requested maximum time interval between the delivery of two
* batches of sensor events.
*/
void onStateChanged(boolean enabled, @NonNull Duration samplingPeriod,
@NonNull Duration batchReportLatency);
}
private final int mType;
private final String mName;
private final IVirtualDevice mVirtualDevice;
private final IBinder mToken;
/**
* @hide
*/
public VirtualSensor(int type, String name, IVirtualDevice virtualDevice, IBinder token) {
mType = type;
mName = name;
mVirtualDevice = virtualDevice;
mToken = token;
}
/**
* Returns the
* <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
*/
public int getType() {
return mType;
}
/**
* Returns the name of the sensor.
*/
@NonNull
public String getName() {
return mName;
}
/**
* Send a sensor event to the system.
*/
@RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
public void sendSensorEvent(@NonNull VirtualSensorEvent event) {
try {
mVirtualDevice.sendSensorEvent(mToken, event);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}

View File

@@ -0,0 +1,19 @@
/*
* 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 android.companion.virtual.sensor;
parcelable VirtualSensorConfig;

View File

@@ -0,0 +1,210 @@
/*
* 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 android.companion.virtual.sensor;
import static java.util.concurrent.TimeUnit.MICROSECONDS;
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
* Configuration for creation of a virtual sensor.
* @see VirtualSensor
* @hide
*/
@SystemApi
public final class VirtualSensorConfig implements Parcelable {
private final int mType;
@NonNull
private final String mName;
@Nullable
private final String mVendor;
@Nullable
private final IVirtualSensorStateChangeCallback mStateChangeCallback;
private VirtualSensorConfig(int type, @NonNull String name, @Nullable String vendor,
@Nullable IVirtualSensorStateChangeCallback stateChangeCallback) {
mType = type;
mName = name;
mVendor = vendor;
mStateChangeCallback = stateChangeCallback;
}
private VirtualSensorConfig(@NonNull Parcel parcel) {
mType = parcel.readInt();
mName = parcel.readString8();
mVendor = parcel.readString8();
mStateChangeCallback =
IVirtualSensorStateChangeCallback.Stub.asInterface(parcel.readStrongBinder());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel parcel, int flags) {
parcel.writeInt(mType);
parcel.writeString8(mName);
parcel.writeString8(mVendor);
parcel.writeStrongBinder(
mStateChangeCallback != null ? mStateChangeCallback.asBinder() : null);
}
/**
* Returns the
* <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
*/
public int getType() {
return mType;
}
/**
* Returns the name of the sensor, which must be unique per sensor type for each virtual device.
*/
@NonNull
public String getName() {
return mName;
}
/**
* Returns the vendor string of the sensor.
* @see Builder#setVendor
*/
@Nullable
public String getVendor() {
return mVendor;
}
/**
* Returns the callback to get notified about changes in the sensor listeners.
* @hide
*/
@Nullable
public IVirtualSensorStateChangeCallback getStateChangeCallback() {
return mStateChangeCallback;
}
/**
* Builder for {@link VirtualSensorConfig}.
*/
public static final class Builder {
private final int mType;
@NonNull
private final String mName;
@Nullable
private String mVendor;
@Nullable
private IVirtualSensorStateChangeCallback mStateChangeCallback;
private static class SensorStateChangeCallbackDelegate
extends IVirtualSensorStateChangeCallback.Stub {
@NonNull
private final Executor mExecutor;
@NonNull
private final VirtualSensor.SensorStateChangeCallback mCallback;
SensorStateChangeCallbackDelegate(@NonNull @CallbackExecutor Executor executor,
@NonNull VirtualSensor.SensorStateChangeCallback callback) {
mCallback = callback;
mExecutor = executor;
}
@Override
public void onStateChanged(boolean enabled, int samplingPeriodMicros,
int batchReportLatencyMicros) {
final Duration samplingPeriod =
Duration.ofNanos(MICROSECONDS.toNanos(samplingPeriodMicros));
final Duration batchReportingLatency =
Duration.ofNanos(MICROSECONDS.toNanos(batchReportLatencyMicros));
mExecutor.execute(() -> mCallback.onStateChanged(
enabled, samplingPeriod, batchReportingLatency));
}
}
/**
* Creates a new builder.
*
* @param type The
* <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
* @param name The name of the sensor. Must be unique among all sensors with the same type
* that belong to the same virtual device.
*/
public Builder(int type, @NonNull String name) {
mType = type;
mName = Objects.requireNonNull(name);
}
/**
* Creates a new {@link VirtualSensorConfig}.
*/
@NonNull
public VirtualSensorConfig build() {
return new VirtualSensorConfig(mType, mName, mVendor, mStateChangeCallback);
}
/**
* Sets the vendor string of the sensor.
*/
@NonNull
public VirtualSensorConfig.Builder setVendor(@Nullable String vendor) {
mVendor = vendor;
return this;
}
/**
* Sets the callback to get notified about changes in the sensor listeners.
*
* @param executor The executor where the callback is executed on.
* @param callback The callback to get notified when the state of the sensor
* listeners has changed, see {@link VirtualSensor.SensorStateChangeCallback}
*/
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public VirtualSensorConfig.Builder setStateChangeCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull VirtualSensor.SensorStateChangeCallback callback) {
mStateChangeCallback = new SensorStateChangeCallbackDelegate(
Objects.requireNonNull(executor),
Objects.requireNonNull(callback));
return this;
}
}
@NonNull
public static final Parcelable.Creator<VirtualSensorConfig> CREATOR =
new Parcelable.Creator<>() {
public VirtualSensorConfig createFromParcel(Parcel source) {
return new VirtualSensorConfig(source);
}
public VirtualSensorConfig[] newArray(int size) {
return new VirtualSensorConfig[size];
}
};
}

View File

@@ -0,0 +1,19 @@
/*
* 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 android.companion.virtual.sensor;
parcelable VirtualSensorEvent;

View File

@@ -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 android.companion.virtual.sensor;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
/**
* A sensor event that originated from a virtual device's sensor.
*
* @hide
*/
@SystemApi
public final class VirtualSensorEvent implements Parcelable {
@NonNull
private float[] mValues;
private long mTimestampNanos;
private VirtualSensorEvent(@NonNull float[] values, long timestampNanos) {
mValues = values;
mTimestampNanos = timestampNanos;
}
private VirtualSensorEvent(@NonNull Parcel parcel) {
final int valuesLength = parcel.readInt();
mValues = new float[valuesLength];
parcel.readFloatArray(mValues);
mTimestampNanos = parcel.readLong();
}
@Override
public void writeToParcel(@NonNull Parcel parcel, int parcelableFlags) {
parcel.writeInt(mValues.length);
parcel.writeFloatArray(mValues);
parcel.writeLong(mTimestampNanos);
}
@Override
public int describeContents() {
return 0;
}
/**
* Returns the values of this sensor event. The length and contents depend on the
* <a href="https://source.android.com/devices/sensors/sensor-types">sensor type</a>.
* @see android.hardware.SensorEvent#values
*/
@NonNull
public float[] getValues() {
return mValues;
}
/**
* The time in nanoseconds at which the event happened. For a given sensor, each new sensor
* event should be monotonically increasing.
*
* @see Builder#setTimestampNanos(long)
*/
public long getTimestampNanos() {
return mTimestampNanos;
}
/**
* Builder for {@link VirtualSensorEvent}.
*/
public static final class Builder {
@NonNull
private float[] mValues;
private long mTimestampNanos = 0;
/**
* Creates a new builder.
* @param values the values of the sensor event. @see android.hardware.SensorEvent#values
*/
public Builder(@NonNull float[] values) {
mValues = values;
}
/**
* Creates a new {@link VirtualSensorEvent}.
*/
@NonNull
public VirtualSensorEvent build() {
if (mValues == null || mValues.length == 0) {
throw new IllegalArgumentException(
"Cannot build virtual sensor event with no values.");
}
if (mTimestampNanos <= 0) {
mTimestampNanos = SystemClock.elapsedRealtimeNanos();
}
return new VirtualSensorEvent(mValues, mTimestampNanos);
}
/**
* Sets the timestamp of this event. For a given sensor, each new sensor event should be
* monotonically increasing using the same time base as
* {@link android.os.SystemClock#elapsedRealtimeNanos()}.
*
* If not explicitly set, the current timestamp is used for the sensor event.
*
* @see android.hardware.SensorEvent#timestamp
*/
@NonNull
public Builder setTimestampNanos(long timestampNanos) {
mTimestampNanos = timestampNanos;
return this;
}
}
public static final @NonNull Parcelable.Creator<VirtualSensorEvent> CREATOR =
new Parcelable.Creator<>() {
public VirtualSensorEvent createFromParcel(Parcel source) {
return new VirtualSensorEvent(source);
}
public VirtualSensorEvent[] newArray(int size) {
return new VirtualSensorEvent[size];
}
};
}

View File

@@ -16,8 +16,14 @@
package android.hardware;
import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -45,6 +51,7 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -80,6 +87,8 @@ public class SystemSensorManager extends SensorManager {
private static native boolean nativeGetSensorAtIndex(long nativeInstance,
Sensor sensor, int index);
private static native void nativeGetDynamicSensors(long nativeInstance, List<Sensor> list);
private static native void nativeGetRuntimeSensors(
long nativeInstance, int deviceId, List<Sensor> list);
private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
private static native int nativeCreateDirectChannel(
@@ -100,6 +109,10 @@ public class SystemSensorManager extends SensorManager {
private final ArrayList<Sensor> mFullSensorsList = new ArrayList<>();
private List<Sensor> mFullDynamicSensorsList = new ArrayList<>();
private final SparseArray<List<Sensor>> mFullRuntimeSensorListByDevice = new SparseArray<>();
private final SparseArray<SparseArray<List<Sensor>>> mRuntimeSensorListByDeviceByType =
new SparseArray<>();
private boolean mDynamicSensorListDirty = true;
private final HashMap<Integer, Sensor> mHandleToSensor = new HashMap<>();
@@ -114,6 +127,7 @@ public class SystemSensorManager extends SensorManager {
private HashMap<DynamicSensorCallback, Handler>
mDynamicSensorCallbacks = new HashMap<>();
private BroadcastReceiver mDynamicSensorBroadcastReceiver;
private BroadcastReceiver mRuntimeSensorBroadcastReceiver;
// Looper associated with the context in which this instance was created.
private final Looper mMainLooper;
@@ -121,6 +135,7 @@ public class SystemSensorManager extends SensorManager {
private final boolean mIsPackageDebuggable;
private final Context mContext;
private final long mNativeInstance;
private final VirtualDeviceManager mVdm;
private Optional<Boolean> mHasHighSamplingRateSensorsPermission = Optional.empty();
@@ -139,6 +154,7 @@ public class SystemSensorManager extends SensorManager {
mContext = context;
mNativeInstance = nativeCreate(context.getOpPackageName());
mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
mVdm = mContext.getSystemService(VirtualDeviceManager.class);
// initialize the sensor list
for (int index = 0;; ++index) {
@@ -147,12 +163,63 @@ public class SystemSensorManager extends SensorManager {
mFullSensorsList.add(sensor);
mHandleToSensor.put(sensor.getHandle(), sensor);
}
}
/** @hide */
@Override
public List<Sensor> getSensorList(int type) {
final int deviceId = mContext.getDeviceId();
if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
|| mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
return super.getSensorList(type);
}
// Cache the per-device lists on demand.
List<Sensor> list;
synchronized (mFullRuntimeSensorListByDevice) {
List<Sensor> fullList = mFullRuntimeSensorListByDevice.get(deviceId);
if (fullList == null) {
fullList = createRuntimeSensorListLocked(deviceId);
}
SparseArray<List<Sensor>> deviceSensorListByType =
mRuntimeSensorListByDeviceByType.get(deviceId);
list = deviceSensorListByType.get(type);
if (list == null) {
if (type == Sensor.TYPE_ALL) {
list = fullList;
} else {
list = new ArrayList<>();
for (Sensor i : fullList) {
if (i.getType() == type) {
list.add(i);
}
}
}
list = Collections.unmodifiableList(list);
deviceSensorListByType.append(type, list);
}
}
return list;
}
/** @hide */
@Override
protected List<Sensor> getFullSensorList() {
return mFullSensorsList;
final int deviceId = mContext.getDeviceId();
if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
|| mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
return mFullSensorsList;
}
List<Sensor> fullList;
synchronized (mFullRuntimeSensorListByDevice) {
fullList = mFullRuntimeSensorListByDevice.get(deviceId);
if (fullList == null) {
fullList = createRuntimeSensorListLocked(deviceId);
}
}
return fullList;
}
/** @hide */
@@ -446,12 +513,53 @@ public class SystemSensorManager extends SensorManager {
}
}
private List<Sensor> createRuntimeSensorListLocked(int deviceId) {
setupRuntimeSensorBroadcastReceiver();
List<Sensor> list = new ArrayList<>();
nativeGetRuntimeSensors(mNativeInstance, deviceId, list);
mFullRuntimeSensorListByDevice.put(deviceId, list);
mRuntimeSensorListByDeviceByType.put(deviceId, new SparseArray<>());
for (Sensor s : list) {
mHandleToSensor.put(s.getHandle(), s);
}
return list;
}
private void setupRuntimeSensorBroadcastReceiver() {
if (mRuntimeSensorBroadcastReceiver == null) {
mRuntimeSensorBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) {
synchronized (mFullRuntimeSensorListByDevice) {
final int deviceId = intent.getIntExtra(
EXTRA_VIRTUAL_DEVICE_ID, DEFAULT_DEVICE_ID);
List<Sensor> removedSensors =
mFullRuntimeSensorListByDevice.removeReturnOld(deviceId);
if (removedSensors != null) {
for (Sensor s : removedSensors) {
cleanupSensorConnection(s);
}
}
mRuntimeSensorListByDeviceByType.remove(deviceId);
}
}
}
};
IntentFilter filter = new IntentFilter("virtual_device_removed");
filter.addAction(ACTION_VIRTUAL_DEVICE_REMOVED);
mContext.registerReceiver(mRuntimeSensorBroadcastReceiver, filter,
Context.RECEIVER_NOT_EXPORTED);
}
}
private void setupDynamicSensorBroadcastReceiver() {
if (mDynamicSensorBroadcastReceiver == null) {
mDynamicSensorBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() == Intent.ACTION_DYNAMIC_SENSOR_CHANGED) {
if (intent.getAction().equals(Intent.ACTION_DYNAMIC_SENSOR_CHANGED)) {
if (DEBUG_DYNAMIC_SENSOR) {
Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANED broadcast");
}

View File

@@ -243,6 +243,23 @@ nativeGetDynamicSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jobject
}
}
static void nativeGetRuntimeSensors(JNIEnv *env, jclass clazz, jlong sensorManager, jint deviceId,
jobject sensorList) {
SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
const ListOffsets &listOffsets(gListOffsets);
Vector<Sensor> nativeList;
mgr->getRuntimeSensorList(deviceId, nativeList);
ALOGI("DYNS native SensorManager.getRuntimeSensorList return %zu sensors", nativeList.size());
for (size_t i = 0; i < nativeList.size(); ++i) {
jobject sensor = translateNativeSensorToJavaSensor(env, NULL, nativeList[i]);
// add to list
env->CallBooleanMethod(sensorList, listOffsets.add, sensor);
}
}
static jboolean nativeIsDataInjectionEnabled(JNIEnv *_env, jclass _this, jlong sensorManager) {
SensorManager* mgr = reinterpret_cast<SensorManager*>(sensorManager);
return mgr->isDataInjectionEnabled();
@@ -503,40 +520,26 @@ static jint nativeInjectSensorData(JNIEnv *env, jclass clazz, jlong eventQ, jint
//----------------------------------------------------------------------------
static const JNINativeMethod gSystemSensorManagerMethods[] = {
{"nativeClassInit",
"()V",
(void*)nativeClassInit },
{"nativeCreate",
"(Ljava/lang/String;)J",
(void*)nativeCreate },
{"nativeClassInit", "()V", (void *)nativeClassInit},
{"nativeCreate", "(Ljava/lang/String;)J", (void *)nativeCreate},
{"nativeGetSensorAtIndex",
"(JLandroid/hardware/Sensor;I)Z",
(void*)nativeGetSensorAtIndex },
{"nativeGetSensorAtIndex", "(JLandroid/hardware/Sensor;I)Z",
(void *)nativeGetSensorAtIndex},
{"nativeGetDynamicSensors",
"(JLjava/util/List;)V",
(void*)nativeGetDynamicSensors },
{"nativeGetDynamicSensors", "(JLjava/util/List;)V", (void *)nativeGetDynamicSensors},
{"nativeIsDataInjectionEnabled",
"(J)Z",
(void*)nativeIsDataInjectionEnabled },
{"nativeGetRuntimeSensors", "(JILjava/util/List;)V", (void *)nativeGetRuntimeSensors},
{"nativeCreateDirectChannel",
"(JJIILandroid/hardware/HardwareBuffer;)I",
(void*)nativeCreateDirectChannel },
{"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
{"nativeDestroyDirectChannel",
"(JI)V",
(void*)nativeDestroyDirectChannel },
{"nativeCreateDirectChannel", "(JJIILandroid/hardware/HardwareBuffer;)I",
(void *)nativeCreateDirectChannel},
{"nativeConfigDirectChannel",
"(JIII)I",
(void*)nativeConfigDirectChannel },
{"nativeDestroyDirectChannel", "(JI)V", (void *)nativeDestroyDirectChannel},
{"nativeSetOperationParameter",
"(JII[F[I)I",
(void*)nativeSetOperationParameter },
{"nativeConfigDirectChannel", "(JIII)I", (void *)nativeConfigDirectChannel},
{"nativeSetOperationParameter", "(JII[F[I)I", (void *)nativeSetOperationParameter},
};
static const JNINativeMethod gBaseEventQueueMethods[] = {

View File

@@ -0,0 +1,104 @@
/*
* 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 android.companion.virtual.sensor;
import static android.hardware.Sensor.TYPE_ACCELEROMETER;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.os.Parcel;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.os.BackgroundThread;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.time.Duration;
@RunWith(AndroidJUnit4.class)
public class VirtualSensorConfigTest {
private static final String SENSOR_NAME = "VirtualSensorName";
private static final String SENSOR_VENDOR = "VirtualSensorVendor";
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@Mock
private VirtualSensor.SensorStateChangeCallback mSensorCallback;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
@Test
public void parcelAndUnparcel_matches() {
final VirtualSensorConfig originalConfig =
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
.setVendor(SENSOR_VENDOR)
.setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
.build();
final Parcel parcel = Parcel.obtain();
originalConfig.writeToParcel(parcel, /* flags= */ 0);
parcel.setDataPosition(0);
final VirtualSensorConfig recreatedConfig =
VirtualSensorConfig.CREATOR.createFromParcel(parcel);
assertThat(recreatedConfig.getType()).isEqualTo(originalConfig.getType());
assertThat(recreatedConfig.getName()).isEqualTo(originalConfig.getName());
assertThat(recreatedConfig.getVendor()).isEqualTo(originalConfig.getVendor());
assertThat(recreatedConfig.getStateChangeCallback()).isNotNull();
}
@Test
public void sensorConfig_onlyRequiredFields() {
final VirtualSensorConfig config =
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME).build();
assertThat(config.getVendor()).isNull();
assertThat(config.getStateChangeCallback()).isNull();
}
@Test
public void sensorConfig_sensorCallbackInvocation() throws Exception {
final VirtualSensorConfig config =
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
.setStateChangeCallback(BackgroundThread.getExecutor(), mSensorCallback)
.build();
final Duration samplingPeriod = Duration.ofMillis(123);
final Duration batchLatency = Duration.ofMillis(456);
config.getStateChangeCallback().onStateChanged(true,
(int) MILLISECONDS.toMicros(samplingPeriod.toMillis()),
(int) MILLISECONDS.toMicros(batchLatency.toMillis()));
verify(mSensorCallback, timeout(1000)).onStateChanged(true, samplingPeriod, batchLatency);
}
}

View File

@@ -0,0 +1,80 @@
/*
* 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 android.companion.virtual.sensor;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.assertThrows;
import android.os.Parcel;
import android.os.SystemClock;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class VirtualSensorEventTest {
private static final long TIMESTAMP_NANOS = SystemClock.elapsedRealtimeNanos();
private static final float[] SENSOR_VALUES = new float[] {1.2f, 3.4f, 5.6f};
@Test
public void parcelAndUnparcel_matches() {
final VirtualSensorEvent originalEvent = new VirtualSensorEvent.Builder(SENSOR_VALUES)
.setTimestampNanos(TIMESTAMP_NANOS)
.build();
final Parcel parcel = Parcel.obtain();
originalEvent.writeToParcel(parcel, /* flags= */ 0);
parcel.setDataPosition(0);
final VirtualSensorEvent recreatedEvent =
VirtualSensorEvent.CREATOR.createFromParcel(parcel);
assertThat(recreatedEvent.getValues()).isEqualTo(originalEvent.getValues());
assertThat(recreatedEvent.getTimestampNanos()).isEqualTo(originalEvent.getTimestampNanos());
}
@Test
public void sensorEvent_nullValues() {
assertThrows(
IllegalArgumentException.class, () -> new VirtualSensorEvent.Builder(null).build());
}
@Test
public void sensorEvent_noValues() {
assertThrows(
IllegalArgumentException.class,
() -> new VirtualSensorEvent.Builder(new float[0]).build());
}
@Test
public void sensorEvent_noTimestamp_usesCurrentTime() {
final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES).build();
assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
assertThat(TIMESTAMP_NANOS).isLessThan(event.getTimestampNanos());
assertThat(event.getTimestampNanos()).isLessThan(SystemClock.elapsedRealtimeNanos());
}
@Test
public void sensorEvent_created() {
final VirtualSensorEvent event = new VirtualSensorEvent.Builder(SENSOR_VALUES)
.setTimestampNanos(TIMESTAMP_NANOS)
.build();
assertThat(event.getTimestampNanos()).isEqualTo(TIMESTAMP_NANOS);
assertThat(event.getValues()).isEqualTo(SENSOR_VALUES);
}
}

View File

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

View File

@@ -38,6 +38,8 @@ import android.companion.virtual.VirtualDeviceManager.ActivityListener;
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.companion.virtual.sensor.VirtualSensorEvent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -76,6 +78,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -98,6 +101,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<>();
@@ -160,6 +164,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
ownerUid,
deviceId,
/* inputController= */ null,
/* sensorController= */ null,
listener,
pendingTrampolineCallback,
activityListener,
@@ -175,6 +180,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
int ownerUid,
int deviceId,
InputController inputController,
SensorController sensorController,
OnDeviceCloseListener listener,
PendingTrampolineCallback pendingTrampolineCallback,
IVirtualDeviceActivityListener activityListener,
@@ -198,6 +204,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);
@@ -319,11 +330,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);
}
}
@@ -403,12 +415,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);
}
}
@@ -430,12 +442,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);
}
}
@@ -457,11 +469,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);
}
}
@@ -491,12 +503,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);
}
}
@@ -506,92 +518,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);
}
}
@@ -601,7 +613,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;
@@ -610,7 +622,50 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
}
} finally {
Binder.restoreCallingIdentity(binderToken);
Binder.restoreCallingIdentity(ident);
}
}
@Override // Binder call
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(@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(@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);
}
}
@@ -627,6 +682,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
fout.println(" mDefaultShowPointerIcon: " + mDefaultShowPointerIcon);
}
mInputController.dump(fout);
mSensorController.dump(fout);
}
GenericWindowPolicyController createWindowPolicyController(

View File

@@ -32,6 +32,7 @@ import android.companion.virtual.VirtualDevice;
import android.companion.virtual.VirtualDeviceManager;
import android.companion.virtual.VirtualDeviceParams;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
@@ -280,7 +281,22 @@ public class VirtualDeviceManagerService extends SystemService {
@Override
public void onClose(int associationId) {
synchronized (mVirtualDeviceManagerLock) {
mVirtualDevices.remove(associationId);
VirtualDeviceImpl removedDevice =
mVirtualDevices.removeReturnOld(associationId);
if (removedDevice != null) {
Intent i = new Intent(
VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED);
i.putExtra(
VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID,
removedDevice.getDeviceId());
i.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
final long identity = Binder.clearCallingIdentity();
try {
getContext().sendBroadcastAsUser(i, UserHandle.ALL);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
mAppsOnVirtualDevices.remove(associationId);
if (cameraAccessController != null) {
cameraAccessController.stopObservingIfNeeded();

View File

@@ -42,6 +42,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.
*/
@@ -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);
}
}

View File

@@ -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);
@@ -84,6 +94,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) {

View File

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

View File

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

View File

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

View File

@@ -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;
@@ -126,16 +129,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;
@@ -150,6 +156,8 @@ public class VirtualDeviceManagerServiceTest {
@Mock
private InputManagerInternal mInputManagerInternalMock;
@Mock
private SensorManagerInternal mSensorManagerInternalMock;
@Mock
private IVirtualDeviceActivityListener mActivityListener;
@Mock
private Consumer<ArraySet<Integer>> mRunningAppsChangedCallback;
@@ -228,6 +236,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());
@@ -252,6 +263,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);
@@ -264,9 +276,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);
}
@@ -308,9 +320,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(
@@ -575,6 +587,18 @@ public class VirtualDeviceManagerServiceTest {
VENDOR_ID, PRODUCT_ID, BINDER, new Point(WIDTH, HEIGHT)));
}
@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);
@@ -678,6 +702,17 @@ public class VirtualDeviceManagerServiceTest {
assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNull();
}
@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(

View File

@@ -16,9 +16,14 @@
package com.android.server.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.hardware.Sensor.TYPE_ACCELEROMETER;
import static com.google.common.truth.Truth.assertThat;
import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.sensor.VirtualSensorConfig;
import android.os.Parcel;
import android.os.UserHandle;
@@ -27,18 +32,25 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import java.util.Set;
@RunWith(AndroidJUnit4.class)
public class VirtualDeviceParamsTest {
private static final String SENSOR_NAME = "VirtualSensorName";
private static final String SENSOR_VENDOR = "VirtualSensorVendor";
@Test
public void parcelable_shouldRecreateSuccessfully() {
VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
.setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
.setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
.addDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS,
VirtualDeviceParams.DEVICE_POLICY_CUSTOM)
.addDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
.addVirtualSensorConfig(
new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
.setVendor(SENSOR_VENDOR)
.build())
.build();
Parcel parcel = Parcel.obtain();
originalParams.writeToParcel(parcel, 0);
@@ -49,7 +61,14 @@ public class VirtualDeviceParamsTest {
assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
assertThat(params.getUsersWithMatchingAccounts())
.containsExactly(UserHandle.of(123), UserHandle.of(456));
assertThat(params.getDevicePolicy(VirtualDeviceParams.POLICY_TYPE_SENSORS))
.isEqualTo(VirtualDeviceParams.DEVICE_POLICY_CUSTOM);
assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM);
List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs();
assertThat(sensorConfigs).hasSize(1);
VirtualSensorConfig sensorConfig = sensorConfigs.get(0);
assertThat(sensorConfig.getType()).isEqualTo(TYPE_ACCELEROMETER);
assertThat(sensorConfig.getName()).isEqualTo(SENSOR_NAME);
assertThat(sensorConfig.getVendor()).isEqualTo(SENSOR_VENDOR);
assertThat(sensorConfig.getStateChangeCallback()).isNull();
}
}