diff options
| author | 2022-12-15 14:34:34 -0800 | |
|---|---|---|
| committer | 2023-01-20 22:01:29 +0000 | |
| commit | 152e9397237ad280baba4ad1f012b841191fe87e (patch) | |
| tree | 16203bec46d1a89eaefc5a486f48e118f84dbcef | |
| parent | e2035b01375653d4a1a5f86f3890f64d72bbdbd8 (diff) | |
VirtualDevice clipboard support
This CL refactors ClipboardService to provide siloed clipboard storage
for VirtualDevices, adding an additional dimension for selecting clip
storage alongside the existing userId dimension.
This behavior is turned off by default behind a DeviceConfig flag.
Bug: 262038440
Test: atest ClipboardManagerTest ClipboardManagerListenerTest StreamedAppClipboardTest
Change-Id: I88de5a9930448f741169cd48d3804a9d936dfa76
7 files changed, 414 insertions, 114 deletions
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java index 85af87722ed2..06a3b1e1cc66 100644 --- a/core/java/android/content/ClipboardManager.java +++ b/core/java/android/content/ClipboardManager.java @@ -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(); } diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl index 46ece2bc3f5e..b2216d4953e6 100644 --- a/core/java/android/content/IClipboard.aidl +++ b/core/java/android/content/IClipboard.aidl @@ -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); } 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 5985ce45e4d5..3100277eb762 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -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; 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 2395814cc4bf..f39c32df1844 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -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(); @@ -587,6 +586,20 @@ public class VirtualDeviceManagerService extends SystemService { } @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) { synchronized (mVirtualDeviceManagerLock) { diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 6f9a17682dd7..ecff0a790559 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -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; } } @@ -333,6 +385,54 @@ public class ClipboardService extends SystemService { } /** + * 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. * * userId means that comes from the calling side and should be validated by @@ -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; } 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 974c04bc45f5..001cb10e1b29 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -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); } 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 dad7977a8bb3..3791b3527b66 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 @@ -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()) |