Merge "VirtualDevice clipboard support"

This commit is contained in:
Antony Sargent
2023-01-24 19:28:10 +00:00
committed by Android (Google) Code Review
7 changed files with 414 additions and 114 deletions

View File

@@ -70,6 +70,23 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public static final boolean DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS = true;
/**
* DeviceConfig property, within the clipboard namespace, that determines whether VirtualDevices
* are allowed to have siloed Clipboards for the apps running on them. If false, then clipboard
* access is blocked entirely for apps running on VirtualDevices.
*
* @hide
*/
public static final String DEVICE_CONFIG_ALLOW_VIRTUALDEVICE_SILOS =
"allow_virtualdevice_silos";
/**
* Default value for the DEVICE_CONFIG_ALLOW_VIRTUALDEVICE_SILOS property.
*
* @hide
*/
public static final boolean DEVICE_CONFIG_DEFAULT_ALLOW_VIRTUALDEVICE_SILOS = false;
private final Context mContext;
private final Handler mHandler;
private final IClipboard mService;
@@ -133,7 +150,8 @@ public class ClipboardManager extends android.text.ClipboardManager {
clip,
mContext.getOpPackageName(),
mContext.getAttributionTag(),
mContext.getUserId());
mContext.getUserId(),
mContext.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -162,6 +180,7 @@ public class ClipboardManager extends android.text.ClipboardManager {
mContext.getOpPackageName(),
mContext.getAttributionTag(),
mContext.getUserId(),
mContext.getDeviceId(),
sourcePackage);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -178,7 +197,8 @@ public class ClipboardManager extends android.text.ClipboardManager {
mService.clearPrimaryClip(
mContext.getOpPackageName(),
mContext.getAttributionTag(),
mContext.getUserId());
mContext.getUserId(),
mContext.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -197,7 +217,8 @@ public class ClipboardManager extends android.text.ClipboardManager {
return mService.getPrimaryClip(
mContext.getOpPackageName(),
mContext.getAttributionTag(),
mContext.getUserId());
mContext.getUserId(),
mContext.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -217,7 +238,8 @@ public class ClipboardManager extends android.text.ClipboardManager {
return mService.getPrimaryClipDescription(
mContext.getOpPackageName(),
mContext.getAttributionTag(),
mContext.getUserId());
mContext.getUserId(),
mContext.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -234,7 +256,8 @@ public class ClipboardManager extends android.text.ClipboardManager {
return mService.hasPrimaryClip(
mContext.getOpPackageName(),
mContext.getAttributionTag(),
mContext.getUserId());
mContext.getUserId(),
mContext.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -248,7 +271,8 @@ public class ClipboardManager extends android.text.ClipboardManager {
mPrimaryClipChangedServiceListener,
mContext.getOpPackageName(),
mContext.getAttributionTag(),
mContext.getUserId());
mContext.getUserId(),
mContext.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -266,7 +290,8 @@ public class ClipboardManager extends android.text.ClipboardManager {
mPrimaryClipChangedServiceListener,
mContext.getOpPackageName(),
mContext.getAttributionTag(),
mContext.getUserId());
mContext.getUserId(),
mContext.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -306,7 +331,8 @@ public class ClipboardManager extends android.text.ClipboardManager {
return mService.hasClipboardText(
mContext.getOpPackageName(),
mContext.getAttributionTag(),
mContext.getUserId());
mContext.getUserId(),
mContext.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -326,7 +352,8 @@ public class ClipboardManager extends android.text.ClipboardManager {
return mService.getPrimaryClipSource(
mContext.getOpPackageName(),
mContext.getAttributionTag(),
mContext.getUserId());
mContext.getUserId(),
mContext.getDeviceId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -26,22 +26,26 @@ import android.content.IOnPrimaryClipChangedListener;
* {@hide}
*/
interface IClipboard {
void setPrimaryClip(in ClipData clip, String callingPackage, String attributionTag, int userId);
void setPrimaryClipAsPackage(in ClipData clip, String callingPackage, String attributionTag, int userId,
String sourcePackage);
void clearPrimaryClip(String callingPackage, String attributionTag, int userId);
ClipData getPrimaryClip(String pkg, String attributionTag, int userId);
ClipDescription getPrimaryClipDescription(String callingPackage, String attributionTag, int userId);
boolean hasPrimaryClip(String callingPackage, String attributionTag, int userId);
void setPrimaryClip(in ClipData clip, String callingPackage, String attributionTag, int userId,
int deviceId);
void setPrimaryClipAsPackage(in ClipData clip, String callingPackage, String attributionTag,
int userId, int deviceId, String sourcePackage);
void clearPrimaryClip(String callingPackage, String attributionTag, int userId, int deviceId);
ClipData getPrimaryClip(String pkg, String attributionTag, int userId, int deviceId);
ClipDescription getPrimaryClipDescription(String callingPackage, String attributionTag,
int userId, int deviceId);
boolean hasPrimaryClip(String callingPackage, String attributionTag, int userId, int deviceId);
void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener,
String callingPackage, String attributionTag, int userId);
String callingPackage, String attributionTag, int userId, int deviceId);
void removePrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener,
String callingPackage, String attributionTag, int userId);
String callingPackage, String attributionTag, int userId, int deviceId);
/**
* Returns true if the clipboard contains text; false otherwise.
*/
boolean hasClipboardText(String callingPackage, String attributionTag, int userId);
boolean hasClipboardText(String callingPackage, String attributionTag, int userId,
int deviceId);
String getPrimaryClipSource(String callingPackage, String attributionTag, int userId);
String getPrimaryClipSource(String callingPackage, String attributionTag, int userId,
int deviceId);
}

View File

@@ -88,7 +88,6 @@ import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
@@ -114,7 +113,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
private final CameraAccessController mCameraAccessController;
private VirtualAudioController mVirtualAudioController;
@VisibleForTesting
final Set<Integer> mVirtualDisplayIds = new ArraySet<>();
final ArraySet<Integer> mVirtualDisplayIds = new ArraySet<>();
private final OnDeviceCloseListener mOnDeviceCloseListener;
private final IBinder mAppToken;
private final VirtualDeviceParams mParams;

View File

@@ -67,7 +67,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
@@ -471,7 +470,7 @@ public class VirtualDeviceManagerService extends SystemService {
}
@Override
public @NonNull Set<Integer> getDeviceIdsForUid(int uid) {
public @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid) {
ArraySet<Integer> result = new ArraySet<>();
synchronized (mVirtualDeviceManagerLock) {
int size = mVirtualDevices.size();
@@ -586,6 +585,20 @@ public class VirtualDeviceManagerService extends SystemService {
return false;
}
@Override
public @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId) {
synchronized (mVirtualDeviceManagerLock) {
int size = mVirtualDevices.size();
for (int i = 0; i < size; i++) {
VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
if (device.getDeviceId() == deviceId) {
return new ArraySet<>(device.mVirtualDisplayIds);
}
}
}
return new ArraySet<>();
}
@Override
public void registerVirtualDisplayListener(
@NonNull VirtualDisplayListener listener) {

View File

@@ -17,6 +17,10 @@
package com.android.server.clipboard;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
import android.Manifest;
import android.annotation.NonNull;
@@ -29,6 +33,8 @@ import android.app.AppOpsManager;
import android.app.IUriGrantsManager;
import android.app.KeyguardManager;
import android.app.UriGrantsManager;
import android.companion.virtual.VirtualDeviceManager;
import android.content.BroadcastReceiver;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
@@ -39,11 +45,13 @@ import android.content.Context;
import android.content.IClipboard;
import android.content.IOnPrimaryClipChangedListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -64,10 +72,13 @@ import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Pair;
import android.util.SafetyProtectionUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseArrayMap;
import android.util.SparseBooleanArray;
import android.view.Display;
import android.view.autofill.AutofillManagerInternal;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationManager;
@@ -130,7 +141,9 @@ public class ClipboardService extends SystemService {
private final IUriGrantsManager mUgm;
private final UriGrantsManagerInternal mUgmInternal;
private final WindowManagerInternal mWm;
private final VirtualDeviceManagerInternal mVdm;
private final VirtualDeviceManagerInternal mVdmInternal;
private final VirtualDeviceManager mVdm;
private BroadcastReceiver mVirtualDeviceRemovedReceiver;
private final IUserManager mUm;
private final PackageManager mPm;
private final AppOpsManager mAppOps;
@@ -141,11 +154,15 @@ public class ClipboardService extends SystemService {
private final Handler mWorkerHandler;
@GuardedBy("mLock")
private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
// Maps (userId, deviceId) to Clipboard.
private final SparseArrayMap<Integer, Clipboard> mClipboards = new SparseArrayMap<>();
@GuardedBy("mLock")
private boolean mShowAccessNotifications =
ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS;
@GuardedBy("mLock")
private boolean mAllowVirtualDeviceSilos =
ClipboardManager.DEVICE_CONFIG_DEFAULT_ALLOW_VIRTUALDEVICE_SILOS;
@GuardedBy("mLock")
private int mMaxClassificationLength = DEFAULT_MAX_CLASSIFICATION_LENGTH;
@@ -163,7 +180,9 @@ public class ClipboardService extends SystemService {
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mWm = LocalServices.getService(WindowManagerInternal.class);
// Can be null; not all products have CDM + VirtualDeviceManager
mVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
mVdm = (mVdmInternal == null) ? null : getContext().getSystemService(
VirtualDeviceManager.class);
mPm = getContext().getPackageManager();
mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
@@ -174,7 +193,7 @@ public class ClipboardService extends SystemService {
if (IS_EMULATOR) {
mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
synchronized (mLock) {
setPrimaryClipInternalLocked(getClipboardLocked(0), clip,
setPrimaryClipInternalLocked(getClipboardLocked(0, DEVICE_ID_DEFAULT), clip,
android.os.Process.SYSTEM_UID, null);
}
});
@@ -194,12 +213,39 @@ public class ClipboardService extends SystemService {
@Override
public void onStart() {
publishBinderService(Context.CLIPBOARD_SERVICE, new ClipboardImpl());
if (mVdmInternal != null) {
registerVirtualDeviceRemovedListener();
}
}
private void registerVirtualDeviceRemovedListener() {
if (mVirtualDeviceRemovedReceiver != null) {
return;
}
mVirtualDeviceRemovedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) {
return;
}
final int removedDeviceId =
intent.getIntExtra(EXTRA_VIRTUAL_DEVICE_ID, DEVICE_ID_INVALID);
synchronized (mLock) {
for (int i = mClipboards.numMaps() - 1; i >= 0; i--) {
mClipboards.delete(mClipboards.keyAt(i), removedDeviceId);
}
}
}
};
IntentFilter filter = new IntentFilter(ACTION_VIRTUAL_DEVICE_REMOVED);
getContext().registerReceiver(mVirtualDeviceRemovedReceiver, filter,
Context.RECEIVER_NOT_EXPORTED);
}
@Override
public void onUserStopped(@NonNull TargetUser user) {
synchronized (mLock) {
mClipboards.remove(user.getUserIdentifier());
mClipboards.delete(user.getUserIdentifier());
}
}
@@ -209,6 +255,10 @@ public class ClipboardService extends SystemService {
DeviceConfig.NAMESPACE_CLIPBOARD,
ClipboardManager.DEVICE_CONFIG_SHOW_ACCESS_NOTIFICATIONS,
ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS);
mAllowVirtualDeviceSilos = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_CLIPBOARD,
ClipboardManager.DEVICE_CONFIG_ALLOW_VIRTUALDEVICE_SILOS,
ClipboardManager.DEVICE_CONFIG_DEFAULT_ALLOW_VIRTUALDEVICE_SILOS);
mMaxClassificationLength = DeviceConfig.getInt(DeviceConfig.NAMESPACE_CLIPBOARD,
PROPERTY_MAX_CLASSIFICATION_LENGTH, DEFAULT_MAX_CLASSIFICATION_LENGTH);
}
@@ -226,8 +276,9 @@ public class ClipboardService extends SystemService {
}
}
private class PerUserClipboard {
final int userId;
private static class Clipboard {
public final int userId;
public final int deviceId;
final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
= new RemoteCallbackList<IOnPrimaryClipChangedListener>();
@@ -254,8 +305,9 @@ public class ClipboardService extends SystemService {
/** The text classifier session that is used to annotate the text in the primary clip. */
TextClassifier mTextClassifier;
PerUserClipboard(int userId) {
Clipboard(int userId, int deviceId) {
this.userId = userId;
this.deviceId = deviceId;
}
}
@@ -332,6 +384,54 @@ public class ClipboardService extends SystemService {
UserHandle.getAppId(Binder.getCallingUid()));
}
/**
* Determines which deviceId to use for selecting a Clipboard, depending on where a given app
* is running.
*
* @param requestedDeviceId the requested deviceId passed in from the client side
* @param uid the intended app uid
* @return a deviceId to use in selecting the appropriate clipboard, or
* DEVICE_ID_INVALID if this uid should not be allowed access. A value of DEVICE_ID_DEFAULT
* means just use the "regular" clipboard.
*/
private int getIntendingDeviceId(int requestedDeviceId, int uid) {
if (mVdmInternal == null) {
return DEVICE_ID_DEFAULT;
}
ArraySet<Integer> virtualDeviceIds = mVdmInternal.getDeviceIdsForUid(uid);
synchronized (mLock) {
if (!mAllowVirtualDeviceSilos
&& (!virtualDeviceIds.isEmpty() || requestedDeviceId != DEVICE_ID_DEFAULT)) {
return DEVICE_ID_INVALID;
}
}
if (requestedDeviceId != DEVICE_ID_DEFAULT) {
// Privileged apps that own the VirtualDevices, or regular apps running on it, can
// request it by id.
if (mVdmInternal.getDeviceOwnerUid(requestedDeviceId) == uid
|| virtualDeviceIds.contains(requestedDeviceId)) {
return requestedDeviceId;
}
return DEVICE_ID_INVALID;
}
// The common case is apps running normally (not on a VirtualDevice).
if (virtualDeviceIds.isEmpty()) {
return DEVICE_ID_DEFAULT;
}
// If an app is running on more than one VirtualDevice, it isn't clear which clipboard they
// should use.
if (virtualDeviceIds.size() > 1) {
return DEVICE_ID_INVALID;
}
return virtualDeviceIds.valueAt(0);
}
/**
* To handle the difference between userId and intendingUserId, uid and intendingUid.
*
@@ -364,8 +464,10 @@ public class ClipboardService extends SystemService {
ClipData clip,
String callingPackage,
String attributionTag,
@UserIdInt int userId) {
checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, callingPackage);
@UserIdInt int userId,
int deviceId) {
checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, deviceId,
callingPackage);
}
@Override
@@ -374,10 +476,12 @@ public class ClipboardService extends SystemService {
String callingPackage,
String attributionTag,
@UserIdInt int userId,
int deviceId,
String sourcePackage) {
getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
"Requires SET_CLIP_SOURCE permission");
checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, sourcePackage);
checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, deviceId,
sourcePackage);
}
private void checkAndSetPrimaryClip(
@@ -385,41 +489,46 @@ public class ClipboardService extends SystemService {
String callingPackage,
String attributionTag,
@UserIdInt int userId,
int deviceId,
String sourcePackage) {
if (clip == null || clip.getItemCount() <= 0) {
throw new IllegalArgumentException("No items");
}
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
if (!clipboardAccessAllowed(
AppOpsManager.OP_WRITE_CLIPBOARD,
callingPackage,
attributionTag,
intendingUid,
intendingUserId)) {
intendingUserId,
intendingDeviceId)) {
return;
}
checkDataOwner(clip, intendingUid);
synchronized (mLock) {
scheduleAutoClear(userId, intendingUid);
setPrimaryClipInternalLocked(clip, intendingUid, sourcePackage);
scheduleAutoClear(userId, intendingUid, intendingDeviceId);
setPrimaryClipInternalLocked(clip, intendingUid, intendingDeviceId, sourcePackage);
}
}
private void scheduleAutoClear(@UserIdInt int userId, int intendingUid) {
private void scheduleAutoClear(
@UserIdInt int userId, int intendingUid, int intendingDeviceId) {
final long oldIdentity = Binder.clearCallingIdentity();
try {
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
PROPERTY_AUTO_CLEAR_ENABLED, true)) {
Pair<Integer, Integer> userIdDeviceId = new Pair<>(userId, intendingDeviceId);
mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
userId);
userIdDeviceId);
Message clearMessage =
Message.obtain(
mClipboardClearHandler,
ClipboardClearHandler.MSG_CLEAR,
userId,
intendingUid,
userId);
userIdDeviceId);
mClipboardClearHandler.sendMessageDelayed(clearMessage,
getTimeoutForAutoClear());
}
@@ -436,52 +545,57 @@ public class ClipboardService extends SystemService {
@Override
public void clearPrimaryClip(
String callingPackage, String attributionTag, @UserIdInt int userId) {
String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
if (!clipboardAccessAllowed(
AppOpsManager.OP_WRITE_CLIPBOARD,
callingPackage,
attributionTag,
intendingUid,
intendingUserId)) {
intendingUserId,
intendingDeviceId)) {
return;
}
synchronized (mLock) {
mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
userId);
setPrimaryClipInternalLocked(null, intendingUid, callingPackage);
new Pair<>(userId, deviceId));
setPrimaryClipInternalLocked(null, intendingUid, intendingDeviceId, callingPackage);
}
}
@Override
public ClipData getPrimaryClip(String pkg, String attributionTag, @UserIdInt int userId) {
public ClipData getPrimaryClip(
String pkg, String attributionTag, @UserIdInt int userId, int deviceId) {
final int intendingUid = getIntendingUid(pkg, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
if (!clipboardAccessAllowed(
AppOpsManager.OP_READ_CLIPBOARD,
pkg,
attributionTag,
intendingUid,
intendingUserId)
intendingUserId,
intendingDeviceId)
|| isDeviceLocked(intendingUserId)) {
return null;
}
synchronized (mLock) {
try {
addActiveOwnerLocked(intendingUid, pkg);
addActiveOwnerLocked(intendingUid, intendingDeviceId, pkg);
} catch (SecurityException e) {
// Permission could not be granted - URI may be invalid
Slog.i(TAG, "Could not grant permission to primary clip. Clearing clipboard.");
setPrimaryClipInternalLocked(null, intendingUid, pkg);
setPrimaryClipInternalLocked(null, intendingUid, intendingDeviceId, pkg);
return null;
}
PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
notifyTextClassifierLocked(clipboard, pkg, intendingUid);
if (clipboard.primaryClip != null) {
scheduleAutoClear(userId, intendingUid);
scheduleAutoClear(userId, intendingUid, intendingDeviceId);
}
return clipboard.primaryClip;
}
@@ -489,21 +603,23 @@ public class ClipboardService extends SystemService {
@Override
public ClipDescription getPrimaryClipDescription(
String callingPackage, String attributionTag, @UserIdInt int userId) {
String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
if (!clipboardAccessAllowed(
AppOpsManager.OP_READ_CLIPBOARD,
callingPackage,
attributionTag,
intendingUid,
intendingUserId,
intendingDeviceId,
false)
|| isDeviceLocked(intendingUserId)) {
return null;
}
synchronized (mLock) {
PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
return clipboard.primaryClip != null
? clipboard.primaryClip.getDescription() : null;
}
@@ -511,21 +627,23 @@ public class ClipboardService extends SystemService {
@Override
public boolean hasPrimaryClip(
String callingPackage, String attributionTag, @UserIdInt int userId) {
String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
if (!clipboardAccessAllowed(
AppOpsManager.OP_READ_CLIPBOARD,
callingPackage,
attributionTag,
intendingUid,
intendingUserId,
intendingDeviceId,
false)
|| isDeviceLocked(intendingUserId)) {
return false;
}
synchronized (mLock) {
return getClipboardLocked(intendingUserId).primaryClip != null;
return getClipboardLocked(intendingUserId, intendingDeviceId).primaryClip != null;
}
}
@@ -534,11 +652,19 @@ public class ClipboardService extends SystemService {
IOnPrimaryClipChangedListener listener,
String callingPackage,
String attributionTag,
@UserIdInt int userId) {
@UserIdInt int userId,
int deviceId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
if (intendingDeviceId == DEVICE_ID_INVALID) {
Slog.i(TAG, "addPrimaryClipChangedListener invalid deviceId for userId:"
+ userId + " uid:" + intendingUid + " callingPackage:" + callingPackage
+ " requestedDeviceId:" + deviceId);
return;
}
synchronized (mLock) {
getClipboardLocked(intendingUserId)
getClipboardLocked(intendingUserId, intendingDeviceId)
.primaryClipListeners
.register(
listener,
@@ -551,29 +677,41 @@ public class ClipboardService extends SystemService {
IOnPrimaryClipChangedListener listener,
String callingPackage,
String attributionTag,
@UserIdInt int userId) {
@UserIdInt int userId,
int deviceId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = getIntendingUserId(callingPackage, userId);
final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
if (intendingDeviceId == DEVICE_ID_INVALID) {
Slog.i(TAG, "removePrimaryClipChangedListener invalid deviceId for userId:"
+ userId + " uid:" + intendingUid + " callingPackage:" + callingPackage);
return;
}
synchronized (mLock) {
getClipboardLocked(intendingUserId).primaryClipListeners.unregister(listener);
getClipboardLocked(intendingUserId,
intendingDeviceId).primaryClipListeners.unregister(listener);
}
}
@Override
public boolean hasClipboardText(String callingPackage, String attributionTag, int userId) {
public boolean hasClipboardText(
String callingPackage, String attributionTag, int userId, int deviceId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
if (!clipboardAccessAllowed(
AppOpsManager.OP_READ_CLIPBOARD,
callingPackage,
attributionTag,
intendingUid,
intendingUserId,
intendingDeviceId,
false)
|| isDeviceLocked(intendingUserId)) {
return false;
}
synchronized (mLock) {
PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
if (clipboard.primaryClip != null) {
CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
return text != null && text.length() > 0;
@@ -584,23 +722,25 @@ public class ClipboardService extends SystemService {
@Override
public String getPrimaryClipSource(
String callingPackage, String attributionTag, int userId) {
String callingPackage, String attributionTag, int userId, int deviceId) {
getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
"Requires SET_CLIP_SOURCE permission");
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
if (!clipboardAccessAllowed(
AppOpsManager.OP_READ_CLIPBOARD,
callingPackage,
attributionTag,
intendingUid,
intendingUserId,
intendingDeviceId,
false)
|| isDeviceLocked(intendingUserId)) {
return null;
}
synchronized (mLock) {
PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
if (clipboard.primaryClip != null) {
return clipboard.mPrimaryClipPackage;
}
@@ -621,11 +761,13 @@ public class ClipboardService extends SystemService {
case MSG_CLEAR:
final int userId = msg.arg1;
final int intendingUid = msg.arg2;
final int intendingDeviceId = ((Pair<Integer, Integer>) msg.obj).second;
synchronized (mLock) {
if (getClipboardLocked(userId).primaryClip != null) {
if (getClipboardLocked(userId, intendingDeviceId).primaryClip != null) {
FrameworkStatsLog.write(FrameworkStatsLog.CLIPBOARD_CLEARED,
FrameworkStatsLog.CLIPBOARD_CLEARED__SOURCE__AUTO_CLEAR);
setPrimaryClipInternalLocked(null, intendingUid, null);
setPrimaryClipInternalLocked(
null, intendingUid, intendingDeviceId, null);
}
}
break;
@@ -637,13 +779,13 @@ public class ClipboardService extends SystemService {
};
@GuardedBy("mLock")
private PerUserClipboard getClipboardLocked(@UserIdInt int userId) {
PerUserClipboard puc = mClipboards.get(userId);
if (puc == null) {
puc = new PerUserClipboard(userId);
mClipboards.put(userId, puc);
private Clipboard getClipboardLocked(@UserIdInt int userId, int deviceId) {
Clipboard clipboard = mClipboards.get(userId, deviceId);
if (clipboard == null) {
clipboard = new Clipboard(userId, deviceId);
mClipboards.add(userId, deviceId, clipboard);
}
return puc;
return clipboard;
}
List<UserInfo> getRelatedProfiles(@UserIdInt int userId) {
@@ -675,19 +817,20 @@ public class ClipboardService extends SystemService {
void setPrimaryClipInternal(@Nullable ClipData clip, int uid) {
synchronized (mLock) {
setPrimaryClipInternalLocked(clip, uid, null);
setPrimaryClipInternalLocked(clip, uid, DEVICE_ID_DEFAULT, null);
}
}
@GuardedBy("mLock")
private void setPrimaryClipInternalLocked(
@Nullable ClipData clip, int uid, @Nullable String sourcePackage) {
@Nullable ClipData clip, int uid, int deviceId, @Nullable String sourcePackage) {
mEmulatorClipboardMonitor.accept(clip);
final int userId = UserHandle.getUserId(uid);
// Update this user
setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage);
setPrimaryClipInternalLocked(getClipboardLocked(userId, deviceId), clip, uid,
sourcePackage);
// Update related users
List<UserInfo> related = getRelatedProfiles(userId);
@@ -722,7 +865,7 @@ public class ClipboardService extends SystemService {
UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
if (canCopyIntoProfile) {
setPrimaryClipInternalNoClassifyLocked(
getClipboardLocked(id), clip, uid, sourcePackage);
getClipboardLocked(id, deviceId), clip, uid, sourcePackage);
}
}
}
@@ -730,7 +873,7 @@ public class ClipboardService extends SystemService {
}
}
void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip,
void setPrimaryClipInternal(Clipboard clipboard, @Nullable ClipData clip,
int uid) {
synchronized (mLock) {
setPrimaryClipInternalLocked(clipboard, clip, uid, null);
@@ -738,18 +881,18 @@ public class ClipboardService extends SystemService {
}
@GuardedBy("mLock")
private void setPrimaryClipInternalLocked(PerUserClipboard clipboard, @Nullable ClipData clip,
private void setPrimaryClipInternalLocked(Clipboard clipboard, @Nullable ClipData clip,
int uid, @Nullable String sourcePackage) {
final int userId = UserHandle.getUserId(uid);
if (clip != null) {
startClassificationLocked(clip, userId);
startClassificationLocked(clip, userId, clipboard.deviceId);
}
setPrimaryClipInternalNoClassifyLocked(clipboard, clip, uid, sourcePackage);
}
@GuardedBy("mLock")
private void setPrimaryClipInternalNoClassifyLocked(PerUserClipboard clipboard,
private void setPrimaryClipInternalNoClassifyLocked(Clipboard clipboard,
@Nullable ClipData clip, int uid, @Nullable String sourcePackage) {
revokeUris(clipboard);
clipboard.activePermissionOwners.clear();
@@ -775,7 +918,7 @@ public class ClipboardService extends SystemService {
sendClipChangedBroadcast(clipboard);
}
private void sendClipChangedBroadcast(PerUserClipboard clipboard) {
private void sendClipChangedBroadcast(Clipboard clipboard) {
final long ident = Binder.clearCallingIdentity();
final int n = clipboard.primaryClipListeners.beginBroadcast();
try {
@@ -789,7 +932,8 @@ public class ClipboardService extends SystemService {
li.mPackageName,
li.mAttributionTag,
li.mUid,
UserHandle.getUserId(li.mUid))) {
UserHandle.getUserId(li.mUid),
clipboard.deviceId)) {
clipboard.primaryClipListeners.getBroadcastItem(i)
.dispatchPrimaryClipChanged();
}
@@ -805,7 +949,8 @@ public class ClipboardService extends SystemService {
}
@GuardedBy("mLock")
private void startClassificationLocked(@NonNull ClipData clip, @UserIdInt int userId) {
private void startClassificationLocked(@NonNull ClipData clip, @UserIdInt int userId,
int deviceId) {
CharSequence text = (clip.getItemCount() == 0) ? null : clip.getItemAt(0).getText();
if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength) {
clip.getDescription().setClassificationStatus(
@@ -830,12 +975,13 @@ public class ClipboardService extends SystemService {
ClipDescription.CLASSIFICATION_NOT_PERFORMED);
return;
}
mWorkerHandler.post(() -> doClassification(text, clip, classifier, userId));
mWorkerHandler.post(() -> doClassification(text, clip, classifier, userId, deviceId));
}
@WorkerThread
private void doClassification(
CharSequence text, ClipData clip, TextClassifier classifier, @UserIdInt int userId) {
CharSequence text, ClipData clip, TextClassifier classifier, @UserIdInt int userId,
int deviceId) {
TextLinks.Request request = new TextLinks.Request.Builder(text).build();
TextLinks links = classifier.generateLinks(request);
@@ -852,7 +998,7 @@ public class ClipboardService extends SystemService {
}
synchronized (mLock) {
PerUserClipboard clipboard = getClipboardLocked(userId);
Clipboard clipboard = getClipboardLocked(userId, deviceId);
if (clipboard.primaryClip == clip) {
applyClassificationAndSendBroadcastLocked(
clipboard, confidences, links, classifier);
@@ -867,7 +1013,7 @@ public class ClipboardService extends SystemService {
final boolean canCopyIntoProfile = !hasRestriction(
UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
if (canCopyIntoProfile) {
PerUserClipboard relatedClipboard = getClipboardLocked(id);
Clipboard relatedClipboard = getClipboardLocked(id, deviceId);
if (hasTextLocked(relatedClipboard, text)) {
applyClassificationAndSendBroadcastLocked(
relatedClipboard, confidences, links, classifier);
@@ -882,7 +1028,7 @@ public class ClipboardService extends SystemService {
@GuardedBy("mLock")
private void applyClassificationAndSendBroadcastLocked(
PerUserClipboard clipboard, ArrayMap<String, Float> confidences, TextLinks links,
Clipboard clipboard, ArrayMap<String, Float> confidences, TextLinks links,
TextClassifier classifier) {
clipboard.mTextClassifier = classifier;
clipboard.primaryClip.getDescription().setConfidenceScores(confidences);
@@ -893,7 +1039,7 @@ public class ClipboardService extends SystemService {
}
@GuardedBy("mLock")
private boolean hasTextLocked(PerUserClipboard clipboard, @NonNull CharSequence text) {
private boolean hasTextLocked(Clipboard clipboard, @NonNull CharSequence text) {
return clipboard.primaryClip != null
&& clipboard.primaryClip.getItemCount() > 0
&& text.equals(clipboard.primaryClip.getItemAt(0).getText());
@@ -972,7 +1118,7 @@ public class ClipboardService extends SystemService {
}
@GuardedBy("mLock")
private void addActiveOwnerLocked(int uid, String pkg) {
private void addActiveOwnerLocked(int uid, int deviceId, String pkg) {
final IPackageManager pm = AppGlobals.getPackageManager();
final int targetUserHandle = UserHandle.getCallingUserId();
final long oldIdentity = Binder.clearCallingIdentity();
@@ -990,7 +1136,7 @@ public class ClipboardService extends SystemService {
} finally {
Binder.restoreCallingIdentity(oldIdentity);
}
PerUserClipboard clipboard = getClipboardLocked(UserHandle.getUserId(uid));
Clipboard clipboard = getClipboardLocked(UserHandle.getUserId(uid), deviceId);
if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
final int N = clipboard.primaryClip.getItemCount();
for (int i=0; i<N; i++) {
@@ -1025,7 +1171,7 @@ public class ClipboardService extends SystemService {
}
}
private void revokeUris(PerUserClipboard clipboard) {
private void revokeUris(Clipboard clipboard) {
if (clipboard.primaryClip == null) {
return;
}
@@ -1036,8 +1182,14 @@ public class ClipboardService extends SystemService {
}
private boolean clipboardAccessAllowed(
int op, String callingPackage, String attributionTag, int uid, @UserIdInt int userId) {
return clipboardAccessAllowed(op, callingPackage, attributionTag, uid, userId, true);
int op,
String callingPackage,
String attributionTag,
int uid,
@UserIdInt int userId,
int intendingDeviceId) {
return clipboardAccessAllowed(op, callingPackage, attributionTag, uid, userId,
intendingDeviceId, true);
}
private boolean clipboardAccessAllowed(
@@ -1046,6 +1198,7 @@ public class ClipboardService extends SystemService {
String attributionTag,
int uid,
@UserIdInt int userId,
int intendingDeviceId,
boolean shouldNoteOp) {
boolean allowed;
@@ -1053,10 +1206,9 @@ public class ClipboardService extends SystemService {
// First, verify package ownership to ensure use below is safe.
mAppOps.checkPackage(uid, callingPackage);
// Nothing in a virtual session is permitted to touch clipboard contents
if (mVdm != null && mVdm.isAppRunningOnAnyVirtualDevice(uid)) {
if (intendingDeviceId == DEVICE_ID_INVALID) {
Slog.w(TAG, "Clipboard access denied to " + uid + "/" + callingPackage
+ " within a virtual device session");
+ " due to invalid device id");
return false;
}
@@ -1077,7 +1229,8 @@ public class ClipboardService extends SystemService {
// Binder.getCallingUid(). Without checking, the user X can't copy any thing from
// INTERNAL_SYSTEM_WINDOW to the other applications.
if (!allowed) {
allowed = mWm.isUidFocused(uid)
allowed = isDefaultDeviceAndUidFocused(intendingDeviceId, uid)
|| isVirtualDeviceAndUidFocused(intendingDeviceId, uid)
|| isInternalSysWindowAppWithWindowFocus(callingPackage);
}
if (!allowed && mContentCaptureInternal != null) {
@@ -1098,6 +1251,12 @@ public class ClipboardService extends SystemService {
// userId must pass intending userId. i.e. user#10.
allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
}
if (!allowed && intendingDeviceId != DEVICE_ID_DEFAULT) {
// Privileged apps which own a VirtualDevice are allowed to read its clipboard
// in the background.
allowed = (mVdmInternal != null)
&& mVdmInternal.getDeviceOwnerUid(intendingDeviceId) == uid;
}
break;
case AppOpsManager.OP_WRITE_CLIPBOARD:
// Writing is allowed without focus.
@@ -1123,6 +1282,19 @@ public class ClipboardService extends SystemService {
return appOpsResult == AppOpsManager.MODE_ALLOWED;
}
private boolean isDefaultDeviceAndUidFocused(int intendingDeviceId, int uid) {
return intendingDeviceId == DEVICE_ID_DEFAULT && mWm.isUidFocused(uid);
}
private boolean isVirtualDeviceAndUidFocused(int intendingDeviceId, int uid) {
if (intendingDeviceId == DEVICE_ID_DEFAULT || mVdm == null) {
return false;
}
int topFocusedDisplayId = mWm.getTopFocusedDisplayId();
int focusedDeviceId = mVdm.getDeviceIdForDisplayId(topFocusedDisplayId);
return (focusedDeviceId == intendingDeviceId) && mWm.isUidFocused(uid);
}
private boolean isDefaultIme(int userId, String packageName) {
String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
Settings.Secure.DEFAULT_INPUT_METHOD, userId);
@@ -1141,7 +1313,7 @@ public class ClipboardService extends SystemService {
*/
@GuardedBy("mLock")
private void showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId,
PerUserClipboard clipboard) {
Clipboard clipboard) {
if (clipboard.primaryClip == null) {
return;
}
@@ -1174,8 +1346,8 @@ public class ClipboardService extends SystemService {
if (clipboard.mNotifiedUids.get(uid)) {
return;
}
clipboard.mNotifiedUids.put(uid, true);
final ArraySet<Context> toastContexts = getToastContexts(clipboard);
Binder.withCleanCallingIdentity(() -> {
try {
CharSequence callingAppLabel = mPm.getApplicationLabel(
@@ -1183,23 +1355,72 @@ public class ClipboardService extends SystemService {
String message =
getContext().getString(R.string.pasted_from_clipboard, callingAppLabel);
Slog.i(TAG, message);
Toast toastToShow;
if (SafetyProtectionUtils.shouldShowSafetyProtectionResources(getContext())) {
Drawable safetyProtectionIcon = getContext()
.getDrawable(R.drawable.ic_safety_protection);
toastToShow = Toast.makeCustomToastWithIcon(getContext(),
UiThread.get().getLooper(), message,
Toast.LENGTH_SHORT, safetyProtectionIcon);
} else {
toastToShow = Toast.makeText(
getContext(), UiThread.get().getLooper(), message,
Toast.LENGTH_SHORT);
for (int i = 0; i < toastContexts.size(); i++) {
Context toastContext = toastContexts.valueAt(i);
Toast toastToShow;
if (SafetyProtectionUtils.shouldShowSafetyProtectionResources(getContext())) {
Drawable safetyProtectionIcon = getContext()
.getDrawable(R.drawable.ic_safety_protection);
toastToShow = Toast.makeCustomToastWithIcon(toastContext,
UiThread.get().getLooper(), message,
Toast.LENGTH_SHORT, safetyProtectionIcon);
} else {
toastToShow = Toast.makeText(
toastContext, UiThread.get().getLooper(), message,
Toast.LENGTH_SHORT);
}
toastToShow.show();
}
toastToShow.show();
} catch (PackageManager.NameNotFoundException e) {
// do nothing
}
});
clipboard.mNotifiedUids.put(uid, true);
}
/**
* Returns the context(s) to use for toasts related to this clipboard. Normally this will just
* contain a single context referencing the default display.
*
* If the clipboard is for a VirtualDevice, we attempt to return the single DisplayContext for
* the focused VirtualDisplay for that device, but might need to return the contexts for
* multiple displays if the VirtualDevice has several but none of them were focused.
*/
private ArraySet<Context> getToastContexts(Clipboard clipboard) throws IllegalStateException {
ArraySet<Context> contexts = new ArraySet<>();
if (clipboard.deviceId != DEVICE_ID_DEFAULT) {
DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
int topFocusedDisplayId = mWm.getTopFocusedDisplayId();
ArraySet<Integer> displayIds = mVdmInternal.getDisplayIdsForDevice(clipboard.deviceId);
if (displayIds.contains(topFocusedDisplayId)) {
Display display = displayManager.getDisplay(topFocusedDisplayId);
if (display != null) {
contexts.add(getContext().createDisplayContext(display));
return contexts;
}
}
for (int i = 0; i < displayIds.size(); i++) {
Display display = displayManager.getDisplay(displayIds.valueAt(i));
if (display != null) {
contexts.add(getContext().createDisplayContext(display));
}
}
if (!contexts.isEmpty()) {
return contexts;
}
Slog.e(TAG, "getToastContexts Couldn't find any VirtualDisplays for VirtualDevice "
+ clipboard.deviceId);
// Since we couldn't find any VirtualDisplays to use at all, just fall through to using
// the default display below.
}
contexts.add(getContext());
return contexts;
}
/**
@@ -1221,7 +1442,7 @@ public class ClipboardService extends SystemService {
/** Potentially notifies the text classifier that an app is accessing a text clip. */
@GuardedBy("mLock")
private void notifyTextClassifierLocked(
PerUserClipboard clipboard, String callingPackage, int callingUid) {
Clipboard clipboard, String callingPackage, int callingUid) {
if (clipboard.primaryClip == null) {
return;
}

View File

@@ -20,6 +20,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.companion.virtual.IVirtualDevice;
import android.os.LocaleList;
import android.util.ArraySet;
import java.util.Set;
@@ -84,7 +85,7 @@ public abstract class VirtualDeviceManagerInternal {
* *Note* this only checks VirtualDevices, and does not include information about whether
* the app is running on the default device or not.
*/
public abstract @NonNull Set<Integer> getDeviceIdsForUid(int uid);
public abstract @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid);
/**
* Notifies that a virtual display is created.
@@ -132,4 +133,12 @@ public abstract class VirtualDeviceManagerInternal {
* Returns true if the {@code displayId} is owned by any virtual device
*/
public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId);
/**
* Gets the ids of VirtualDisplays owned by a VirtualDevice.
*
* @param deviceId which device we're asking about
* @return the set of display ids for all VirtualDisplays owned by the device
*/
public abstract @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId);
}

View File

@@ -1595,6 +1595,33 @@ public class VirtualDeviceManagerServiceTest {
verify(mSoundEffectListener).onPlaySoundEffect(AudioManager.FX_KEY_CLICK);
}
@Test
public void getDisplayIdsForDevice_invalidDeviceId_emptyResult() {
ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_2);
assertThat(displayIds).isEmpty();
}
@Test
public void getDisplayIdsForDevice_noDisplays_emptyResult() {
ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
assertThat(displayIds).isEmpty();
}
@Test
public void getDisplayIdsForDevice_oneDisplay_resultContainsCorrectDisplayId() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
assertThat(displayIds).containsExactly(DISPLAY_ID_1);
}
@Test
public void getDisplayIdsForDevice_twoDisplays_resultContainsCorrectDisplayIds() {
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_2);
ArraySet<Integer> displayIds = mLocalService.getDisplayIdsForDevice(VIRTUAL_DEVICE_ID_1);
assertThat(displayIds).containsExactly(DISPLAY_ID_1, DISPLAY_ID_2);
}
private VirtualDeviceImpl createVirtualDevice(int virtualDeviceId, int ownerUid) {
VirtualDeviceParams params = new VirtualDeviceParams.Builder()
.setBlockedActivities(getBlockedActivities())