From 877f52c27a1883e334e715a36e22ab553a14e643 Mon Sep 17 00:00:00 2001 From: Vladimir Komsiyski Date: Fri, 21 Oct 2022 13:37:41 +0200 Subject: [PATCH] Allow virtual device owners to specify custom policies per policy type. The policy per device and per type is available to the system components via both VirtualDeviceManager and VirtualDeviceManagerInternal. Currently only adding SENSORS polcy type. Bug: 239152561 Bug: 237278244 Test: atest VirtualDeviceParamsTest, atest VirtualDeviceManagerServiceTest Change-Id: Id32997d716653306abbff5e9dacf600a0ee9f3e4 --- core/api/system-current.txt | 5 + .../virtual/IVirtualDeviceManager.aidl | 5 + .../virtual/VirtualDeviceManager.java | 22 +++++ .../virtual/VirtualDeviceParams.java | 99 ++++++++++++++++++- .../companion/virtual/VirtualDeviceImpl.java | 6 ++ .../virtual/VirtualDeviceManagerService.java | 27 +++++ .../virtual/VirtualDeviceManagerInternal.java | 11 +++ .../VirtualDeviceManagerServiceTest.java | 53 ++++++++++ .../virtual/VirtualDeviceParamsTest.java | 4 + 9 files changed, 228 insertions(+), 4 deletions(-) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index c170f74b41c38..5b5d51d328c5f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2832,6 +2832,7 @@ package android.companion.virtual { method @NonNull public java.util.Set getBlockedCrossTaskNavigations(); method public int getDefaultActivityPolicy(); method public int getDefaultNavigationPolicy(); + method public int getDevicePolicy(int); method public int getLockState(); method @Nullable public String getName(); method @NonNull public java.util.Set getUsersWithMatchingAccounts(); @@ -2839,14 +2840,18 @@ package android.companion.virtual { field public static final int ACTIVITY_POLICY_DEFAULT_ALLOWED = 0; // 0x0 field public static final int ACTIVITY_POLICY_DEFAULT_BLOCKED = 1; // 0x1 field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int DEVICE_POLICY_CUSTOM = 1; // 0x1 + field public static final int DEVICE_POLICY_DEFAULT = 0; // 0x0 field public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; // 0x1 field public static final int LOCK_STATE_DEFAULT = 0; // 0x0 field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0 field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1 + field public static final int POLICY_TYPE_SENSORS = 0; // 0x0 } 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 build(); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set); diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index 82d7534c84d93..7d6336a225bd5 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -51,6 +51,11 @@ interface IVirtualDeviceManager { */ List getVirtualDevices(); + /** + * Returns the device policy for the given virtual device and policy type. + */ + int getDevicePolicy(int deviceId, int policyType); + /** * Creates a virtual display owned by a particular virtual device. * diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 0bb86fbf00f89..c14bb1beb0252 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -181,6 +181,28 @@ public final class VirtualDeviceManager { } } + /** + * Returns the device policy for the given virtual device and policy type. + * + *

In case the virtual device identifier is not valid, or there's no explicitly specified + * policy for that device and policy type, then + * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned. + * + * @hide + */ + public @VirtualDeviceParams.DevicePolicy int getDevicePolicy( + int deviceId, @VirtualDeviceParams.PolicyType int policyType) { + if (mService == null) { + Log.w(TAG, "Failed to retrieve device policy; no virtual device manager service."); + return VirtualDeviceParams.DEVICE_POLICY_DEFAULT; + } + try { + return mService.getDevicePolicy(deviceId, policyType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * A virtual device has its own virtual display, audio output, microphone, and camera etc. The * creator of a virtual device can take the output from the virtual display and stream it over diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index d40c9d63039dd..c6e6f8324cff9 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -28,6 +28,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; import android.util.ArraySet; +import android.util.SparseIntArray; import com.android.internal.util.Preconditions; @@ -103,6 +104,47 @@ public final class VirtualDeviceParams implements Parcelable { */ public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; + /** @hide */ + @IntDef(prefix = "DEVICE_POLICY_", value = {DEVICE_POLICY_DEFAULT, DEVICE_POLICY_CUSTOM}) + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface DevicePolicy {} + + /** + * Indicates that there is no special logic for this virtual device and it should be treated + * the same way as the default device, keeping the default behavior unchanged. + */ + public static final int DEVICE_POLICY_DEFAULT = 0; + + /** + * Indicates that there is custom logic, specific to this virtual device, which should be + * triggered instead of the default behavior. + */ + public static final int DEVICE_POLICY_CUSTOM = 1; + + /** + * Any relevant component must be able to interpret the correct meaning of a custom policy for + * a given policy type. + * @hide + */ + @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS}) + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface PolicyType {} + + /** + * Tells the sensor framework how to handle sensor requests from contexts associated with this + * virtual device, namely the sensors returned by + * {@link android.hardware.SensorManager#getSensorList}: + * + *

    + *
  • {@link #DEVICE_POLICY_DEFAULT}: Return the sensors of the default device. + *
  • {@link #DEVICE_POLICY_CUSTOM}: Return the sensors of the virtual device. Note that if + * the virtual device did not create any virtual sensors, then an empty list is returned. + *
+ */ + public static final int POLICY_TYPE_SENSORS = 0; + private final int mLockState; @NonNull private final ArraySet mUsersWithMatchingAccounts; @NonNull private final ArraySet mAllowedCrossTaskNavigations; @@ -114,6 +156,8 @@ public final class VirtualDeviceParams implements Parcelable { @ActivityPolicy private final int mDefaultActivityPolicy; @Nullable private final String mName; + // Mapping of @PolicyType to @DevicePolicy + @NonNull private final SparseIntArray mDevicePolicies; private VirtualDeviceParams( @LockState int lockState, @@ -124,12 +168,14 @@ public final class VirtualDeviceParams implements Parcelable { @NonNull Set allowedActivities, @NonNull Set blockedActivities, @ActivityPolicy int defaultActivityPolicy, - @Nullable String name) { + @Nullable String name, + @NonNull SparseIntArray devicePolicies) { Preconditions.checkNotNull(usersWithMatchingAccounts); Preconditions.checkNotNull(allowedCrossTaskNavigations); Preconditions.checkNotNull(blockedCrossTaskNavigations); Preconditions.checkNotNull(allowedActivities); Preconditions.checkNotNull(blockedActivities); + Preconditions.checkNotNull(devicePolicies); mLockState = lockState; mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts); @@ -140,6 +186,7 @@ public final class VirtualDeviceParams implements Parcelable { mBlockedActivities = new ArraySet<>(blockedActivities); mDefaultActivityPolicy = defaultActivityPolicy; mName = name; + mDevicePolicies = devicePolicies; } @SuppressWarnings("unchecked") @@ -153,6 +200,7 @@ public final class VirtualDeviceParams implements Parcelable { mBlockedActivities = (ArraySet) parcel.readArraySet(null); mDefaultActivityPolicy = parcel.readInt(); mName = parcel.readString8(); + mDevicePolicies = parcel.readSparseIntArray(); } /** @@ -258,6 +306,16 @@ public final class VirtualDeviceParams implements Parcelable { return mName; } + /** + * Returns the policy specified for this policy type, or {@link #DEVICE_POLICY_DEFAULT} if no + * policy for this type has been explicitly specified. + * + * @see Builder#addDevicePolicy + */ + public @DevicePolicy int getDevicePolicy(@PolicyType int policyType) { + return mDevicePolicies.get(policyType, DEVICE_POLICY_DEFAULT); + } + @Override public int describeContents() { return 0; @@ -274,6 +332,7 @@ public final class VirtualDeviceParams implements Parcelable { dest.writeArraySet(mBlockedActivities); dest.writeInt(mDefaultActivityPolicy); dest.writeString8(mName); + dest.writeSparseIntArray(mDevicePolicies); } @Override @@ -285,6 +344,18 @@ public final class VirtualDeviceParams implements Parcelable { return false; } VirtualDeviceParams that = (VirtualDeviceParams) o; + final int devicePoliciesCount = mDevicePolicies.size(); + if (devicePoliciesCount != that.mDevicePolicies.size()) { + return false; + } + for (int i = 0; i < devicePoliciesCount; i++) { + if (mDevicePolicies.keyAt(i) != that.mDevicePolicies.keyAt(i)) { + return false; + } + if (mDevicePolicies.valueAt(i) != that.mDevicePolicies.valueAt(i)) { + return false; + } + } return mLockState == that.mLockState && mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts) && Objects.equals(mAllowedCrossTaskNavigations, that.mAllowedCrossTaskNavigations) @@ -298,10 +369,15 @@ public final class VirtualDeviceParams implements Parcelable { @Override public int hashCode() { - return Objects.hash( + int hashCode = Objects.hash( mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations, mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities, - mBlockedActivities, mDefaultActivityPolicy, mName); + mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies); + for (int i = 0; i < mDevicePolicies.size(); i++) { + hashCode = 31 * hashCode + mDevicePolicies.keyAt(i); + hashCode = 31 * hashCode + mDevicePolicies.valueAt(i); + } + return hashCode; } @Override @@ -317,6 +393,7 @@ public final class VirtualDeviceParams implements Parcelable { + " mBlockedActivities=" + mBlockedActivities + " mDefaultActivityPolicy=" + mDefaultActivityPolicy + " mName=" + mName + + " mDevicePolicies=" + mDevicePolicies + ")"; } @@ -350,6 +427,7 @@ public final class VirtualDeviceParams implements Parcelable { private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED; private boolean mDefaultActivityPolicyConfigured = false; @Nullable private String mName; + @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray(); /** * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} @@ -527,6 +605,18 @@ public final class VirtualDeviceParams implements Parcelable { return this; } + /** + * Specifies a policy for this virtual device. + * + * @param policyType the type of policy, i.e. which behavior to specify a policy for. + * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior. + */ + @NonNull + public Builder addDevicePolicy(@PolicyType int policyType, @DevicePolicy int devicePolicy) { + mDevicePolicies.put(policyType, devicePolicy); + return this; + } + /** * Builds the {@link VirtualDeviceParams} instance. */ @@ -541,7 +631,8 @@ public final class VirtualDeviceParams implements Parcelable { mAllowedActivities, mBlockedActivities, mDefaultActivityPolicy, - mName); + mName, + mDevicePolicies); } } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 5ebbf07526f1f..8d99edf1bf3df 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -227,6 +227,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub return mParams.getName(); } + /** Returns the policy specified for this policy type */ + public @VirtualDeviceParams.DevicePolicy int getDevicePolicy( + @VirtualDeviceParams.PolicyType int policyType) { + return mParams.getDevicePolicy(policyType); + } + /** Returns the unique device ID of this device. */ @Override // Binder call public int getDeviceId() { diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index c400a74da4ced..a8797a05ed240 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -233,6 +233,13 @@ public class VirtualDeviceManagerService extends SystemService { mLocalService.onAppsOnVirtualDeviceChanged(); } + @VisibleForTesting + void addVirtualDevice(VirtualDeviceImpl virtualDevice) { + synchronized (mVirtualDeviceManagerLock) { + mVirtualDevices.put(virtualDevice.getAssociationId(), virtualDevice); + } + } + class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub implements VirtualDeviceImpl.PendingTrampolineCallback { @@ -358,6 +365,12 @@ public class VirtualDeviceManagerService extends SystemService { return virtualDevices; } + @Override // BinderCall + @VirtualDeviceParams.DevicePolicy + public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) { + return mLocalService.getDevicePolicy(deviceId, policyType); + } + @Nullable private AssociationInfo getAssociationInfo(String packageName, int associationId) { final int callingUserId = getCallingUserHandle().getIdentifier(); @@ -438,6 +451,20 @@ public class VirtualDeviceManagerService extends SystemService { } } + @Override + @VirtualDeviceParams.DevicePolicy + public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) { + synchronized (mVirtualDeviceManagerLock) { + for (int i = 0; i < mVirtualDevices.size(); i++) { + final VirtualDeviceImpl device = mVirtualDevices.valueAt(i); + if (device.getDeviceId() == deviceId) { + return device.getDevicePolicy(policyType); + } + } + } + return VirtualDeviceParams.DEVICE_POLICY_DEFAULT; + } + @Override public void onVirtualDisplayCreated(int displayId) { final VirtualDisplayListener[] listeners; diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index 81b56a3107380..d2e572f2f979f 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -18,6 +18,7 @@ package com.android.server.companion.virtual; import android.annotation.NonNull; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.VirtualDeviceParams; import java.util.Set; @@ -109,4 +110,14 @@ public abstract class VirtualDeviceManagerInternal { * Returns true if the {@code displayId} is owned by any virtual device */ public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId); + + /** + * Returns the device policy for the given virtual device and policy type. + * + *

In case the virtual device identifier is not valid, or there's no explicitly specified + * policy for that device and policy type, then + * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned. + */ + public abstract @VirtualDeviceParams.DevicePolicy int getDevicePolicy( + int deviceId, @VirtualDeviceParams.PolicyType int policyType); } diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 02bbe658f9b27..1dce97386ebc3 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -16,6 +16,9 @@ package com.android.server.companion.virtual; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM; +import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT; +import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS; import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES; import static com.google.common.truth.Truth.assertThat; @@ -44,6 +47,7 @@ import android.app.WindowConfiguration; import android.app.admin.DevicePolicyManager; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDeviceActivityListener; +import android.companion.virtual.VirtualDeviceManager; import android.companion.virtual.VirtualDeviceParams; import android.companion.virtual.audio.IAudioConfigChangedCallback; import android.companion.virtual.audio.IAudioRoutingCallback; @@ -240,6 +244,55 @@ public class VirtualDeviceManagerServiceTest { mAssociationInfo, new Binder(), /* ownerUid */ 0, /* uniqueId */ 1, mInputController, (int associationId) -> {}, mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params); + mVdms.addVirtualDevice(mDeviceImpl); + } + + @Test + public void getDevicePolicy_invalidDeviceId_returnsDefault() { + assertThat( + mLocalService.getDevicePolicy( + VirtualDeviceManager.INVALID_DEVICE_ID, POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); + } + + @Test + public void getDevicePolicy_defaultDeviceId_returnsDefault() { + assertThat( + mLocalService.getDevicePolicy( + VirtualDeviceManager.DEFAULT_DEVICE_ID, POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); + } + + @Test + public void getDevicePolicy_nonExistentDeviceId_returnsDefault() { + assertThat( + mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); + } + + @Test + public void getDevicePolicy_unspecifiedPolicy_returnsDefault() { + assertThat( + mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_DEFAULT); + } + + @Test + public void getDevicePolicy_returnsCustom() { + VirtualDeviceParams params = new VirtualDeviceParams + .Builder() + .setBlockedActivities(getBlockedActivities()) + .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); + mVdms.addVirtualDevice(mDeviceImpl); + + assertThat( + mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS)) + .isEqualTo(DEVICE_POLICY_CUSTOM); } @Test diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java index 77f1e24ee7716..036b6df92ef99 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java @@ -37,6 +37,8 @@ public class VirtualDeviceParamsTest { 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) .build(); Parcel parcel = Parcel.obtain(); originalParams.writeToParcel(parcel, 0); @@ -47,5 +49,7 @@ 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); } }