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