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:
committed by
Android (Google) Code Review
commit
79101e966f
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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[] = {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 { *; }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user