Merge changes from topic "virtualdevice-params"

* changes:
  Implement VirtualDevice's device policy enforcement
  Add params object to createVirtualDevice
This commit is contained in:
TreeHugger Robot
2022-01-13 21:07:43 +00:00
committed by Android (Google) Code Review
11 changed files with 393 additions and 38 deletions

View File

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

View File

@@ -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) {

View File

@@ -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;

View File

@@ -0,0 +1,197 @@
/*
* 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 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.
*
* @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 final ArraySet<UserHandle> mUsersWithMatchingAccounts;
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;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mLockState);
dest.writeArraySet(mUsersWithMatchingAccounts);
}
@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 && mUsersWithMatchingAccounts.equals(
that.mUsersWithMatchingAccounts);
}
@Override
public int hashCode() {
return Objects.hash(mLockState, mUsersWithMatchingAccounts);
}
@Override
public String toString() {
return "VirtualDeviceParams("
+ " mLockState=" + mLockState
+ " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts
+ ")";
}
public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
new Parcelable.Creator<VirtualDeviceParams>() {
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;
private Set<UserHandle> mUsersWithMatchingAccounts;
/**
* 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;
}
/**
* 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() {
if (mUsersWithMatchingAccounts == null) {
mUsersWithMatchingAccounts = Collections.emptySet();
}
return new VirtualDeviceParams(mLockState, mUsersWithMatchingAccounts);
}
}
}

View File

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

View File

@@ -16,14 +16,20 @@
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;
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;
@@ -32,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;
@@ -56,6 +65,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
final List<Integer> 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 +75,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 +103,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();
}
@@ -267,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<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(

View File

@@ -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) {

View File

@@ -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.

View File

@@ -1250,6 +1250,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");
@@ -1282,14 +1290,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)) {

View File

@@ -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

View File

@@ -0,0 +1,51 @@
/*
* 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 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)
.setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
.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);
assertThat(params.getUsersWithMatchingAccounts())
.containsExactly(UserHandle.of(123), UserHandle.of(456));
}
}