From 57cd80185e846defabfc710080a19adfb798efb6 Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Wed, 17 Nov 2021 20:16:08 -0800 Subject: [PATCH 1/2] Add params object to createVirtualDevice The VirtualDeviceParams are passed into createVirtualDevice to provide additional configuration options to the method. Bug: 194949534 Test: To be added in ag/15781910 Change-Id: Ief9c013a2f1c70a63e3dc3eb8fbc0af60ea65c44 --- .../virtual/IVirtualDeviceManager.aidl | 7 +- .../virtual/VirtualDeviceManager.java | 10 +- .../virtual/VirtualDeviceParams.aidl | 19 +++ .../virtual/VirtualDeviceParams.java | 152 ++++++++++++++++++ .../companion/virtual/VirtualDeviceImpl.java | 27 +++- .../virtual/VirtualDeviceManagerService.java | 13 +- .../virtual/VirtualDeviceManagerInternal.java | 6 + .../server/display/DisplayManagerService.java | 16 +- .../VirtualDeviceManagerServiceTest.java | 3 +- .../virtual/VirtualDeviceParamsTest.java | 44 +++++ 10 files changed, 279 insertions(+), 18 deletions(-) create mode 100644 core/java/android/companion/virtual/VirtualDeviceParams.aidl create mode 100644 core/java/android/companion/virtual/VirtualDeviceParams.java create mode 100644 services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl index 2dfa2021fdfe0..d80bee668f182 100644 --- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl +++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl @@ -17,6 +17,7 @@ package android.companion.virtual; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.VirtualDeviceParams; /** * Interface for communication between VirtualDeviceManager and VirtualDeviceManagerService. @@ -33,6 +34,10 @@ interface IVirtualDeviceManager { * that this belongs to the calling UID. * @param associationId The association ID as returned by {@link AssociationInfo#getId()} from * CDM. Virtual devices must have a corresponding association with CDM in order to be created. + * @param params The parameters for creating this virtual device. See {@link + * VirtualDeviceManager.VirtualDeviceParams}. */ - IVirtualDevice createVirtualDevice(in IBinder token, String packageName, int associationId); + IVirtualDevice createVirtualDevice( + in IBinder token, String packageName, int associationId, + in VirtualDeviceParams params); } diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index bace45bccbf44..858e4daa136f0 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -100,11 +100,11 @@ public final class VirtualDeviceManager { */ @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @Nullable - public VirtualDevice createVirtualDevice(int associationId) { + public VirtualDevice createVirtualDevice(int associationId, VirtualDeviceParams params) { // TODO(b/194949534): Unhide this API try { IVirtualDevice virtualDevice = mService.createVirtualDevice( - new Binder(), mContext.getPackageName(), associationId); + new Binder(), mContext.getPackageName(), associationId, params); return new VirtualDevice(mContext, virtualDevice); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -273,6 +273,12 @@ public final class VirtualDeviceManager { } } + /** + * Returns the display flags that should be added to a particular virtual display. + * Additional device-level flags from {@link + * com.android.server.companion.virtual.VirtualDeviceImpl#getBaseVirtualDisplayFlags()} will + * be added by DisplayManagerService. + */ private int getVirtualDisplayFlags(@DisplayFlags int flags) { int virtualDisplayFlags = DEFAULT_VIRTUAL_DISPLAY_FLAGS; if ((flags & DISPLAY_FLAG_TRUSTED) != 0) { diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.aidl b/core/java/android/companion/virtual/VirtualDeviceParams.aidl new file mode 100644 index 0000000000000..9b3974a9c904d --- /dev/null +++ b/core/java/android/companion/virtual/VirtualDeviceParams.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2021 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; + +parcelable VirtualDeviceParams; diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java new file mode 100644 index 0000000000000..ad2dea9958fd6 --- /dev/null +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2021 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; + +import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Objects; + +/** + * Params that can be configured when creating virtual devices. + * + * @hide + */ +// TODO(b/194949534): Unhide this API +public final class VirtualDeviceParams implements Parcelable { + + /** @hide */ + @IntDef(prefix = "LOCK_STATE_", + value = {LOCK_STATE_ALWAYS_LOCKED, LOCK_STATE_ALWAYS_UNLOCKED}) + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) + public @interface LockState {} + + /** + * Indicates that the lock state of the virtual device should be always locked. + * + * @hide // TODO(b/194949534): Unhide this API + */ + public static final int LOCK_STATE_ALWAYS_LOCKED = 0; + + /** + * Indicates that the lock state of the virtual device should be always unlocked. + * + * @hide // TODO(b/194949534): Unhide this API + */ + public static final int LOCK_STATE_ALWAYS_UNLOCKED = 1; + + private final int mLockState; + + private VirtualDeviceParams(@LockState int lockState) { + mLockState = lockState; + } + + private VirtualDeviceParams(Parcel parcel) { + mLockState = parcel.readInt(); + } + + @LockState + public int getLockState() { + return mLockState; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mLockState); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof VirtualDeviceParams)) { + return false; + } + VirtualDeviceParams that = (VirtualDeviceParams) o; + return mLockState == that.mLockState; + } + + @Override + public int hashCode() { + return Objects.hash(mLockState); + } + + @Override + public String toString() { + return "VirtualDeviceParams(" + + " mLockState=" + mLockState + + ")"; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public VirtualDeviceParams createFromParcel(Parcel in) { + return new VirtualDeviceParams(in); + } + + public VirtualDeviceParams[] newArray(int size) { + return new VirtualDeviceParams[size]; + } + }; + + /** + * Builder for {@link VirtualDeviceParams}. + */ + public static final class Builder { + + private @LockState int mLockState = LOCK_STATE_ALWAYS_LOCKED; + + /** + * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY} + * is required if this is set to {@link #LOCK_STATE_ALWAYS_UNLOCKED}. + * The default is {@link #LOCK_STATE_ALWAYS_LOCKED}. + * + * @param lockState The lock state, either {@link #LOCK_STATE_ALWAYS_LOCKED} or + * {@link #LOCK_STATE_ALWAYS_UNLOCKED}. + */ + @RequiresPermission(value = ADD_ALWAYS_UNLOCKED_DISPLAY, conditional = true) + @NonNull + public Builder setLockState(@LockState int lockState) { + mLockState = lockState; + return this; + } + + /** + * Builds the {@link VirtualDeviceParams} instance. + */ + @NonNull + public VirtualDeviceParams build() { + return new VirtualDeviceParams(mLockState); + } + } +} 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 ca35e033ed70c..01e8d3bce211b 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -22,8 +22,10 @@ import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTE import android.annotation.NonNull; import android.companion.AssociationInfo; import android.companion.virtual.IVirtualDevice; +import android.companion.virtual.VirtualDeviceParams; import android.content.Context; import android.graphics.Point; +import android.hardware.display.DisplayManager; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; @@ -56,6 +58,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub final List mVirtualDisplayIds = new ArrayList<>(); private final OnDeviceCloseListener mListener; private final IBinder mAppToken; + private final VirtualDeviceParams mParams; /** * A mapping from the virtual display ID to its corresponding @@ -65,17 +68,21 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub new SparseArray<>(); VirtualDeviceImpl(Context context, AssociationInfo associationInfo, - IBinder token, int ownerUid, OnDeviceCloseListener listener) { - this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener); + IBinder token, int ownerUid, OnDeviceCloseListener listener, + VirtualDeviceParams params) { + this(context, associationInfo, token, ownerUid, /* inputController= */ null, listener, + params); } @VisibleForTesting VirtualDeviceImpl(Context context, AssociationInfo associationInfo, IBinder token, - int ownerUid, InputController inputController, OnDeviceCloseListener listener) { + int ownerUid, InputController inputController, OnDeviceCloseListener listener, + VirtualDeviceParams params) { mContext = context; mAssociationInfo = associationInfo; mOwnerUid = ownerUid; mAppToken = token; + mParams = params; if (inputController == null) { mInputController = new InputController(mVirtualDeviceLock); } else { @@ -89,7 +96,19 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub } } - @Override + /** + * Returns the flags that should be added to any virtual displays created on this virtual + * device. + */ + int getBaseVirtualDisplayFlags() { + int flags = 0; + if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) { + flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; + } + return flags; + } + + @Override // Binder call public int getAssociationId() { return mAssociationInfo.getId(); } 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 0db670e469092..12df79d2ddc90 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -24,6 +24,7 @@ import android.companion.CompanionDeviceManager; import android.companion.CompanionDeviceManager.OnAssociationsChangedListener; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.IVirtualDeviceManager; +import android.companion.virtual.VirtualDeviceParams; import android.content.Context; import android.os.IBinder; import android.os.Parcel; @@ -131,7 +132,10 @@ public class VirtualDeviceManagerService extends SystemService { @Override // Binder call public IVirtualDevice createVirtualDevice( - IBinder token, String packageName, int associationId) { + IBinder token, + String packageName, + int associationId, + @NonNull VirtualDeviceParams params) { getContext().enforceCallingOrSelfPermission( android.Manifest.permission.CREATE_VIRTUAL_DEVICE, "createVirtualDevice"); @@ -160,7 +164,7 @@ public class VirtualDeviceManagerService extends SystemService { mVirtualDevices.remove(associationId); } } - }); + }, params); mVirtualDevices.put(associationInfo.getId(), virtualDevice); return virtualDevice; } @@ -237,6 +241,11 @@ public class VirtualDeviceManagerService extends SystemService { } } + @Override + public int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice) { + return ((VirtualDeviceImpl) virtualDevice).getBaseVirtualDisplayFlags(); + } + @Override public boolean isAppOwnerOfAnyVirtualDevice(int uid) { synchronized (mVirtualDeviceManagerLock) { 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 39fa3f200aab0..135276e789c1e 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -49,6 +49,12 @@ public abstract class VirtualDeviceManagerInternal { */ public abstract void onVirtualDisplayRemoved(IVirtualDevice virtualDevice, int displayId); + /** + * Returns the flags that should be added to any virtual displays created on this virtual + * device. + */ + public abstract int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice); + /** * Returns true if the given {@code uid} is the owner of any virtual devices that are * currently active. diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index c0a6abf3a121a..20f0f16d947fa 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1251,6 +1251,14 @@ public final class DisplayManagerService extends SystemService { } final Surface surface = virtualDisplayConfig.getSurface(); int flags = virtualDisplayConfig.getFlags(); + if (virtualDevice != null) { + final VirtualDeviceManagerInternal vdm = + getLocalService(VirtualDeviceManagerInternal.class); + if (!vdm.isValidVirtualDevice(virtualDevice)) { + throw new SecurityException("Invalid virtual device"); + } + flags |= vdm.getBaseVirtualDisplayFlags(virtualDevice); + } if (surface != null && surface.isSingleBuffered()) { throw new IllegalArgumentException("Surface can't be single-buffered"); @@ -1283,14 +1291,6 @@ public final class DisplayManagerService extends SystemService { } } - if (virtualDevice != null) { - final VirtualDeviceManagerInternal vdm = - getLocalService(VirtualDeviceManagerInternal.class); - if (!vdm.isValidVirtualDevice(virtualDevice)) { - throw new SecurityException("Invalid virtual device"); - } - } - if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) { if (!canProjectVideo(projection)) { 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 c7c0756bc0d08..d79a833d70460 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 @@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify; import static org.testng.Assert.assertThrows; import android.Manifest; +import android.companion.virtual.VirtualDeviceParams; import android.content.Context; import android.content.ContextWrapper; import android.graphics.Point; @@ -84,7 +85,7 @@ public class VirtualDeviceManagerServiceTest { mInputController = new InputController(new Object(), mNativeWrapperMock); mDeviceImpl = new VirtualDeviceImpl(mContext, /* association info */ null, new Binder(), /* uid */ 0, mInputController, - (int associationId) -> {}); + (int associationId) -> {}, new VirtualDeviceParams.Builder().build()); } @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 new file mode 100644 index 0000000000000..ecaf5814d8fe1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2021 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 android.companion.virtual.VirtualDeviceParams; +import android.os.Parcel; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class VirtualDeviceParamsTest { + + @Test + public void parcelable_shouldRecreateSuccessfully() { + VirtualDeviceParams originalParams = new VirtualDeviceParams.Builder().setLockState( + VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED).build(); + Parcel parcel = Parcel.obtain(); + originalParams.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + + VirtualDeviceParams params = VirtualDeviceParams.CREATOR.createFromParcel(parcel); + assertThat(params).isEqualTo(originalParams); + assertThat(params.getLockState()).isEqualTo(VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED); + } +} From a4ed6819c8f71d9a1e84d66e4f894557ee859d69 Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Tue, 14 Dec 2021 22:58:30 -0800 Subject: [PATCH 2/2] 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)); } }