• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.clipboard;
18 
19 import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
20 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
21 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_CLIPBOARD;
22 import static android.content.ClipDescription.MIMETYPE_UNKNOWN;
23 import static android.content.ClipDescription.MIMETYPE_TEXT_PLAIN;
24 import static android.content.ClipDescription.MIMETYPE_TEXT_HTML;
25 import static android.content.ClipDescription.MIMETYPE_TEXT_URILIST;
26 import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
27 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
28 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT;
29 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
30 import static android.content.Context.DEVICE_ID_DEFAULT;
31 import static android.content.Context.DEVICE_ID_INVALID;
32 
33 import static com.android.internal.util.FrameworkStatsLog.CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_UNKNOWN;
34 import static com.android.internal.util.FrameworkStatsLog.CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_TEXT_PLAIN;
35 import static com.android.internal.util.FrameworkStatsLog.CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_TEXT_HTML;
36 import static com.android.internal.util.FrameworkStatsLog.CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_TEXT_URILIST;
37 import static com.android.internal.util.FrameworkStatsLog.CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_TEXT_INTENT;
38 import static com.android.internal.util.FrameworkStatsLog.CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_APPLICATION_ACTIVITY;
39 import static com.android.internal.util.FrameworkStatsLog.CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_APPLICATION_SHORTCUT;
40 import static com.android.internal.util.FrameworkStatsLog.CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_APPLICATION_TASK;
41 import static com.android.server.clipboard.Flags.clipboardGetEventLogging;
42 
43 import android.Manifest;
44 import android.annotation.NonNull;
45 import android.annotation.Nullable;
46 import android.annotation.UserIdInt;
47 import android.annotation.WorkerThread;
48 import android.app.ActivityManagerInternal;
49 import android.app.AppOpsManager;
50 import android.app.IUriGrantsManager;
51 import android.app.KeyguardManager;
52 import android.app.UriGrantsManager;
53 import android.companion.virtual.VirtualDeviceManager;
54 import android.content.BroadcastReceiver;
55 import android.content.ClipData;
56 import android.content.ClipDescription;
57 import android.content.ClipboardManager;
58 import android.content.ComponentName;
59 import android.content.ContentProvider;
60 import android.content.ContentResolver;
61 import android.content.Context;
62 import android.content.IClipboard;
63 import android.content.IOnPrimaryClipChangedListener;
64 import android.content.Intent;
65 import android.content.pm.PackageManager;
66 import android.content.pm.PackageManagerInternal;
67 import android.content.pm.UserInfo;
68 import android.graphics.drawable.Drawable;
69 import android.hardware.display.DisplayManager;
70 import android.net.Uri;
71 import android.os.Binder;
72 import android.os.Build;
73 import android.os.Bundle;
74 import android.os.Handler;
75 import android.os.HandlerThread;
76 import android.os.IBinder;
77 import android.os.IUserManager;
78 import android.os.Looper;
79 import android.os.Message;
80 import android.os.Parcel;
81 import android.os.RemoteCallbackList;
82 import android.os.RemoteException;
83 import android.os.ServiceManager;
84 import android.os.UserHandle;
85 import android.os.UserManager;
86 import android.provider.DeviceConfig;
87 import android.provider.Settings;
88 import android.text.TextUtils;
89 import android.util.ArrayMap;
90 import android.util.ArraySet;
91 import android.util.IntArray;
92 import android.util.Pair;
93 import android.util.SafetyProtectionUtils;
94 import android.util.Slog;
95 import android.util.SparseArrayMap;
96 import android.util.SparseBooleanArray;
97 import android.view.Display;
98 import android.view.autofill.AutofillManagerInternal;
99 import android.view.textclassifier.TextClassificationContext;
100 import android.view.textclassifier.TextClassificationManager;
101 import android.view.textclassifier.TextClassifier;
102 import android.view.textclassifier.TextClassifierEvent;
103 import android.view.textclassifier.TextLinks;
104 import android.widget.Toast;
105 
106 import com.android.internal.R;
107 import com.android.internal.annotations.GuardedBy;
108 import com.android.internal.annotations.VisibleForTesting;
109 import com.android.internal.util.FrameworkStatsLog;
110 import com.android.server.LocalServices;
111 import com.android.server.SystemService;
112 import com.android.server.UiThread;
113 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
114 import com.android.server.contentcapture.ContentCaptureManagerInternal;
115 import com.android.server.uri.UriGrantsManagerInternal;
116 import com.android.server.wm.WindowManagerInternal;
117 
118 import java.util.HashSet;
119 import java.util.List;
120 import java.util.concurrent.TimeUnit;
121 import java.util.function.Consumer;
122 
123 /**
124  * Implementation of the clipboard for copy and paste.
125  * <p>
126  * Caution: exception for clipboard data and isInternalSysWindowAppWithWindowFocus, any of data
127  * is accessed by userId or uid should be in * the try segment between
128  * Binder.clearCallingIdentity and Binder.restoreCallingIdentity.
129  * </p>
130  */
131 public class ClipboardService extends SystemService {
132 
133     private static final String TAG = "ClipboardService";
134 
135     @VisibleForTesting
136     public static final long DEFAULT_CLIPBOARD_TIMEOUT_MILLIS = 3600000;
137 
138     /**
139      * Device config property for whether clipboard auto clear is enabled on the device
140      **/
141     public static final String PROPERTY_AUTO_CLEAR_ENABLED =
142             "auto_clear_enabled";
143 
144     /**
145      * Device config property for time period in milliseconds after which clipboard is auto
146      * cleared
147      **/
148     public static final String PROPERTY_AUTO_CLEAR_TIMEOUT =
149             "auto_clear_timeout";
150 
151     // DeviceConfig properties
152     private static final String PROPERTY_MAX_CLASSIFICATION_LENGTH = "max_classification_length";
153     private static final int DEFAULT_MAX_CLASSIFICATION_LENGTH = 400;
154 
155     private static final int[] CLIP_DATA_TYPES_UNKNOWN = {
156             CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_UNKNOWN
157     };
158 
159     private final ActivityManagerInternal mAmInternal;
160     private final IUriGrantsManager mUgm;
161     private final UriGrantsManagerInternal mUgmInternal;
162     private final WindowManagerInternal mWm;
163     private final VirtualDeviceManagerInternal mVdmInternal;
164     private final VirtualDeviceManager mVdm;
165     private BroadcastReceiver mVirtualDeviceRemovedReceiver;
166     private VirtualDeviceManager.VirtualDeviceListener mVirtualDeviceListener;
167     private final IUserManager mUm;
168     private final PackageManager mPm;
169     private final AppOpsManager mAppOps;
170     private final ContentCaptureManagerInternal mContentCaptureInternal;
171     private final AutofillManagerInternal mAutofillInternal;
172     private final IBinder mPermissionOwner;
173     private final Consumer<ClipData> mClipboardMonitor;
174     private final Handler mWorkerHandler;
175 
176     @GuardedBy("mLock")
177     // Maps (userId, deviceId) to Clipboard.
178     private final SparseArrayMap<Integer, Clipboard> mClipboards = new SparseArrayMap<>();
179 
180     @GuardedBy("mLock")
181     private boolean mShowAccessNotifications =
182             ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS;
183     @GuardedBy("mLock")
184     private boolean mAllowVirtualDeviceSilos =
185             ClipboardManager.DEVICE_CONFIG_DEFAULT_ALLOW_VIRTUALDEVICE_SILOS;
186 
187     @GuardedBy("mLock")
188     private int mMaxClassificationLength = DEFAULT_MAX_CLASSIFICATION_LENGTH;
189 
190     private final Object mLock = new Object();
191 
192     /**
193      * Instantiates the clipboard.
194      */
ClipboardService(Context context)195     public ClipboardService(Context context) {
196         super(context);
197 
198         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
199         mUgm = UriGrantsManager.getService();
200         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
201         mWm = LocalServices.getService(WindowManagerInternal.class);
202         // Can be null; not all products have CDM + VirtualDeviceManager
203         mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
204         mVdm = (mVdmInternal == null) ? null : getContext().getSystemService(
205                 VirtualDeviceManager.class);
206         mPm = getContext().getPackageManager();
207         mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
208         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
209         mContentCaptureInternal = LocalServices.getService(ContentCaptureManagerInternal.class);
210         mAutofillInternal = LocalServices.getService(AutofillManagerInternal.class);
211         final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
212         mPermissionOwner = permOwner;
213         if (Build.IS_EMULATOR) {
214             mClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
215                 synchronized (mLock) {
216                     Clipboard clipboard = getClipboardLocked(0, DEVICE_ID_DEFAULT);
217                     if (clipboard != null) {
218                         setPrimaryClipInternalLocked(clipboard, clip, android.os.Process.SYSTEM_UID,
219                                 null);
220                     }
221                 }
222             });
223         } else if (Build.IS_ARC) {
224             mClipboardMonitor = new ArcClipboardMonitor((clip, uid) -> {
225                 setPrimaryClipInternal(clip, uid);
226             });
227         } else {
228             mClipboardMonitor = (clip) -> {};
229         }
230 
231         updateConfig();
232         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD,
233                 getContext().getMainExecutor(), properties -> updateConfig());
234 
235         HandlerThread workerThread = new HandlerThread(TAG);
236         workerThread.start();
237         mWorkerHandler = workerThread.getThreadHandler();
238     }
239 
240     @Override
onStart()241     public void onStart() {
242         publishBinderService(Context.CLIPBOARD_SERVICE, new ClipboardImpl());
243         registerVirtualDeviceListener();
244     }
245 
registerVirtualDeviceListener()246     private void registerVirtualDeviceListener() {
247         if (mVdm == null || mVirtualDeviceListener != null) {
248             return;
249         }
250         mVirtualDeviceListener = new VirtualDeviceManager.VirtualDeviceListener() {
251             @Override
252             public void onVirtualDeviceClosed(int deviceId) {
253                 synchronized (mLock) {
254                     for (int i = mClipboards.numMaps() - 1; i >= 0; i--) {
255                         mClipboards.delete(mClipboards.keyAt(i), deviceId);
256                     }
257                 }
258             }
259         };
260         mVdm.registerVirtualDeviceListener(getContext().getMainExecutor(), mVirtualDeviceListener);
261     }
262 
263     @Override
onUserStopped(@onNull TargetUser user)264     public void onUserStopped(@NonNull TargetUser user) {
265         synchronized (mLock) {
266             mClipboards.delete(user.getUserIdentifier());
267         }
268     }
269 
updateConfig()270     private void updateConfig() {
271         synchronized (mLock) {
272             mShowAccessNotifications = DeviceConfig.getBoolean(
273                     DeviceConfig.NAMESPACE_CLIPBOARD,
274                     ClipboardManager.DEVICE_CONFIG_SHOW_ACCESS_NOTIFICATIONS,
275                     ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS);
276             mAllowVirtualDeviceSilos = DeviceConfig.getBoolean(
277                     DeviceConfig.NAMESPACE_CLIPBOARD,
278                     ClipboardManager.DEVICE_CONFIG_ALLOW_VIRTUALDEVICE_SILOS,
279                     ClipboardManager.DEVICE_CONFIG_DEFAULT_ALLOW_VIRTUALDEVICE_SILOS);
280             mMaxClassificationLength = DeviceConfig.getInt(DeviceConfig.NAMESPACE_CLIPBOARD,
281                     PROPERTY_MAX_CLASSIFICATION_LENGTH, DEFAULT_MAX_CLASSIFICATION_LENGTH);
282         }
283     }
284 
285     private class ListenerInfo {
286         final int mUid;
287         final String mPackageName;
288         final String mAttributionTag;
289 
ListenerInfo(int uid, String packageName, String attributionTag)290         ListenerInfo(int uid, String packageName, String attributionTag) {
291             mUid = uid;
292             mPackageName = packageName;
293             mAttributionTag = attributionTag;
294         }
295     }
296 
297     private static class Clipboard {
298         public final int userId;
299         public final int deviceId;
300 
301         final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
302                 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
303 
304         /** Current primary clip. */
305         ClipData primaryClip;
306         /** UID that set {@link #primaryClip}. */
307         int primaryClipUid = android.os.Process.NOBODY_UID;
308         /** Package of the app that set {@link #primaryClip}. */
309         String mPrimaryClipPackage;
310 
311         /** Uids that have already triggered a toast notification for {@link #primaryClip} */
312         final SparseBooleanArray mNotifiedUids = new SparseBooleanArray();
313 
314         /**
315          * Uids that have already triggered a notification to text classifier for
316          * {@link #primaryClip}.
317          */
318         final SparseBooleanArray mNotifiedTextClassifierUids = new SparseBooleanArray();
319 
320         final HashSet<String> activePermissionOwners
321                 = new HashSet<String>();
322 
323         /** The text classifier session that is used to annotate the text in the primary clip. */
324         TextClassifier mTextClassifier;
325 
Clipboard(int userId, int deviceId)326         Clipboard(int userId, int deviceId) {
327             this.userId = userId;
328             this.deviceId = deviceId;
329         }
330     }
331 
332     /**
333      * To check if the application has granted the INTERNAL_SYSTEM_WINDOW permission and window
334      * focus.
335      * <p>
336      * All of applications granted INTERNAL_SYSTEM_WINDOW has the risk to leak clip information to
337      * the other user because INTERNAL_SYSTEM_WINDOW is signature level. i.e. platform key. Because
338      * some of applications have both of INTERNAL_SYSTEM_WINDOW and INTERACT_ACROSS_USERS_FULL at
339      * the same time, that means they show the same window to all of users.
340      * </p><p>
341      * Unfortunately, all of applications with INTERNAL_SYSTEM_WINDOW starts very early and then
342      * the real window show is belong to user 0 rather user X. The result of
343      * WindowManager.isUidFocused checking user X window is false.
344      * </p>
345      * @return true if the app granted INTERNAL_SYSTEM_WINDOW permission.
346      */
isInternalSysWindowAppWithWindowFocus(String callingPackage)347     private boolean isInternalSysWindowAppWithWindowFocus(String callingPackage) {
348         // Shell can access the clipboard for testing purposes.
349         if (mPm.checkPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW,
350                     callingPackage) == PackageManager.PERMISSION_GRANTED) {
351             if (mWm.isUidFocused(Binder.getCallingUid())) {
352                 return true;
353             }
354         }
355 
356         return false;
357     }
358 
359     /**
360      * To get the validate current userId.
361      * <p>
362      * The intending userId needs to be validated by ActivityManagerInternal.handleIncomingUser.
363      * To check if the uid of the process have the permission to run as the userId.
364      * e.x. INTERACT_ACROSS_USERS_FULL or INTERACT_ACROSS_USERS permission granted.
365      * </p>
366      * <p>
367      * The application with the granted INTERNAL_SYSTEM_WINDOW permission should run as the output
368      * of ActivityManagerInternal.handleIncomingUser rather the userId of Binder.getCAllingUid().
369      * To use the userId of Binder.getCallingUid() is the root cause that leaks the information
370      * comes from user 0 to user X.
371      * </p>
372      *
373      * @param packageName the package name of the calling side
374      * @param userId the userId passed by the calling side
375      * @return return the intending userId that has been validated by ActivityManagerInternal.
376      */
377     @UserIdInt
getIntendingUserId(String packageName, @UserIdInt int userId)378     private int getIntendingUserId(String packageName, @UserIdInt int userId) {
379         final int callingUid = Binder.getCallingUid();
380         final int callingUserId = UserHandle.getUserId(callingUid);
381         if (!UserManager.supportsMultipleUsers() || callingUserId == userId) {
382             return callingUserId;
383         }
384 
385         int intendingUserId = callingUserId;
386         intendingUserId = mAmInternal.handleIncomingUser(Binder.getCallingPid(),
387                 Binder.getCallingUid(), userId, false /* allow all */, ALLOW_FULL_ONLY,
388                 "checkClipboardServiceCallingUser", packageName);
389 
390         return intendingUserId;
391     }
392 
393     /**
394      * To get the current running uid who is intend to run as.
395      * In ording to distinguish the nameing and reducing the confusing names, the client client
396      * side pass userId that is intend to run as,
397      * @return return IntentingUid = validated intenting userId +
398      *         UserHandle.getAppId(Binder.getCallingUid())
399      */
getIntendingUid(String packageName, @UserIdInt int userId)400     private int getIntendingUid(String packageName, @UserIdInt int userId) {
401         return UserHandle.getUid(getIntendingUserId(packageName, userId),
402                 UserHandle.getAppId(Binder.getCallingUid()));
403     }
404 
405     /**
406      * Determines which deviceId to use for selecting a Clipboard, depending on where a given app
407      * is running and the device's clipboard policy.
408      *
409      * @param requestedDeviceId the requested deviceId passed in from the client side
410      * @param uid the intended app uid
411      * @return a deviceId to use in selecting the appropriate clipboard, or
412      * DEVICE_ID_INVALID if this uid should not be allowed access. A value of DEVICE_ID_DEFAULT
413      * means just use the "regular" clipboard.
414      */
getIntendingDeviceId(int requestedDeviceId, int uid)415     private int getIntendingDeviceId(int requestedDeviceId, int uid) {
416         if (mVdmInternal == null) {
417             return DEVICE_ID_DEFAULT;
418         }
419 
420         ArraySet<Integer> virtualDeviceIds = mVdmInternal.getDeviceIdsForUid(uid);
421 
422         synchronized (mLock) {
423             if (!mAllowVirtualDeviceSilos
424                     && (!virtualDeviceIds.isEmpty() || requestedDeviceId != DEVICE_ID_DEFAULT)) {
425                 return DEVICE_ID_INVALID;
426             }
427         }
428 
429         // If an app is running on any VirtualDevice, it isn't clear which clipboard they
430         // should use, unless all of the devices share the default device's clipboard.
431         boolean allDevicesHaveDefaultClipboard = true;
432         for (int deviceId : virtualDeviceIds) {
433             if (!deviceUsesDefaultClipboard(deviceId)) {
434                 allDevicesHaveDefaultClipboard = false;
435                 break;
436             }
437         }
438 
439         // Apps running on a virtual device may get the default clipboard if all the devices the app
440         // runs on share that clipboard. Otherwise it's not clear which clipboard to use.
441         if (requestedDeviceId == DEVICE_ID_DEFAULT) {
442             return allDevicesHaveDefaultClipboard ? DEVICE_ID_DEFAULT : DEVICE_ID_INVALID;
443         }
444 
445         // At this point the app wants to access a virtual device clipboard. It may do so if:
446         //  1. The app owns the VirtualDevice
447         //  2. The app is present on the VirtualDevice
448         //  3. The VirtualDevice shares the default device clipboard and all virtual devices that
449         //     the app is running on do the same.
450         int clipboardDeviceId = deviceUsesDefaultClipboard(requestedDeviceId)
451                 ? DEVICE_ID_DEFAULT
452                 : requestedDeviceId;
453 
454         if (mVdmInternal.getDeviceOwnerUid(requestedDeviceId) == uid
455                 || virtualDeviceIds.contains(requestedDeviceId)
456                 || (clipboardDeviceId == DEVICE_ID_DEFAULT && allDevicesHaveDefaultClipboard)) {
457             return clipboardDeviceId;
458         }
459 
460         // Fallback to the device where the app is running, unless it uses the default clipboard.
461         int fallbackDeviceId = virtualDeviceIds.valueAt(0);
462         return deviceUsesDefaultClipboard(fallbackDeviceId) ? DEVICE_ID_DEFAULT : fallbackDeviceId;
463     }
464 
deviceUsesDefaultClipboard(int deviceId)465     private boolean deviceUsesDefaultClipboard(int deviceId) {
466         if (deviceId == DEVICE_ID_DEFAULT || mVdm == null) {
467             return true;
468         }
469         return mVdm.getDevicePolicy(deviceId, POLICY_TYPE_CLIPBOARD) == DEVICE_POLICY_CUSTOM;
470     }
471 
472     /**
473      * To handle the difference between userId and intendingUserId, uid and intendingUid.
474      *
475      * userId means that comes from the calling side and should be validated by
476      * ActivityManagerInternal.handleIncomingUser.
477      * After validation of ActivityManagerInternal.handleIncomingUser, the userId is called
478      * 'intendingUserId' and the uid is called 'intendingUid'.
479      */
480     private class ClipboardImpl extends IClipboard.Stub {
481 
482         private final Handler mClipboardClearHandler = new ClipboardClearHandler(
483                 mWorkerHandler.getLooper());
484 
485         @Override
onTransact(int code, Parcel data, Parcel reply, int flags)486         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
487                 throws RemoteException {
488             try {
489                 return super.onTransact(code, data, reply, flags);
490             } catch (RuntimeException e) {
491                 if (!(e instanceof SecurityException)) {
492                     Slog.wtf("clipboard", "Exception: ", e);
493                 }
494                 throw e;
495             }
496 
497         }
498 
499         @Override
setPrimaryClip( ClipData clip, String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId)500         public void setPrimaryClip(
501                 ClipData clip,
502                 String callingPackage,
503                 String attributionTag,
504                 @UserIdInt int userId,
505                 int deviceId) {
506             checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, deviceId,
507                     callingPackage);
508         }
509 
510         @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
511         @Override
setPrimaryClipAsPackage( ClipData clip, String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId, String sourcePackage)512         public void setPrimaryClipAsPackage(
513                 ClipData clip,
514                 String callingPackage,
515                 String attributionTag,
516                 @UserIdInt int userId,
517                 int deviceId,
518                 String sourcePackage) {
519             setPrimaryClipAsPackage_enforcePermission();
520             checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, deviceId,
521                     sourcePackage);
522         }
523 
524         @Override
areClipboardAccessNotificationsEnabledForUser(int userId)525         public boolean areClipboardAccessNotificationsEnabledForUser(int userId) {
526             int result = getContext().checkCallingOrSelfPermission(
527                     Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION);
528             if (result != PackageManager.PERMISSION_GRANTED) {
529                 throw new SecurityException("areClipboardAccessNotificationsEnable requires "
530                         + "permission MANAGE_CLIPBOARD_ACCESS_NOTIFICATION");
531             }
532 
533             long callingId = Binder.clearCallingIdentity();
534             try {
535                 return Settings.Secure.getIntForUser(getContext().getContentResolver(),
536                         Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
537                         getDefaultClipboardAccessNotificationsSetting(), userId) != 0;
538             } finally {
539                 Binder.restoreCallingIdentity(callingId);
540             }
541         }
542 
543         @Override
setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId)544         public void setClipboardAccessNotificationsEnabledForUser(boolean enable, int userId) {
545             int result = getContext().checkCallingOrSelfPermission(
546                     Manifest.permission.MANAGE_CLIPBOARD_ACCESS_NOTIFICATION);
547             if (result != PackageManager.PERMISSION_GRANTED) {
548                 throw new SecurityException("areClipboardAccessNotificationsEnable requires "
549                         + "permission MANAGE_CLIPBOARD_ACCESS_NOTIFICATION");
550             }
551 
552             long callingId = Binder.clearCallingIdentity();
553             try {
554                 ContentResolver resolver = getContext()
555                         .createContextAsUser(UserHandle.of(userId), 0).getContentResolver();
556                 Settings.Secure.putInt(resolver,
557                         Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS, (enable ? 1 : 0));
558             } finally {
559                 Binder.restoreCallingIdentity(callingId);
560             }
561         }
562 
getDefaultClipboardAccessNotificationsSetting()563         private int getDefaultClipboardAccessNotificationsSetting() {
564             return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
565                     ClipboardManager.DEVICE_CONFIG_SHOW_ACCESS_NOTIFICATIONS,
566                     ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS) ? 1 : 0;
567         }
568 
checkAndSetPrimaryClip( ClipData clip, String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId, String sourcePackage)569         private void checkAndSetPrimaryClip(
570                 ClipData clip,
571                 String callingPackage,
572                 String attributionTag,
573                 @UserIdInt int userId,
574                 int deviceId,
575                 String sourcePackage) {
576             if (clip == null || clip.getItemCount() <= 0) {
577                 throw new IllegalArgumentException("No items");
578             }
579             final int intendingUid = getIntendingUid(callingPackage, userId);
580             final int intendingUserId = UserHandle.getUserId(intendingUid);
581             final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
582             if (!clipboardAccessAllowed(
583                     AppOpsManager.OP_WRITE_CLIPBOARD,
584                     callingPackage,
585                     attributionTag,
586                     intendingUid,
587                     intendingUserId,
588                     intendingDeviceId)) {
589                 return;
590             }
591             checkDataOwner(clip, intendingUid);
592             synchronized (mLock) {
593                 scheduleAutoClear(userId, intendingUid, intendingDeviceId);
594                 setPrimaryClipInternalLocked(clip, intendingUid, intendingDeviceId, sourcePackage);
595             }
596         }
597 
scheduleAutoClear( @serIdInt int userId, int intendingUid, int intendingDeviceId)598         private void scheduleAutoClear(
599                 @UserIdInt int userId, int intendingUid, int intendingDeviceId) {
600             final long oldIdentity = Binder.clearCallingIdentity();
601             try {
602                 if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
603                         PROPERTY_AUTO_CLEAR_ENABLED, true)) {
604                     Pair<Integer, Integer> userIdDeviceId = new Pair<>(userId, intendingDeviceId);
605                     mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
606                             userIdDeviceId);
607                     Message clearMessage =
608                             Message.obtain(
609                                     mClipboardClearHandler,
610                                     ClipboardClearHandler.MSG_CLEAR,
611                                     userId,
612                                     intendingUid,
613                                     userIdDeviceId);
614                     mClipboardClearHandler.sendMessageDelayed(clearMessage,
615                             getTimeoutForAutoClear());
616                 }
617             } finally {
618                 Binder.restoreCallingIdentity(oldIdentity);
619             }
620         }
621 
getTimeoutForAutoClear()622         private long getTimeoutForAutoClear() {
623             return DeviceConfig.getLong(DeviceConfig.NAMESPACE_CLIPBOARD,
624                     PROPERTY_AUTO_CLEAR_TIMEOUT,
625                     DEFAULT_CLIPBOARD_TIMEOUT_MILLIS);
626         }
627 
628         @Override
clearPrimaryClip( String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId)629         public void clearPrimaryClip(
630                 String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId) {
631             final int intendingUid = getIntendingUid(callingPackage, userId);
632             final int intendingUserId = UserHandle.getUserId(intendingUid);
633             final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
634             if (!clipboardAccessAllowed(
635                     AppOpsManager.OP_WRITE_CLIPBOARD,
636                     callingPackage,
637                     attributionTag,
638                     intendingUid,
639                     intendingUserId,
640                     intendingDeviceId)) {
641                 return;
642             }
643             synchronized (mLock) {
644                 mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
645                         new Pair<>(userId, deviceId));
646                 setPrimaryClipInternalLocked(null, intendingUid, intendingDeviceId, callingPackage);
647             }
648         }
649 
650         @Override
getPrimaryClip( String pkg, String attributionTag, @UserIdInt int userId, int deviceId)651         public ClipData getPrimaryClip(
652                 String pkg, String attributionTag, @UserIdInt int userId, int deviceId) {
653             final int intendingUid = getIntendingUid(pkg, userId);
654             final int intendingUserId = UserHandle.getUserId(intendingUid);
655             final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
656             if (!clipboardAccessAllowed(
657                             AppOpsManager.OP_READ_CLIPBOARD,
658                             pkg,
659                             attributionTag,
660                             intendingUid,
661                             intendingUserId,
662                             intendingDeviceId)
663                     || isDeviceLocked(intendingUserId, deviceId)) {
664                 return null;
665             }
666             synchronized (mLock) {
667                 try {
668                     addActiveOwnerLocked(intendingUid, intendingDeviceId, pkg);
669                 } catch (SecurityException e) {
670                     // Permission could not be granted - URI may be invalid
671                     Slog.i(TAG, "Could not grant permission to primary clip. Clearing clipboard.");
672                     setPrimaryClipInternalLocked(null, intendingUid, intendingDeviceId, pkg);
673                     return null;
674                 }
675 
676                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
677                 if (clipboard == null) {
678                     return null;
679                 }
680                 showAccessNotificationLocked(
681                         pkg, intendingUid, intendingUserId, clipboard, deviceId);
682                 notifyTextClassifierLocked(clipboard, pkg, intendingUid);
683                 if (clipboard.primaryClip != null) {
684                     scheduleWriteClipDataStats(clipboard.primaryClip,
685                             clipboard.primaryClipUid, intendingUid);
686                     scheduleAutoClear(userId, intendingUid, intendingDeviceId);
687                 }
688                 return clipboard.primaryClip;
689             }
690         }
691 
692         @Override
getPrimaryClipDescription( String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId)693         public ClipDescription getPrimaryClipDescription(
694                 String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId) {
695             final int intendingUid = getIntendingUid(callingPackage, userId);
696             final int intendingUserId = UserHandle.getUserId(intendingUid);
697             final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
698             if (!clipboardAccessAllowed(
699                             AppOpsManager.OP_READ_CLIPBOARD,
700                             callingPackage,
701                             attributionTag,
702                             intendingUid,
703                             intendingUserId,
704                             intendingDeviceId,
705                             false)
706                     || isDeviceLocked(intendingUserId, deviceId)) {
707                 return null;
708             }
709             synchronized (mLock) {
710                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
711                 return (clipboard != null && clipboard.primaryClip != null)
712                         ? clipboard.primaryClip.getDescription() : null;
713             }
714         }
715 
716         @Override
hasPrimaryClip( String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId)717         public boolean hasPrimaryClip(
718                 String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId) {
719             final int intendingUid = getIntendingUid(callingPackage, userId);
720             final int intendingUserId = UserHandle.getUserId(intendingUid);
721             final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
722             if (!clipboardAccessAllowed(
723                             AppOpsManager.OP_READ_CLIPBOARD,
724                             callingPackage,
725                             attributionTag,
726                             intendingUid,
727                             intendingUserId,
728                             intendingDeviceId,
729                             false)
730                     || isDeviceLocked(intendingUserId, deviceId)) {
731                 return false;
732             }
733             synchronized (mLock) {
734                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
735                 return clipboard != null && clipboard.primaryClip != null;
736             }
737         }
738 
739         @Override
addPrimaryClipChangedListener( IOnPrimaryClipChangedListener listener, String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId)740         public void addPrimaryClipChangedListener(
741                 IOnPrimaryClipChangedListener listener,
742                 String callingPackage,
743                 String attributionTag,
744                 @UserIdInt int userId,
745                 int deviceId) {
746             final int intendingUid = getIntendingUid(callingPackage, userId);
747             final int intendingUserId = UserHandle.getUserId(intendingUid);
748             final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
749             if (intendingDeviceId == DEVICE_ID_INVALID) {
750                 Slog.i(TAG, "addPrimaryClipChangedListener invalid deviceId for userId:"
751                         + userId + " uid:" + intendingUid + " callingPackage:" + callingPackage
752                         + " requestedDeviceId:" + deviceId);
753                 return;
754             }
755             synchronized (mLock) {
756                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
757                 if (clipboard == null) {
758                     return;
759                 }
760                 clipboard.primaryClipListeners
761                         .register(
762                                 listener,
763                                 new ListenerInfo(intendingUid, callingPackage, attributionTag));
764             }
765         }
766 
767         @Override
removePrimaryClipChangedListener( IOnPrimaryClipChangedListener listener, String callingPackage, String attributionTag, @UserIdInt int userId, int deviceId)768         public void removePrimaryClipChangedListener(
769                 IOnPrimaryClipChangedListener listener,
770                 String callingPackage,
771                 String attributionTag,
772                 @UserIdInt int userId,
773                 int deviceId) {
774             final int intendingUid = getIntendingUid(callingPackage, userId);
775             final int intendingUserId = getIntendingUserId(callingPackage, userId);
776             final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
777             if (intendingDeviceId == DEVICE_ID_INVALID) {
778                 Slog.i(TAG, "removePrimaryClipChangedListener invalid deviceId for userId:"
779                         + userId + " uid:" + intendingUid + " callingPackage:" + callingPackage);
780                 return;
781             }
782             synchronized (mLock) {
783                 Clipboard clipboard = getClipboardLocked(intendingUserId,
784                                 intendingDeviceId);
785                 if (clipboard != null) {
786                     clipboard.primaryClipListeners.unregister(listener);
787                 }
788             }
789         }
790 
791         @Override
hasClipboardText( String callingPackage, String attributionTag, int userId, int deviceId)792         public boolean hasClipboardText(
793                 String callingPackage, String attributionTag, int userId, int deviceId) {
794             final int intendingUid = getIntendingUid(callingPackage, userId);
795             final int intendingUserId = UserHandle.getUserId(intendingUid);
796             final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
797             if (!clipboardAccessAllowed(
798                             AppOpsManager.OP_READ_CLIPBOARD,
799                             callingPackage,
800                             attributionTag,
801                             intendingUid,
802                             intendingUserId,
803                             intendingDeviceId,
804                             false)
805                     || isDeviceLocked(intendingUserId, deviceId)) {
806                 return false;
807             }
808             synchronized (mLock) {
809                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
810                 if (clipboard != null && clipboard.primaryClip != null) {
811                     CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
812                     return text != null && text.length() > 0;
813                 }
814                 return false;
815             }
816         }
817 
818         @android.annotation.EnforcePermission(android.Manifest.permission.SET_CLIP_SOURCE)
819         @Override
getPrimaryClipSource( String callingPackage, String attributionTag, int userId, int deviceId)820         public String getPrimaryClipSource(
821                 String callingPackage, String attributionTag, int userId, int deviceId) {
822             getPrimaryClipSource_enforcePermission();
823             final int intendingUid = getIntendingUid(callingPackage, userId);
824             final int intendingUserId = UserHandle.getUserId(intendingUid);
825             final int intendingDeviceId = getIntendingDeviceId(deviceId, intendingUid);
826             if (!clipboardAccessAllowed(
827                             AppOpsManager.OP_READ_CLIPBOARD,
828                             callingPackage,
829                             attributionTag,
830                             intendingUid,
831                             intendingUserId,
832                             intendingDeviceId,
833                             false)
834                     || isDeviceLocked(intendingUserId, deviceId)) {
835                 return null;
836             }
837             synchronized (mLock) {
838                 Clipboard clipboard = getClipboardLocked(intendingUserId, intendingDeviceId);
839                 if (clipboard != null && clipboard.primaryClip != null) {
840                     return clipboard.mPrimaryClipPackage;
841                 }
842                 return null;
843             }
844         }
845 
846         private class ClipboardClearHandler extends Handler {
847 
848             public static final int MSG_CLEAR = 101;
849 
ClipboardClearHandler(Looper looper)850             ClipboardClearHandler(Looper looper) {
851                 super(looper);
852             }
853 
handleMessage(@onNull Message msg)854             public void handleMessage(@NonNull Message msg) {
855                 switch (msg.what) {
856                     case MSG_CLEAR:
857                         final int userId = msg.arg1;
858                         final int intendingUid = msg.arg2;
859                         final int intendingDeviceId = ((Pair<Integer, Integer>) msg.obj).second;
860                         synchronized (mLock) {
861                             Clipboard clipboard = getClipboardLocked(userId, intendingDeviceId);
862                             if (clipboard != null && clipboard.primaryClip != null) {
863                                 FrameworkStatsLog.write(FrameworkStatsLog.CLIPBOARD_CLEARED,
864                                         FrameworkStatsLog.CLIPBOARD_CLEARED__SOURCE__AUTO_CLEAR);
865                                 setPrimaryClipInternalLocked(
866                                         null, intendingUid, intendingDeviceId, null);
867                             }
868                         }
869                         break;
870                     default:
871                         Slog.wtf(TAG, "ClipboardClearHandler received unknown message " + msg.what);
872                 }
873             }
874         }
875     };
876 
877     @GuardedBy("mLock")
getClipboardLocked(@serIdInt int userId, int deviceId)878     private @Nullable Clipboard getClipboardLocked(@UserIdInt int userId, int deviceId) {
879         Clipboard clipboard = mClipboards.get(userId, deviceId);
880         if (clipboard == null) {
881             try {
882                 if (!mUm.isUserRunning(userId)) {
883                     Slog.w(TAG, "getClipboardLocked called with not running userId " + userId);
884                     return null;
885                 }
886             } catch (RemoteException e) {
887                 Slog.e(TAG, "RemoteException calling UserManager: " + e);
888                 return null;
889             }
890             if (deviceId != DEVICE_ID_DEFAULT
891                     && mVdm != null && !mVdm.isValidVirtualDeviceId(deviceId)) {
892                 Slog.w(TAG, "getClipboardLocked called with invalid (possibly released) deviceId "
893                         + deviceId);
894                 return null;
895             }
896             clipboard = new Clipboard(userId, deviceId);
897             mClipboards.add(userId, deviceId, clipboard);
898         }
899         return clipboard;
900     }
901 
getRelatedProfiles(@serIdInt int userId)902     List<UserInfo> getRelatedProfiles(@UserIdInt int userId) {
903         final List<UserInfo> related;
904         final long origId = Binder.clearCallingIdentity();
905         try {
906             related = mUm.getProfiles(userId, true);
907         } catch (RemoteException e) {
908             Slog.e(TAG, "Remote Exception calling UserManager: " + e);
909             return null;
910         } finally{
911             Binder.restoreCallingIdentity(origId);
912         }
913         return related;
914     }
915 
916     /** Check if the user has the given restriction set. Default to true if error occured during
917      * calling UserManager, so it fails safe.
918      */
hasRestriction(String restriction, int userId)919     private boolean hasRestriction(String restriction, int userId) {
920         try {
921             return mUm.hasUserRestriction(restriction, userId);
922         } catch (RemoteException e) {
923             Slog.e(TAG, "Remote Exception calling UserManager.getUserRestrictions: ", e);
924             // Fails safe
925             return true;
926         }
927     }
928 
setPrimaryClipInternal(@ullable ClipData clip, int uid)929     void setPrimaryClipInternal(@Nullable ClipData clip, int uid) {
930         synchronized (mLock) {
931             setPrimaryClipInternalLocked(clip, uid, DEVICE_ID_DEFAULT, null);
932         }
933     }
934 
935     @GuardedBy("mLock")
setPrimaryClipInternalLocked( @ullable ClipData clip, int uid, int deviceId, @Nullable String sourcePackage)936     private void setPrimaryClipInternalLocked(
937             @Nullable ClipData clip, int uid, int deviceId, @Nullable String sourcePackage) {
938         if (deviceId == DEVICE_ID_DEFAULT) {
939             mClipboardMonitor.accept(clip);
940         }
941 
942         final int userId = UserHandle.getUserId(uid);
943 
944         // Update this user
945         Clipboard clipboard = getClipboardLocked(userId, deviceId);
946         if (clipboard == null) {
947             return;
948         }
949         setPrimaryClipInternalLocked(clipboard, clip, uid, sourcePackage);
950 
951         // Update related users
952         List<UserInfo> related = getRelatedProfiles(userId);
953         if (related != null) {
954             int size = related.size();
955             if (size > 1) { // Related profiles list include the current profile.
956                 final boolean canCopy = !hasRestriction(
957                         UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, userId);
958                 // Copy clip data to related users if allowed. If disallowed, then remove
959                 // primary clip in related users to prevent pasting stale content.
960                 if (!canCopy) {
961                     clip = null;
962                 } else if (clip == null) {
963                     // do nothing for canCopy == true and clip == null case
964                     // To prevent from NPE happen in 'new ClipData(clip)' when run
965                     // android.content.cts.ClipboardManagerTest#testClearPrimaryClip
966                 } else {
967                     // We want to fix the uris of the related user's clip without changing the
968                     // uris of the current user's clip.
969                     // So, copy the ClipData, and then copy all the items, so that nothing
970                     // is shared in memory.
971                     clip = new ClipData(clip);
972                     for (int i = clip.getItemCount() - 1; i >= 0; i--) {
973                         clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
974                     }
975                     clip.fixUrisLight(userId);
976                 }
977                 for (int i = 0; i < size; i++) {
978                     int id = related.get(i).id;
979                     if (id != userId) {
980                         final boolean canCopyIntoProfile = !hasRestriction(
981                                 UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
982                         if (canCopyIntoProfile) {
983                             Clipboard relatedClipboard = getClipboardLocked(id, deviceId);
984                             if (relatedClipboard != null) {
985                                 setPrimaryClipInternalNoClassifyLocked(relatedClipboard, clip, uid,
986                                         sourcePackage);
987                             }
988                         }
989                     }
990                 }
991             }
992         }
993     }
994 
setPrimaryClipInternal(Clipboard clipboard, @Nullable ClipData clip, int uid)995     void setPrimaryClipInternal(Clipboard clipboard, @Nullable ClipData clip,
996             int uid) {
997         synchronized (mLock) {
998             setPrimaryClipInternalLocked(clipboard, clip, uid, null);
999         }
1000     }
1001 
1002     @GuardedBy("mLock")
setPrimaryClipInternalLocked(Clipboard clipboard, @Nullable ClipData clip, int uid, @Nullable String sourcePackage)1003     private void setPrimaryClipInternalLocked(Clipboard clipboard, @Nullable ClipData clip,
1004             int uid, @Nullable String sourcePackage) {
1005         final int userId = UserHandle.getUserId(uid);
1006         if (clip != null) {
1007             startClassificationLocked(clip, userId, clipboard.deviceId);
1008         }
1009 
1010         setPrimaryClipInternalNoClassifyLocked(clipboard, clip, uid, sourcePackage);
1011     }
1012 
1013     @GuardedBy("mLock")
setPrimaryClipInternalNoClassifyLocked(Clipboard clipboard, @Nullable ClipData clip, int uid, @Nullable String sourcePackage)1014     private void setPrimaryClipInternalNoClassifyLocked(Clipboard clipboard,
1015             @Nullable ClipData clip, int uid, @Nullable String sourcePackage) {
1016         revokeUris(clipboard);
1017         clipboard.activePermissionOwners.clear();
1018         if (clip == null && clipboard.primaryClip == null) {
1019             return;
1020         }
1021         clipboard.primaryClip = clip;
1022         clipboard.mNotifiedUids.clear();
1023         clipboard.mNotifiedTextClassifierUids.clear();
1024         if (clip != null) {
1025             clipboard.primaryClipUid = uid;
1026             clipboard.mPrimaryClipPackage = sourcePackage;
1027         } else {
1028             clipboard.primaryClipUid = android.os.Process.NOBODY_UID;
1029             clipboard.mPrimaryClipPackage = null;
1030         }
1031         if (clip != null) {
1032             final ClipDescription description = clip.getDescription();
1033             if (description != null) {
1034                 description.setTimestamp(System.currentTimeMillis());
1035             }
1036         }
1037         sendClipChangedBroadcast(clipboard);
1038     }
1039 
sendClipChangedBroadcast(Clipboard clipboard)1040     private void sendClipChangedBroadcast(Clipboard clipboard) {
1041         final long ident = Binder.clearCallingIdentity();
1042         final int n = clipboard.primaryClipListeners.beginBroadcast();
1043         try {
1044             for (int i = 0; i < n; i++) {
1045                 try {
1046                     ListenerInfo li = (ListenerInfo)
1047                             clipboard.primaryClipListeners.getBroadcastCookie(i);
1048 
1049                     if (clipboardAccessAllowed(
1050                             AppOpsManager.OP_READ_CLIPBOARD,
1051                             li.mPackageName,
1052                             li.mAttributionTag,
1053                             li.mUid,
1054                             UserHandle.getUserId(li.mUid),
1055                             clipboard.deviceId)) {
1056                         clipboard.primaryClipListeners.getBroadcastItem(i)
1057                                 .dispatchPrimaryClipChanged();
1058                     }
1059                 } catch (RemoteException | SecurityException e) {
1060                     // The RemoteCallbackList will take care of removing
1061                     // the dead object for us.
1062                 }
1063             }
1064         } finally {
1065             clipboard.primaryClipListeners.finishBroadcast();
1066             Binder.restoreCallingIdentity(ident);
1067         }
1068     }
1069 
1070     @GuardedBy("mLock")
startClassificationLocked(@onNull ClipData clip, @UserIdInt int userId, int deviceId)1071     private void startClassificationLocked(@NonNull ClipData clip, @UserIdInt int userId,
1072             int deviceId) {
1073         CharSequence text = (clip.getItemCount() == 0) ? null : clip.getItemAt(0).getText();
1074         if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength) {
1075             clip.getDescription().setClassificationStatus(
1076                     ClipDescription.CLASSIFICATION_NOT_PERFORMED);
1077             return;
1078         }
1079         TextClassifier classifier;
1080         final long ident = Binder.clearCallingIdentity();
1081         try {
1082             classifier = createTextClassificationManagerAsUser(userId)
1083                     .createTextClassificationSession(
1084                             new TextClassificationContext.Builder(
1085                                     getContext().getPackageName(),
1086                                     TextClassifier.WIDGET_TYPE_CLIPBOARD
1087                             ).build()
1088                     );
1089         } finally {
1090             Binder.restoreCallingIdentity(ident);
1091         }
1092         if (text.length() > classifier.getMaxGenerateLinksTextLength()) {
1093             clip.getDescription().setClassificationStatus(
1094                     ClipDescription.CLASSIFICATION_NOT_PERFORMED);
1095             return;
1096         }
1097         mWorkerHandler.post(() -> doClassification(text, clip, classifier, userId, deviceId));
1098     }
1099 
1100     @WorkerThread
doClassification( CharSequence text, ClipData clip, TextClassifier classifier, @UserIdInt int userId, int deviceId)1101     private void doClassification(
1102             CharSequence text, ClipData clip, TextClassifier classifier, @UserIdInt int userId,
1103             int deviceId) {
1104         TextLinks.Request request = new TextLinks.Request.Builder(text).build();
1105         TextLinks links = classifier.generateLinks(request);
1106 
1107         // Find the highest confidence for each entity in the text.
1108         ArrayMap<String, Float> confidences = new ArrayMap<>();
1109         for (TextLinks.TextLink link : links.getLinks()) {
1110             for (int i = 0; i < link.getEntityCount(); i++) {
1111                 String entity = link.getEntity(i);
1112                 float conf = link.getConfidenceScore(entity);
1113                 if (conf > confidences.getOrDefault(entity, 0f)) {
1114                     confidences.put(entity, conf);
1115                 }
1116             }
1117         }
1118 
1119         synchronized (mLock) {
1120             Clipboard clipboard = getClipboardLocked(userId, deviceId);
1121             if (clipboard == null) {
1122                 return;
1123             }
1124             if (clipboard.primaryClip == clip) {
1125                 applyClassificationAndSendBroadcastLocked(
1126                         clipboard, confidences, links, classifier);
1127 
1128                 // Also apply to related profiles if needed
1129                 List<UserInfo> related = getRelatedProfiles(userId);
1130                 if (related != null) {
1131                     int size = related.size();
1132                     for (int i = 0; i < size; i++) {
1133                         int id = related.get(i).id;
1134                         if (id != userId) {
1135                             final boolean canCopyIntoProfile = !hasRestriction(
1136                                     UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
1137                             if (canCopyIntoProfile) {
1138                                 Clipboard relatedClipboard = getClipboardLocked(id, deviceId);
1139                                 if (relatedClipboard != null
1140                                         && hasTextLocked(relatedClipboard, text)) {
1141                                     applyClassificationAndSendBroadcastLocked(
1142                                             relatedClipboard, confidences, links, classifier);
1143                                 }
1144                             }
1145                         }
1146                     }
1147                 }
1148             }
1149         }
1150     }
1151 
1152     @GuardedBy("mLock")
applyClassificationAndSendBroadcastLocked( Clipboard clipboard, ArrayMap<String, Float> confidences, TextLinks links, TextClassifier classifier)1153     private void applyClassificationAndSendBroadcastLocked(
1154             Clipboard clipboard, ArrayMap<String, Float> confidences, TextLinks links,
1155             TextClassifier classifier) {
1156         clipboard.mTextClassifier = classifier;
1157         clipboard.primaryClip.getDescription().setConfidenceScores(confidences);
1158         if (!links.getLinks().isEmpty()) {
1159             clipboard.primaryClip.getItemAt(0).setTextLinks(links);
1160         }
1161         sendClipChangedBroadcast(clipboard);
1162     }
1163 
1164     @GuardedBy("mLock")
hasTextLocked(Clipboard clipboard, @NonNull CharSequence text)1165     private boolean hasTextLocked(Clipboard clipboard, @NonNull CharSequence text) {
1166         return clipboard.primaryClip != null
1167                 && clipboard.primaryClip.getItemCount() > 0
1168                 && text.equals(clipboard.primaryClip.getItemAt(0).getText());
1169     }
1170 
isDeviceLocked(@serIdInt int userId, int deviceId)1171     private boolean isDeviceLocked(@UserIdInt int userId, int deviceId) {
1172         final long token = Binder.clearCallingIdentity();
1173         try {
1174             final KeyguardManager keyguardManager = getContext().getSystemService(
1175                     KeyguardManager.class);
1176             return keyguardManager != null && keyguardManager.isDeviceLocked(userId, deviceId);
1177         } finally {
1178             Binder.restoreCallingIdentity(token);
1179         }
1180     }
1181 
checkUriOwner(Uri uri, int sourceUid)1182     private void checkUriOwner(Uri uri, int sourceUid) {
1183         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
1184 
1185         final long ident = Binder.clearCallingIdentity();
1186         try {
1187             // This will throw SecurityException if caller can't grant
1188             mUgmInternal.checkGrantUriPermission(sourceUid, null,
1189                     ContentProvider.getUriWithoutUserId(uri),
1190                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
1191                     ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
1192         } finally {
1193             Binder.restoreCallingIdentity(ident);
1194         }
1195     }
1196 
checkItemOwner(ClipData.Item item, int uid)1197     private void checkItemOwner(ClipData.Item item, int uid) {
1198         if (item.getUri() != null) {
1199             checkUriOwner(item.getUri(), uid);
1200         }
1201         Intent intent = item.getIntent();
1202         if (intent != null && intent.getData() != null) {
1203             checkUriOwner(intent.getData(), uid);
1204         }
1205     }
1206 
checkDataOwner(ClipData data, int uid)1207     private void checkDataOwner(ClipData data, int uid) {
1208         final int N = data.getItemCount();
1209         for (int i=0; i<N; i++) {
1210             checkItemOwner(data.getItemAt(i), uid);
1211         }
1212     }
1213 
grantUriPermission(Uri uri, int sourceUid, String targetPkg, int targetUserId)1214     private void grantUriPermission(Uri uri, int sourceUid, String targetPkg,
1215             int targetUserId) {
1216         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
1217 
1218         final long ident = Binder.clearCallingIdentity();
1219         try {
1220             mUgm.grantUriPermissionFromOwner(mPermissionOwner, sourceUid, targetPkg,
1221                     ContentProvider.getUriWithoutUserId(uri),
1222                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
1223                     ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)),
1224                     targetUserId);
1225         } catch (RemoteException ignored) {
1226             // Ignored because we're in same process
1227         } finally {
1228             Binder.restoreCallingIdentity(ident);
1229         }
1230     }
1231 
grantItemPermission(ClipData.Item item, int sourceUid, String targetPkg, int targetUserId)1232     private void grantItemPermission(ClipData.Item item, int sourceUid, String targetPkg,
1233             int targetUserId) {
1234         if (item.getUri() != null) {
1235             grantUriPermission(item.getUri(), sourceUid, targetPkg, targetUserId);
1236         }
1237         Intent intent = item.getIntent();
1238         if (intent != null && intent.getData() != null) {
1239             grantUriPermission(intent.getData(), sourceUid, targetPkg, targetUserId);
1240         }
1241     }
1242 
1243     @GuardedBy("mLock")
addActiveOwnerLocked(int uid, int deviceId, String pkg)1244     private void addActiveOwnerLocked(int uid, int deviceId, String pkg) {
1245         final PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
1246         final int targetUserHandle = UserHandle.getCallingUserId();
1247         final long oldIdentity = Binder.clearCallingIdentity();
1248         try {
1249             if (!pm.isSameApp(pkg, 0, uid, targetUserHandle)) {
1250                 throw new SecurityException("Calling uid " + uid + " does not own package " + pkg);
1251             }
1252         } finally {
1253             Binder.restoreCallingIdentity(oldIdentity);
1254         }
1255         Clipboard clipboard = getClipboardLocked(UserHandle.getUserId(uid), deviceId);
1256         if (clipboard != null && clipboard.primaryClip != null
1257                 && !clipboard.activePermissionOwners.contains(pkg)) {
1258             final int N = clipboard.primaryClip.getItemCount();
1259             for (int i = 0; i < N; i++) {
1260                 grantItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid,
1261                         pkg, UserHandle.getUserId(uid));
1262             }
1263             clipboard.activePermissionOwners.add(pkg);
1264         }
1265     }
1266 
revokeUriPermission(Uri uri, int sourceUid)1267     private void revokeUriPermission(Uri uri, int sourceUid) {
1268         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
1269 
1270         final long ident = Binder.clearCallingIdentity();
1271         try {
1272             mUgmInternal.revokeUriPermissionFromOwner(mPermissionOwner,
1273                     ContentProvider.getUriWithoutUserId(uri),
1274                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
1275                     ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
1276         } finally {
1277             Binder.restoreCallingIdentity(ident);
1278         }
1279     }
1280 
revokeItemPermission(ClipData.Item item, int sourceUid)1281     private void revokeItemPermission(ClipData.Item item, int sourceUid) {
1282         if (item.getUri() != null) {
1283             revokeUriPermission(item.getUri(), sourceUid);
1284         }
1285         Intent intent = item.getIntent();
1286         if (intent != null && intent.getData() != null) {
1287             revokeUriPermission(intent.getData(), sourceUid);
1288         }
1289     }
1290 
revokeUris(Clipboard clipboard)1291     private void revokeUris(Clipboard clipboard) {
1292         if (clipboard.primaryClip == null) {
1293             return;
1294         }
1295         final int N = clipboard.primaryClip.getItemCount();
1296         for (int i=0; i<N; i++) {
1297             revokeItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid);
1298         }
1299     }
1300 
clipboardAccessAllowed( int op, String callingPackage, String attributionTag, int uid, @UserIdInt int userId, int intendingDeviceId)1301     private boolean clipboardAccessAllowed(
1302             int op,
1303             String callingPackage,
1304             String attributionTag,
1305             int uid,
1306             @UserIdInt int userId,
1307             int intendingDeviceId) {
1308         return clipboardAccessAllowed(op, callingPackage, attributionTag, uid, userId,
1309                 intendingDeviceId, true);
1310     }
1311 
clipboardAccessAllowed( int op, String callingPackage, String attributionTag, int uid, @UserIdInt int userId, int intendingDeviceId, boolean shouldNoteOp)1312     private boolean clipboardAccessAllowed(
1313             int op,
1314             String callingPackage,
1315             String attributionTag,
1316             int uid,
1317             @UserIdInt int userId,
1318             int intendingDeviceId,
1319             boolean shouldNoteOp) {
1320 
1321         boolean allowed;
1322 
1323         // First, verify package ownership to ensure use below is safe.
1324         mAppOps.checkPackage(uid, callingPackage);
1325 
1326         if (intendingDeviceId == DEVICE_ID_INVALID) {
1327             Slog.w(TAG, "Clipboard access denied to " + uid + "/" + callingPackage
1328                     + " due to invalid device id");
1329             return false;
1330         }
1331 
1332         // Shell can access the clipboard for testing purposes.
1333         if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND,
1334                     callingPackage) == PackageManager.PERMISSION_GRANTED) {
1335             allowed = true;
1336         } else {
1337             // The default IME is always allowed to access the clipboard.
1338             allowed = isDefaultIme(userId, callingPackage);
1339         }
1340 
1341         switch (op) {
1342             case AppOpsManager.OP_READ_CLIPBOARD:
1343                 // Clipboard can only be read by applications with focus..
1344                 // or the application have the INTERNAL_SYSTEM_WINDOW and INTERACT_ACROSS_USERS_FULL
1345                 // at the same time. e.x. SystemUI. It needs to check the window focus of
1346                 // Binder.getCallingUid(). Without checking, the user X can't copy any thing from
1347                 // INTERNAL_SYSTEM_WINDOW to the other applications.
1348                 if (!allowed) {
1349                     allowed = isDefaultDeviceAndUidFocused(intendingDeviceId, uid)
1350                             || isVirtualDeviceAndUidFocused(intendingDeviceId, uid)
1351                             || isInternalSysWindowAppWithWindowFocus(callingPackage);
1352                 }
1353                 if (!allowed && mContentCaptureInternal != null) {
1354                     // ...or the Content Capture Service
1355                     // The uid parameter of mContentCaptureInternal.isContentCaptureServiceForUser
1356                     // is used to check if the uid has the permission BIND_CONTENT_CAPTURE_SERVICE.
1357                     // if the application has the permission, let it to access user's clipboard.
1358                     // To passed synthesized uid user#10_app#systemui may not tell the real uid.
1359                     // userId must pass intending userId. i.e. user#10.
1360                     allowed = mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId);
1361                 }
1362                 if (!allowed && mAutofillInternal != null) {
1363                     // ...or the Augmented Autofill Service
1364                     // The uid parameter of mAutofillInternal.isAugmentedAutofillServiceForUser
1365                     // is used to check if the uid has the permission BIND_AUTOFILL_SERVICE.
1366                     // if the application has the permission, let it to access user's clipboard.
1367                     // To passed synthesized uid user#10_app#systemui may not tell the real uid.
1368                     // userId must pass intending userId. i.e. user#10.
1369                     allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
1370                 }
1371                 if (!allowed && intendingDeviceId != DEVICE_ID_DEFAULT) {
1372                     // Privileged apps which own a VirtualDevice are allowed to read its clipboard
1373                     // in the background.
1374                     allowed = (mVdmInternal != null)
1375                             && mVdmInternal.getDeviceOwnerUid(intendingDeviceId) == uid;
1376                 }
1377                 break;
1378             case AppOpsManager.OP_WRITE_CLIPBOARD:
1379                 // Writing is allowed without focus.
1380                 allowed = true;
1381                 break;
1382             default:
1383                 throw new IllegalArgumentException("Unknown clipboard appop " + op);
1384         }
1385         if (!allowed) {
1386             Slog.e(TAG, "Denying clipboard access to " + callingPackage
1387                     + ", application is not in focus nor is it a system service for "
1388                     + "user " + userId);
1389             return false;
1390         }
1391         // Finally, check the app op.
1392         int appOpsResult;
1393         if (shouldNoteOp) {
1394             appOpsResult = mAppOps.noteOp(op, uid, callingPackage, attributionTag, null);
1395         } else {
1396             appOpsResult = mAppOps.checkOp(op, uid, callingPackage);
1397         }
1398 
1399         return appOpsResult == AppOpsManager.MODE_ALLOWED;
1400     }
1401 
isDefaultDeviceAndUidFocused(int intendingDeviceId, int uid)1402     private boolean isDefaultDeviceAndUidFocused(int intendingDeviceId, int uid) {
1403         return intendingDeviceId == DEVICE_ID_DEFAULT && mWm.isUidFocused(uid);
1404     }
1405 
isVirtualDeviceAndUidFocused(int intendingDeviceId, int uid)1406     private boolean isVirtualDeviceAndUidFocused(int intendingDeviceId, int uid) {
1407         if (intendingDeviceId == DEVICE_ID_DEFAULT || mVdm == null) {
1408             return false;
1409         }
1410         int topFocusedDisplayId = mWm.getTopFocusedDisplayId();
1411         int focusedDeviceId = mVdm.getDeviceIdForDisplayId(topFocusedDisplayId);
1412         return (focusedDeviceId == intendingDeviceId) && mWm.isUidFocused(uid);
1413     }
1414 
isDefaultIme(int userId, String packageName)1415     private boolean isDefaultIme(int userId, String packageName) {
1416         String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
1417                 Settings.Secure.DEFAULT_INPUT_METHOD, userId);
1418         if (!TextUtils.isEmpty(defaultIme)) {
1419             final ComponentName imeComponent = ComponentName.unflattenFromString(defaultIme);
1420             if (imeComponent == null) {
1421                 return false;
1422             }
1423             final String imePkg = imeComponent.getPackageName();
1424             return imePkg.equals(packageName);
1425         }
1426         return false;
1427     }
1428 
1429     /**
1430      * Shows a toast to inform the user that an app has accessed the clipboard. This is only done if
1431      * the setting is enabled, and if the accessing app is not the source of the data and is not the
1432      * IME, the content capture service, or the autofill service. The notification is also only
1433      * shown once per clip for each app.
1434      */
1435     @GuardedBy("mLock")
showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId, Clipboard clipboard, int accessDeviceId)1436     private void showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId,
1437             Clipboard clipboard, int accessDeviceId) {
1438         if (clipboard.primaryClip == null) {
1439             return;
1440         }
1441         if (Settings.Secure.getInt(getContext().getContentResolver(),
1442                 Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
1443                 (mShowAccessNotifications ? 1 : 0)) == 0) {
1444             return;
1445         }
1446         // Don't notify if the app accessing the clipboard is the same as the current owner.
1447         if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
1448             return;
1449         }
1450         // Exclude special cases: IME, ContentCapture, Autofill.
1451         if (isDefaultIme(userId, callingPackage)) {
1452             return;
1453         }
1454         if (mContentCaptureInternal != null
1455                 && mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId)) {
1456             return;
1457         }
1458         if (mAutofillInternal != null
1459                 && mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) {
1460             return;
1461         }
1462         if (mPm.checkPermission(Manifest.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION,
1463                 callingPackage) == PackageManager.PERMISSION_GRANTED) {
1464             return;
1465         }
1466         // Don't notify if this access is coming from the privileged app which owns the device.
1467         if (clipboard.deviceId != DEVICE_ID_DEFAULT && mVdmInternal != null
1468                 && mVdmInternal.getDeviceOwnerUid(clipboard.deviceId) == uid) {
1469             return;
1470         }
1471         // Don't notify if already notified for this uid and clip.
1472         if (clipboard.mNotifiedUids.get(uid)) {
1473             return;
1474         }
1475 
1476         final ArraySet<Context> toastContexts = getToastContexts(clipboard, accessDeviceId);
1477         Binder.withCleanCallingIdentity(() -> {
1478             try {
1479                 CharSequence callingAppLabel = mPm.getApplicationLabel(
1480                         mPm.getApplicationInfoAsUser(callingPackage, 0, userId));
1481                 String message =
1482                         getContext().getString(R.string.pasted_from_clipboard, callingAppLabel);
1483                 Slog.i(TAG, message);
1484                 for (int i = 0; i < toastContexts.size(); i++) {
1485                     Context toastContext = toastContexts.valueAt(i);
1486                     Toast toastToShow;
1487                     if (SafetyProtectionUtils.shouldShowSafetyProtectionResources(getContext())) {
1488                         Drawable safetyProtectionIcon = getContext()
1489                                 .getDrawable(R.drawable.ic_safety_protection);
1490                         toastToShow = Toast.makeCustomToastWithIcon(toastContext,
1491                                 UiThread.get().getLooper(), message,
1492                                 Toast.LENGTH_LONG, safetyProtectionIcon);
1493                     } else {
1494                         toastToShow = Toast.makeText(
1495                                 toastContext, UiThread.get().getLooper(), message,
1496                                 Toast.LENGTH_LONG);
1497                     }
1498                     toastToShow.show();
1499                 }
1500             } catch (PackageManager.NameNotFoundException e) {
1501                 // do nothing
1502             }
1503         });
1504 
1505         clipboard.mNotifiedUids.put(uid, true);
1506     }
1507 
1508     /**
1509      * Returns the context(s) to use for toasts related to this clipboard. Normally this will just
1510      * contain a single context referencing the default display.
1511      *
1512      * If the clipboard is for a VirtualDevice, we attempt to return the single DisplayContext for
1513      * the focused VirtualDisplay for that device, but might need to return the contexts for
1514      * multiple displays if the VirtualDevice has several but none of them were focused.
1515      *
1516      * If the clipboard is NOT for a VirtualDevice, but it's being accessed from a VirtualDevice,
1517      * this means that the clipboard is shared between the default and that device. In this case we
1518      * need to show a toast in both places.
1519      */
getToastContexts(Clipboard clipboard, int accessDeviceId)1520     private ArraySet<Context> getToastContexts(Clipboard clipboard, int accessDeviceId)
1521             throws IllegalStateException {
1522         ArraySet<Context> contexts = new ArraySet<>();
1523         if (clipboard.deviceId == DEVICE_ID_DEFAULT || accessDeviceId == DEVICE_ID_DEFAULT) {
1524             // Always show the toast on the default display when the default clipboard is accessed -
1525             // also when the clipboard is shared with a virtual device and accessed from there.
1526             // Same when any clipboard is accessed from the default device.
1527             contexts.add(getContext());
1528         }
1529 
1530         if ((accessDeviceId == DEVICE_ID_DEFAULT && clipboard.deviceId == DEVICE_ID_DEFAULT)
1531                 || mVdmInternal == null) {
1532             // No virtual devices involved.
1533             return contexts;
1534         }
1535 
1536         // At this point the clipboard is either accessed from a virtual device, or it is a virtual
1537         // device clipboard, so show a toast on the relevant virtual display(s).
1538         DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
1539         ArraySet<Integer> displayIds = mVdmInternal.getDisplayIdsForDevice(accessDeviceId);
1540         int topFocusedDisplayId = mWm.getTopFocusedDisplayId();
1541 
1542         if (displayIds.contains(topFocusedDisplayId)) {
1543             Display display = displayManager.getDisplay(topFocusedDisplayId);
1544             if (display != null) {
1545                 contexts.add(getContext().createDisplayContext(display));
1546                 return contexts;
1547             }
1548         }
1549 
1550         for (int i = 0; i < displayIds.size(); i++) {
1551             Display display = displayManager.getDisplay(displayIds.valueAt(i));
1552             if (display != null) {
1553                 contexts.add(getContext().createDisplayContext(display));
1554             }
1555         }
1556         if (contexts.isEmpty()) {
1557             Slog.e(TAG, "getToastContexts Couldn't find any VirtualDisplays for VirtualDevice "
1558                     + accessDeviceId);
1559             // Since we couldn't find any VirtualDisplays to use at all, just fall through to using
1560             // the default display below.
1561             contexts.add(getContext());
1562         }
1563 
1564         return contexts;
1565     }
1566 
1567     /**
1568      * Returns true if the provided {@link ClipData} represents a single piece of text. That is, if
1569      * there is only on {@link ClipData.Item}, and that item contains a non-empty piece of text and
1570      * no URI or Intent. Note that HTML may be provided along with text so the presence of
1571      * HtmlText in the clip does not prevent this method returning true.
1572      */
isText(@onNull ClipData data)1573     private static boolean isText(@NonNull ClipData data) {
1574         if (data.getItemCount() > 1) {
1575             return false;
1576         }
1577         ClipData.Item item = data.getItemAt(0);
1578 
1579         return !TextUtils.isEmpty(item.getText()) && item.getUri() == null
1580                 && item.getIntent() == null;
1581     }
1582 
1583     /** Potentially notifies the text classifier that an app is accessing a text clip. */
1584     @GuardedBy("mLock")
notifyTextClassifierLocked( Clipboard clipboard, String callingPackage, int callingUid)1585     private void notifyTextClassifierLocked(
1586             Clipboard clipboard, String callingPackage, int callingUid) {
1587         if (clipboard.primaryClip == null) {
1588             return;
1589         }
1590         ClipData.Item item = clipboard.primaryClip.getItemAt(0);
1591         if (item == null) {
1592             return;
1593         }
1594         if (!isText(clipboard.primaryClip)) {
1595             return;
1596         }
1597         TextClassifier textClassifier = clipboard.mTextClassifier;
1598         // Don't notify text classifier if we haven't used it to annotate the text in the clip.
1599         if (textClassifier == null) {
1600             return;
1601         }
1602         // Don't notify text classifier if the app reading the clipboard does not have the focus.
1603         if (!mWm.isUidFocused(callingUid)) {
1604             return;
1605         }
1606         // Don't notify text classifier again if already notified for this uid and clip.
1607         if (clipboard.mNotifiedTextClassifierUids.get(callingUid)) {
1608             return;
1609         }
1610         clipboard.mNotifiedTextClassifierUids.put(callingUid, true);
1611         Binder.withCleanCallingIdentity(() -> {
1612             TextClassifierEvent.TextLinkifyEvent pasteEvent =
1613                     new TextClassifierEvent.TextLinkifyEvent.Builder(
1614                             TextClassifierEvent.TYPE_READ_CLIPBOARD)
1615                             .setEventContext(new TextClassificationContext.Builder(
1616                                     callingPackage, TextClassifier.WIDGET_TYPE_CLIPBOARD)
1617                                     .build())
1618                             .setExtras(
1619                                     Bundle.forPair("source_package", clipboard.mPrimaryClipPackage))
1620                             .build();
1621             textClassifier.onTextClassifierEvent(pasteEvent);
1622         });
1623     }
1624 
createTextClassificationManagerAsUser(@serIdInt int userId)1625     private TextClassificationManager createTextClassificationManagerAsUser(@UserIdInt int userId) {
1626         Context context = getContext().createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
1627         return context.getSystemService(TextClassificationManager.class);
1628     }
1629 
mimeTypeToClipDataType(@onNull String mimeType)1630     private static int mimeTypeToClipDataType(@NonNull String mimeType) {
1631         switch (mimeType) {
1632             case MIMETYPE_TEXT_PLAIN:
1633                 return CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_TEXT_PLAIN;
1634             case MIMETYPE_TEXT_HTML:
1635                 return CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_TEXT_HTML;
1636             case MIMETYPE_TEXT_URILIST:
1637                 return CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_TEXT_URILIST;
1638             case MIMETYPE_TEXT_INTENT:
1639                 return CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_TEXT_INTENT;
1640             case MIMETYPE_APPLICATION_ACTIVITY:
1641                 return CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_APPLICATION_ACTIVITY;
1642             case MIMETYPE_APPLICATION_SHORTCUT:
1643                 return CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_APPLICATION_SHORTCUT;
1644             case MIMETYPE_APPLICATION_TASK:
1645                 return CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_APPLICATION_TASK;
1646             case MIMETYPE_UNKNOWN:
1647                 // fallthrough
1648             default:
1649                 return CLIPBOARD_GET_EVENT_REPORTED__CLIP_DATA_TYPE__MIMETYPE_UNKNOWN;
1650         }
1651     }
1652 
scheduleWriteClipDataStats(@onNull ClipData clipData, int sourceUid, int intendingUid)1653     private void scheduleWriteClipDataStats(@NonNull ClipData clipData,
1654             int sourceUid, int intendingUid) {
1655         if (!clipboardGetEventLogging()) {
1656             return;
1657         }
1658         final ClipDescription description = clipData.getDescription();
1659         if (description != null) {
1660             final IntArray mimeTypes = new IntArray();
1661             final int secondsSinceSet = (int) TimeUnit.MILLISECONDS.toSeconds(
1662                     System.currentTimeMillis() - description.getTimestamp());
1663             for (int i = description.getMimeTypeCount() - 1; i >= 0; i--) {
1664                 final String mimeType = description.getMimeType(i);
1665                 if (mimeType != null) {
1666                     final int clipDataType = mimeTypeToClipDataType(mimeType);
1667                     if (!mimeTypes.contains(clipDataType)) {
1668                         mimeTypes.add(clipDataType);
1669                     }
1670                 }
1671             }
1672             // The getUidProcessState() will hit AMS lock which might be slow, while getting the
1673             // clip data might be on the critical UI path. So post to the work thread.
1674             // There could be race conditions where the UID state might have been changed
1675             // between now and the work thread execution time, but this should be acceptable.
1676             mWorkerHandler.post(() -> FrameworkStatsLog.write(
1677                     FrameworkStatsLog.CLIPBOARD_GET_EVENT_REPORTED,
1678                     sourceUid, intendingUid,
1679                     mAmInternal.getUidProcessState(intendingUid),
1680                     mimeTypes.toArray(),
1681                     secondsSinceSet));
1682         } else {
1683             mWorkerHandler.post(() -> FrameworkStatsLog.write(
1684                     FrameworkStatsLog.CLIPBOARD_GET_EVENT_REPORTED,
1685                     sourceUid, intendingUid,
1686                     mAmInternal.getUidProcessState(intendingUid),
1687                     CLIP_DATA_TYPES_UNKNOWN, 0));
1688         }
1689     }
1690 }
1691