Implement VirtualDevice's device policy enforcement
The policy defined in DevicePolicyManager.getNearbyAppStreamingPolicy
is implemented in this change. The policy in each profile defines its
own behavior. For example, if a work profile sets the policy as ENABLED,
activities for the work profile can be started on virtual displays
created for the parent (personal) profile as well.
If the policy is SAME_MANAGED_ACCOUNT_ONLY, VirtualDeviceManager will
compare whether the UserHandle associated with the activity is listed as
"usersWithMatchingAccounts", which is supplied by the virtual device
owner.
If usersWithMatchingAccounts is not given, it is treated the same way as
an empty list, in which case SAME_MANAGED_ACCOUNT_ONLY policy has the
same behavior as DISABLED.
Bug: 179910177
Test: To be added in ag/15781910
Change-Id: I0d89aaafecdc579482a38ee8c0bff0cc300d4f41
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index ad2dea9..d61d474 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -23,12 +23,16 @@
import android.annotation.RequiresPermission;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.UserHandle;
+import android.util.ArraySet;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
+import java.util.Collections;
import java.util.Objects;
+import java.util.Set;
/**
* Params that can be configured when creating virtual devices.
@@ -60,20 +64,40 @@
public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1;
private final int mLockState;
+ private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
- private VirtualDeviceParams(@LockState int lockState) {
+ private VirtualDeviceParams(
+ @LockState int lockState,
+ @NonNull Set<UserHandle> usersWithMatchingAccounts) {
mLockState = lockState;
+ mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts);
}
+ @SuppressWarnings("unchecked")
private VirtualDeviceParams(Parcel parcel) {
mLockState = parcel.readInt();
+ mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null);
}
+ /**
+ * Returns the lock state of the virtual device.
+ */
@LockState
public int getLockState() {
return mLockState;
}
+ /**
+ * Returns the user handles with matching managed accounts on the remote device to which
+ * this virtual device is streaming.
+ *
+ * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
+ */
+ @NonNull
+ public Set<UserHandle> getUsersWithMatchingAccounts() {
+ return Collections.unmodifiableSet(mUsersWithMatchingAccounts);
+ }
+
@Override
public int describeContents() {
return 0;
@@ -82,6 +106,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mLockState);
+ dest.writeArraySet(mUsersWithMatchingAccounts);
}
@Override
@@ -93,18 +118,20 @@
return false;
}
VirtualDeviceParams that = (VirtualDeviceParams) o;
- return mLockState == that.mLockState;
+ return mLockState == that.mLockState && mUsersWithMatchingAccounts.equals(
+ that.mUsersWithMatchingAccounts);
}
@Override
public int hashCode() {
- return Objects.hash(mLockState);
+ return Objects.hash(mLockState, mUsersWithMatchingAccounts);
}
@Override
public String toString() {
return "VirtualDeviceParams("
+ " mLockState=" + mLockState
+ + " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts
+ ")";
}
@@ -125,6 +152,7 @@
public static final class Builder {
private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED;
+ private Set<UserHandle> mUsersWithMatchingAccounts;
/**
* Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -142,11 +170,28 @@
}
/**
+ * Sets the user handles with matching managed accounts on the remote device to which
+ * this virtual device is streaming.
+ *
+ * @param usersWithMatchingAccounts A set of user handles with matching managed
+ * accounts on the remote device this is streaming to.
+ * @see android.app.admin.DevicePolicyManager#NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY
+ */
+ public Builder setUsersWithMatchingAccounts(
+ @NonNull Set<UserHandle> usersWithMatchingAccounts) {
+ mUsersWithMatchingAccounts = usersWithMatchingAccounts;
+ return this;
+ }
+
+ /**
* Builds the {@link VirtualDeviceParams} instance.
*/
@NonNull
public VirtualDeviceParams build() {
- return new VirtualDeviceParams(mLockState);
+ if (mUsersWithMatchingAccounts == null) {
+ mUsersWithMatchingAccounts = Collections.emptySet();
+ }
+ return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts);
}
}
}
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index e98b63e..8e71dd3 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -16,6 +16,7 @@
package com.android.server.companion.virtual;
+import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -27,9 +28,10 @@
import android.content.pm.ActivityInfo;
import android.os.Build;
import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
import android.window.DisplayWindowPolicyController;
-import java.util.HashSet;
import java.util.List;
@@ -38,6 +40,8 @@
*/
class GenericWindowPolicyController extends DisplayWindowPolicyController {
+ private static final String TAG = "VirtualDeviceManager";
+
/**
* If required, allow the secure activity to display on remote device since
* {@link android.os.Build.VERSION_CODES#TIRAMISU}.
@@ -45,10 +49,13 @@
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
+ @NonNull private final ArraySet<UserHandle> mAllowedUsers;
- @NonNull final HashSet<Integer> mRunningUids = new HashSet<>();
+ @NonNull final ArraySet<Integer> mRunningUids = new ArraySet<>();
- GenericWindowPolicyController(int windowFlags, int systemWindowFlags) {
+ GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
+ @NonNull ArraySet<UserHandle> allowedUsers) {
+ mAllowedUsers = allowedUsers;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
}
@@ -58,7 +65,7 @@
final int activityCount = activities.size();
for (int i = 0; i < activityCount; i++) {
final ActivityInfo aInfo = activities.get(i);
- if ((aInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+ if (!canContainActivity(aInfo, /* windowFlags= */ 0, /* systemWindowFlags= */ 0)) {
return false;
}
}
@@ -68,21 +75,7 @@
@Override
public boolean keepActivityOnWindowFlagsChanged(ActivityInfo activityInfo, int windowFlags,
int systemWindowFlags) {
- if ((activityInfo.flags & ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
- return false;
- }
- if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
- activityInfo.packageName,
- UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid))) {
- // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
- if ((windowFlags & FLAG_SECURE) != 0) {
- return false;
- }
- if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
- return false;
- }
- }
- return true;
+ return canContainActivity(activityInfo, windowFlags, systemWindowFlags);
}
@Override
@@ -105,4 +98,28 @@
boolean containsUid(int uid) {
return mRunningUids.contains(uid);
}
+
+ private boolean canContainActivity(ActivityInfo activityInfo, int windowFlags,
+ int systemWindowFlags) {
+ if ((activityInfo.flags & FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES) == 0) {
+ return false;
+ }
+ final UserHandle activityUser =
+ UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid);
+ if (!mAllowedUsers.contains(activityUser)) {
+ Slog.d(TAG, "Virtual device activity not allowed from user " + activityUser);
+ return false;
+ }
+ if (!CompatChanges.isChangeEnabled(ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE,
+ activityInfo.packageName, activityUser)) {
+ // TODO(b/201712607): Add checks for the apps that use SurfaceView#setSecure.
+ if ((windowFlags & FLAG_SECURE) != 0) {
+ return false;
+ }
+ if ((systemWindowFlags & SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
}
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 01e8d3b..1bb95f8 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -16,10 +16,14 @@
package com.android.server.companion.virtual;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_ENABLED;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY;
+import static android.app.admin.DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.annotation.NonNull;
+import android.app.admin.DevicePolicyManager;
import android.companion.AssociationInfo;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.VirtualDeviceParams;
@@ -34,6 +38,9 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.ArraySet;
import android.util.SparseArray;
import android.window.DisplayWindowPolicyController;
@@ -286,11 +293,29 @@
mVirtualDisplayIds.add(displayId);
final GenericWindowPolicyController dwpc =
new GenericWindowPolicyController(FLAG_SECURE,
- SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+ SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS, getAllowedUserHandles());
mWindowPolicyControllers.put(displayId, dwpc);
return dwpc;
}
+ private ArraySet<UserHandle> getAllowedUserHandles() {
+ ArraySet<UserHandle> result = new ArraySet<>();
+ DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ for (UserHandle profile : userManager.getAllProfiles()) {
+ int nearbyAppStreamingPolicy = dpm.getNearbyAppStreamingPolicy(profile.getIdentifier());
+ if (nearbyAppStreamingPolicy == NEARBY_STREAMING_ENABLED
+ || nearbyAppStreamingPolicy == NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY) {
+ result.add(profile);
+ } else if (nearbyAppStreamingPolicy == NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY) {
+ if (mParams.getUsersWithMatchingAccounts().contains(profile)) {
+ result.add(profile);
+ }
+ }
+ }
+ return result;
+ }
+
void onVirtualDisplayRemovedLocked(int displayId) {
if (!mVirtualDisplayIds.contains(displayId)) {
throw new IllegalStateException(
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 ecaf581..77f1e24 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
@@ -20,19 +20,24 @@
import android.companion.virtual.VirtualDeviceParams;
import android.os.Parcel;
+import android.os.UserHandle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Set;
+
@RunWith(AndroidJUnit4.class)
public class VirtualDeviceParamsTest {
@Test
public void parcelable_shouldRecreateSuccessfully() {
- VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder().setLockState(
- VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED).build();
+ VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder()
+ .setLockState(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED)
+ .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
+ .build();
Parcel parcel = Parcel.obtain();
originalParams.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -40,5 +45,7 @@
VirtualDeviceParams params = VirtualDeviceParams.CREATOR.createFromParcel(parcel);
assertThat(params).isEqualTo(originalParams);
assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED);
+ assertThat(params.getUsersWithMatchingAccounts())
+ .containsExactly(UserHandle.of(123), UserHandle.of(456));
}
}