From a4ed6819c8f71d9a1e84d66e4f894557ee859d69 Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Tue, 14 Dec 2021 22:58:30 -0800 Subject: [PATCH] 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 --- .../virtual/VirtualDeviceParams.java | 53 ++++++++++++++++-- .../GenericWindowPolicyController.java | 55 ++++++++++++------- .../companion/virtual/VirtualDeviceImpl.java | 27 ++++++++- .../virtual/VirtualDeviceParamsTest.java | 11 +++- 4 files changed, 120 insertions(+), 26 deletions(-) diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index ad2dea9958fd6..d61d4741637a2 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -23,12 +23,16 @@ import android.annotation.NonNull; 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 final class VirtualDeviceParams implements Parcelable { public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; private final int mLockState; + private final ArraySet mUsersWithMatchingAccounts; - private VirtualDeviceParams(@LockState int lockState) { + private VirtualDeviceParams( + @LockState int lockState, + @NonNull Set usersWithMatchingAccounts) { mLockState = lockState; + mUsersWithMatchingAccounts = new ArraySet<>(usersWithMatchingAccounts); } + @SuppressWarnings("unchecked") private VirtualDeviceParams(Parcel parcel) { mLockState = parcel.readInt(); + mUsersWithMatchingAccounts = (ArraySet) 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 getUsersWithMatchingAccounts() { + return Collections.unmodifiableSet(mUsersWithMatchingAccounts); + } + @Override public int describeContents() { return 0; @@ -82,6 +106,7 @@ public final class VirtualDeviceParams implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mLockState); + dest.writeArraySet(mUsersWithMatchingAccounts); } @Override @@ -93,18 +118,20 @@ public final class VirtualDeviceParams implements Parcelable { 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 final class VirtualDeviceParams implements Parcelable { public static final class Builder { private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED; + private Set mUsersWithMatchingAccounts; /** * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} @@ -141,12 +169,29 @@ public final class VirtualDeviceParams implements Parcelable { return this; } + /** + * 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 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 e98b63ecd4b56..8e71dd35faf00 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.ComponentName; 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 @@ import java.util.List; */ 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 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @ChangeId @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L; + @NonNull private final ArraySet mAllowedUsers; - @NonNull final HashSet mRunningUids = new HashSet<>(); + @NonNull final ArraySet mRunningUids = new ArraySet<>(); - GenericWindowPolicyController(int windowFlags, int systemWindowFlags) { + GenericWindowPolicyController(int windowFlags, int systemWindowFlags, + @NonNull ArraySet allowedUsers) { + mAllowedUsers = allowedUsers; setInterestedWindowFlags(windowFlags, systemWindowFlags); } @@ -58,7 +65,7 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { 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 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { @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 @@ class GenericWindowPolicyController extends DisplayWindowPolicyController { 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 01e8d3bce211b..1bb95f8db337f 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.hardware.input.VirtualTouchEvent; 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 getAllowedUserHandles() { + ArraySet 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 ecaf5814d8fe1..77f1e24ee7716 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 static com.google.common.truth.Truth.assertThat; 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 @@ public class VirtualDeviceParamsTest { 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)); } }