• 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 
21 import android.Manifest;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.UserIdInt;
25 import android.annotation.WorkerThread;
26 import android.app.ActivityManagerInternal;
27 import android.app.AppGlobals;
28 import android.app.AppOpsManager;
29 import android.app.IUriGrantsManager;
30 import android.app.KeyguardManager;
31 import android.app.UriGrantsManager;
32 import android.content.ClipData;
33 import android.content.ClipDescription;
34 import android.content.ClipboardManager;
35 import android.content.ComponentName;
36 import android.content.ContentProvider;
37 import android.content.ContentResolver;
38 import android.content.Context;
39 import android.content.IClipboard;
40 import android.content.IOnPrimaryClipChangedListener;
41 import android.content.Intent;
42 import android.content.pm.IPackageManager;
43 import android.content.pm.PackageInfo;
44 import android.content.pm.PackageManager;
45 import android.content.pm.UserInfo;
46 import android.graphics.drawable.Drawable;
47 import android.net.Uri;
48 import android.os.Binder;
49 import android.os.Bundle;
50 import android.os.Handler;
51 import android.os.HandlerThread;
52 import android.os.IBinder;
53 import android.os.IUserManager;
54 import android.os.Looper;
55 import android.os.Message;
56 import android.os.Parcel;
57 import android.os.RemoteCallbackList;
58 import android.os.RemoteException;
59 import android.os.ServiceManager;
60 import android.os.SystemProperties;
61 import android.os.UserHandle;
62 import android.os.UserManager;
63 import android.provider.DeviceConfig;
64 import android.provider.Settings;
65 import android.text.TextUtils;
66 import android.util.ArrayMap;
67 import android.util.SafetyProtectionUtils;
68 import android.util.Slog;
69 import android.util.SparseArray;
70 import android.util.SparseBooleanArray;
71 import android.view.autofill.AutofillManagerInternal;
72 import android.view.textclassifier.TextClassificationContext;
73 import android.view.textclassifier.TextClassificationManager;
74 import android.view.textclassifier.TextClassifier;
75 import android.view.textclassifier.TextClassifierEvent;
76 import android.view.textclassifier.TextLinks;
77 import android.widget.Toast;
78 
79 import com.android.internal.R;
80 import com.android.internal.annotations.GuardedBy;
81 import com.android.internal.annotations.VisibleForTesting;
82 import com.android.internal.util.FrameworkStatsLog;
83 import com.android.server.LocalServices;
84 import com.android.server.SystemService;
85 import com.android.server.UiThread;
86 import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
87 import com.android.server.contentcapture.ContentCaptureManagerInternal;
88 import com.android.server.uri.UriGrantsManagerInternal;
89 import com.android.server.wm.WindowManagerInternal;
90 
91 import java.util.HashSet;
92 import java.util.List;
93 import java.util.function.Consumer;
94 
95 /**
96  * Implementation of the clipboard for copy and paste.
97  * <p>
98  * Caution: exception for clipboard data and isInternalSysWindowAppWithWindowFocus, any of data
99  * is accessed by userId or uid should be in * the try segment between
100  * Binder.clearCallingIdentity and Binder.restoreCallingIdentity.
101  * </p>
102  */
103 public class ClipboardService extends SystemService {
104 
105     private static final String TAG = "ClipboardService";
106     private static final boolean IS_EMULATOR =
107             SystemProperties.getBoolean("ro.boot.qemu", false);
108 
109     @VisibleForTesting
110     public static final long DEFAULT_CLIPBOARD_TIMEOUT_MILLIS = 3600000;
111 
112     /**
113      * Device config property for whether clipboard auto clear is enabled on the device
114      **/
115     public static final String PROPERTY_AUTO_CLEAR_ENABLED =
116             "auto_clear_enabled";
117 
118     /**
119      * Device config property for time period in milliseconds after which clipboard is auto
120      * cleared
121      **/
122     public static final String PROPERTY_AUTO_CLEAR_TIMEOUT =
123             "auto_clear_timeout";
124 
125     // DeviceConfig properties
126     private static final String PROPERTY_MAX_CLASSIFICATION_LENGTH = "max_classification_length";
127     private static final int DEFAULT_MAX_CLASSIFICATION_LENGTH = 400;
128 
129     private final ActivityManagerInternal mAmInternal;
130     private final IUriGrantsManager mUgm;
131     private final UriGrantsManagerInternal mUgmInternal;
132     private final WindowManagerInternal mWm;
133     private final VirtualDeviceManagerInternal mVdm;
134     private final IUserManager mUm;
135     private final PackageManager mPm;
136     private final AppOpsManager mAppOps;
137     private final ContentCaptureManagerInternal mContentCaptureInternal;
138     private final AutofillManagerInternal mAutofillInternal;
139     private final IBinder mPermissionOwner;
140     private final Consumer<ClipData> mEmulatorClipboardMonitor;
141     private final Handler mWorkerHandler;
142 
143     @GuardedBy("mLock")
144     private final SparseArray<PerUserClipboard> mClipboards = new SparseArray<>();
145 
146     @GuardedBy("mLock")
147     private boolean mShowAccessNotifications =
148             ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS;
149 
150     @GuardedBy("mLock")
151     private int mMaxClassificationLength = DEFAULT_MAX_CLASSIFICATION_LENGTH;
152 
153     private final Object mLock = new Object();
154 
155     /**
156      * Instantiates the clipboard.
157      */
ClipboardService(Context context)158     public ClipboardService(Context context) {
159         super(context);
160 
161         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
162         mUgm = UriGrantsManager.getService();
163         mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
164         mWm = LocalServices.getService(WindowManagerInternal.class);
165         // Can be null; not all products have CDM + VirtualDeviceManager
166         mVdm = LocalServices.getService(VirtualDeviceManagerInternal.class);
167         mPm = getContext().getPackageManager();
168         mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
169         mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
170         mContentCaptureInternal = LocalServices.getService(ContentCaptureManagerInternal.class);
171         mAutofillInternal = LocalServices.getService(AutofillManagerInternal.class);
172         final IBinder permOwner = mUgmInternal.newUriPermissionOwner("clipboard");
173         mPermissionOwner = permOwner;
174         if (IS_EMULATOR) {
175             mEmulatorClipboardMonitor = new EmulatorClipboardMonitor((clip) -> {
176                 synchronized (mLock) {
177                     setPrimaryClipInternalLocked(getClipboardLocked(0), clip,
178                             android.os.Process.SYSTEM_UID, null);
179                 }
180             });
181         } else {
182             mEmulatorClipboardMonitor = (clip) -> {};
183         }
184 
185         updateConfig();
186         DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CLIPBOARD,
187                 getContext().getMainExecutor(), properties -> updateConfig());
188 
189         HandlerThread workerThread = new HandlerThread(TAG);
190         workerThread.start();
191         mWorkerHandler = workerThread.getThreadHandler();
192     }
193 
194     @Override
onStart()195     public void onStart() {
196         publishBinderService(Context.CLIPBOARD_SERVICE, new ClipboardImpl());
197     }
198 
199     @Override
onUserStopped(@onNull TargetUser user)200     public void onUserStopped(@NonNull TargetUser user) {
201         synchronized (mLock) {
202             mClipboards.remove(user.getUserIdentifier());
203         }
204     }
205 
updateConfig()206     private void updateConfig() {
207         synchronized (mLock) {
208             mShowAccessNotifications = DeviceConfig.getBoolean(
209                     DeviceConfig.NAMESPACE_CLIPBOARD,
210                     ClipboardManager.DEVICE_CONFIG_SHOW_ACCESS_NOTIFICATIONS,
211                     ClipboardManager.DEVICE_CONFIG_DEFAULT_SHOW_ACCESS_NOTIFICATIONS);
212             mMaxClassificationLength = DeviceConfig.getInt(DeviceConfig.NAMESPACE_CLIPBOARD,
213                     PROPERTY_MAX_CLASSIFICATION_LENGTH, DEFAULT_MAX_CLASSIFICATION_LENGTH);
214         }
215     }
216 
217     private class ListenerInfo {
218         final int mUid;
219         final String mPackageName;
220         final String mAttributionTag;
221 
ListenerInfo(int uid, String packageName, String attributionTag)222         ListenerInfo(int uid, String packageName, String attributionTag) {
223             mUid = uid;
224             mPackageName = packageName;
225             mAttributionTag = attributionTag;
226         }
227     }
228 
229     private class PerUserClipboard {
230         final int userId;
231 
232         final RemoteCallbackList<IOnPrimaryClipChangedListener> primaryClipListeners
233                 = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
234 
235         /** Current primary clip. */
236         ClipData primaryClip;
237         /** UID that set {@link #primaryClip}. */
238         int primaryClipUid = android.os.Process.NOBODY_UID;
239         /** Package of the app that set {@link #primaryClip}. */
240         String mPrimaryClipPackage;
241 
242         /** Uids that have already triggered a toast notification for {@link #primaryClip} */
243         final SparseBooleanArray mNotifiedUids = new SparseBooleanArray();
244 
245         /**
246          * Uids that have already triggered a notification to text classifier for
247          * {@link #primaryClip}.
248          */
249         final SparseBooleanArray mNotifiedTextClassifierUids = new SparseBooleanArray();
250 
251         final HashSet<String> activePermissionOwners
252                 = new HashSet<String>();
253 
254         /** The text classifier session that is used to annotate the text in the primary clip. */
255         TextClassifier mTextClassifier;
256 
PerUserClipboard(int userId)257         PerUserClipboard(int userId) {
258             this.userId = userId;
259         }
260     }
261 
262     /**
263      * To check if the application has granted the INTERNAL_SYSTEM_WINDOW permission and window
264      * focus.
265      * <p>
266      * All of applications granted INTERNAL_SYSTEM_WINDOW has the risk to leak clip information to
267      * the other user because INTERNAL_SYSTEM_WINDOW is signature level. i.e. platform key. Because
268      * some of applications have both of INTERNAL_SYSTEM_WINDOW and INTERACT_ACROSS_USERS_FULL at
269      * the same time, that means they show the same window to all of users.
270      * </p><p>
271      * Unfortunately, all of applications with INTERNAL_SYSTEM_WINDOW starts very early and then
272      * the real window show is belong to user 0 rather user X. The result of
273      * WindowManager.isUidFocused checking user X window is false.
274      * </p>
275      * @return true if the app granted INTERNAL_SYSTEM_WINDOW permission.
276      */
isInternalSysWindowAppWithWindowFocus(String callingPackage)277     private boolean isInternalSysWindowAppWithWindowFocus(String callingPackage) {
278         // Shell can access the clipboard for testing purposes.
279         if (mPm.checkPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW,
280                     callingPackage) == PackageManager.PERMISSION_GRANTED) {
281             if (mWm.isUidFocused(Binder.getCallingUid())) {
282                 return true;
283             }
284         }
285 
286         return false;
287     }
288 
289     /**
290      * To get the validate current userId.
291      * <p>
292      * The intending userId needs to be validated by ActivityManagerInternal.handleIncomingUser.
293      * To check if the uid of the process have the permission to run as the userId.
294      * e.x. INTERACT_ACROSS_USERS_FULL or INTERACT_ACROSS_USERS permission granted.
295      * </p>
296      * <p>
297      * The application with the granted INTERNAL_SYSTEM_WINDOW permission should run as the output
298      * of ActivityManagerInternal.handleIncomingUser rather the userId of Binder.getCAllingUid().
299      * To use the userId of Binder.getCallingUid() is the root cause that leaks the information
300      * comes from user 0 to user X.
301      * </p>
302      *
303      * @param packageName the package name of the calling side
304      * @param userId the userId passed by the calling side
305      * @return return the intending userId that has been validated by ActivityManagerInternal.
306      */
307     @UserIdInt
getIntendingUserId(String packageName, @UserIdInt int userId)308     private int getIntendingUserId(String packageName, @UserIdInt int userId) {
309         final int callingUid = Binder.getCallingUid();
310         final int callingUserId = UserHandle.getUserId(callingUid);
311         if (!UserManager.supportsMultipleUsers() || callingUserId == userId) {
312             return callingUserId;
313         }
314 
315         int intendingUserId = callingUserId;
316         intendingUserId = mAmInternal.handleIncomingUser(Binder.getCallingPid(),
317                 Binder.getCallingUid(), userId, false /* allow all */, ALLOW_FULL_ONLY,
318                 "checkClipboardServiceCallingUser", packageName);
319 
320         return intendingUserId;
321     }
322 
323     /**
324      * To get the current running uid who is intend to run as.
325      * In ording to distinguish the nameing and reducing the confusing names, the client client
326      * side pass userId that is intend to run as,
327      * @return return IntentingUid = validated intenting userId +
328      *         UserHandle.getAppId(Binder.getCallingUid())
329      */
getIntendingUid(String packageName, @UserIdInt int userId)330     private int getIntendingUid(String packageName, @UserIdInt int userId) {
331         return UserHandle.getUid(getIntendingUserId(packageName, userId),
332                 UserHandle.getAppId(Binder.getCallingUid()));
333     }
334 
335     /**
336      * To handle the difference between userId and intendingUserId, uid and intendingUid.
337      *
338      * userId means that comes from the calling side and should be validated by
339      * ActivityManagerInternal.handleIncomingUser.
340      * After validation of ActivityManagerInternal.handleIncomingUser, the userId is called
341      * 'intendingUserId' and the uid is called 'intendingUid'.
342      */
343     private class ClipboardImpl extends IClipboard.Stub {
344 
345         private final Handler mClipboardClearHandler = new ClipboardClearHandler(
346                 mWorkerHandler.getLooper());
347 
348         @Override
onTransact(int code, Parcel data, Parcel reply, int flags)349         public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
350                 throws RemoteException {
351             try {
352                 return super.onTransact(code, data, reply, flags);
353             } catch (RuntimeException e) {
354                 if (!(e instanceof SecurityException)) {
355                     Slog.wtf("clipboard", "Exception: ", e);
356                 }
357                 throw e;
358             }
359 
360         }
361 
362         @Override
setPrimaryClip( ClipData clip, String callingPackage, String attributionTag, @UserIdInt int userId)363         public void setPrimaryClip(
364                 ClipData clip,
365                 String callingPackage,
366                 String attributionTag,
367                 @UserIdInt int userId) {
368             checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, callingPackage);
369         }
370 
371         @Override
setPrimaryClipAsPackage( ClipData clip, String callingPackage, String attributionTag, @UserIdInt int userId, String sourcePackage)372         public void setPrimaryClipAsPackage(
373                 ClipData clip,
374                 String callingPackage,
375                 String attributionTag,
376                 @UserIdInt int userId,
377                 String sourcePackage) {
378             getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
379                     "Requires SET_CLIP_SOURCE permission");
380             checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, sourcePackage);
381         }
382 
checkAndSetPrimaryClip( ClipData clip, String callingPackage, String attributionTag, @UserIdInt int userId, String sourcePackage)383         private void checkAndSetPrimaryClip(
384                 ClipData clip,
385                 String callingPackage,
386                 String attributionTag,
387                 @UserIdInt int userId,
388                 String sourcePackage) {
389             if (clip == null || clip.getItemCount() <= 0) {
390                 throw new IllegalArgumentException("No items");
391             }
392             final int intendingUid = getIntendingUid(callingPackage, userId);
393             final int intendingUserId = UserHandle.getUserId(intendingUid);
394             if (!clipboardAccessAllowed(
395                     AppOpsManager.OP_WRITE_CLIPBOARD,
396                     callingPackage,
397                     attributionTag,
398                     intendingUid,
399                     intendingUserId)) {
400                 return;
401             }
402             checkDataOwner(clip, intendingUid);
403             synchronized (mLock) {
404                 scheduleAutoClear(userId, intendingUid);
405                 setPrimaryClipInternalLocked(clip, intendingUid, sourcePackage);
406             }
407         }
408 
scheduleAutoClear(@serIdInt int userId, int intendingUid)409         private void scheduleAutoClear(@UserIdInt int userId, int intendingUid) {
410             final long oldIdentity = Binder.clearCallingIdentity();
411             try {
412                 if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_CLIPBOARD,
413                         PROPERTY_AUTO_CLEAR_ENABLED, true)) {
414                     mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
415                             userId);
416                     Message clearMessage =
417                             Message.obtain(
418                                     mClipboardClearHandler,
419                                     ClipboardClearHandler.MSG_CLEAR,
420                                     userId,
421                                     intendingUid,
422                                     userId);
423                     mClipboardClearHandler.sendMessageDelayed(clearMessage,
424                             getTimeoutForAutoClear());
425                 }
426             } finally {
427                 Binder.restoreCallingIdentity(oldIdentity);
428             }
429         }
430 
getTimeoutForAutoClear()431         private long getTimeoutForAutoClear() {
432             return DeviceConfig.getLong(DeviceConfig.NAMESPACE_CLIPBOARD,
433                     PROPERTY_AUTO_CLEAR_TIMEOUT,
434                     DEFAULT_CLIPBOARD_TIMEOUT_MILLIS);
435         }
436 
437         @Override
clearPrimaryClip( String callingPackage, String attributionTag, @UserIdInt int userId)438         public void clearPrimaryClip(
439                 String callingPackage, String attributionTag, @UserIdInt int userId) {
440             final int intendingUid = getIntendingUid(callingPackage, userId);
441             final int intendingUserId = UserHandle.getUserId(intendingUid);
442             if (!clipboardAccessAllowed(
443                     AppOpsManager.OP_WRITE_CLIPBOARD,
444                     callingPackage,
445                     attributionTag,
446                     intendingUid,
447                     intendingUserId)) {
448                 return;
449             }
450             synchronized (mLock) {
451                 mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
452                         userId);
453                 setPrimaryClipInternalLocked(null, intendingUid, callingPackage);
454             }
455         }
456 
457         @Override
getPrimaryClip(String pkg, String attributionTag, @UserIdInt int userId)458         public ClipData getPrimaryClip(String pkg, String attributionTag, @UserIdInt int userId) {
459             final int intendingUid = getIntendingUid(pkg, userId);
460             final int intendingUserId = UserHandle.getUserId(intendingUid);
461             if (!clipboardAccessAllowed(
462                             AppOpsManager.OP_READ_CLIPBOARD,
463                             pkg,
464                             attributionTag,
465                             intendingUid,
466                             intendingUserId)
467                     || isDeviceLocked(intendingUserId)) {
468                 return null;
469             }
470             synchronized (mLock) {
471                 try {
472                     addActiveOwnerLocked(intendingUid, pkg);
473                 } catch (SecurityException e) {
474                     // Permission could not be granted - URI may be invalid
475                     Slog.i(TAG, "Could not grant permission to primary clip. Clearing clipboard.");
476                     setPrimaryClipInternalLocked(null, intendingUid, pkg);
477                     return null;
478                 }
479 
480                 PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
481                 showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard);
482                 notifyTextClassifierLocked(clipboard, pkg, intendingUid);
483                 if (clipboard.primaryClip != null) {
484                     scheduleAutoClear(userId, intendingUid);
485                 }
486                 return clipboard.primaryClip;
487             }
488         }
489 
490         @Override
getPrimaryClipDescription( String callingPackage, String attributionTag, @UserIdInt int userId)491         public ClipDescription getPrimaryClipDescription(
492                 String callingPackage, String attributionTag, @UserIdInt int userId) {
493             final int intendingUid = getIntendingUid(callingPackage, userId);
494             final int intendingUserId = UserHandle.getUserId(intendingUid);
495             if (!clipboardAccessAllowed(
496                             AppOpsManager.OP_READ_CLIPBOARD,
497                             callingPackage,
498                             attributionTag,
499                             intendingUid,
500                             intendingUserId,
501                             false)
502                     || isDeviceLocked(intendingUserId)) {
503                 return null;
504             }
505             synchronized (mLock) {
506                 PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
507                 return clipboard.primaryClip != null
508                         ? clipboard.primaryClip.getDescription() : null;
509             }
510         }
511 
512         @Override
hasPrimaryClip( String callingPackage, String attributionTag, @UserIdInt int userId)513         public boolean hasPrimaryClip(
514                 String callingPackage, String attributionTag, @UserIdInt int userId) {
515             final int intendingUid = getIntendingUid(callingPackage, userId);
516             final int intendingUserId = UserHandle.getUserId(intendingUid);
517             if (!clipboardAccessAllowed(
518                             AppOpsManager.OP_READ_CLIPBOARD,
519                             callingPackage,
520                             attributionTag,
521                             intendingUid,
522                             intendingUserId,
523                             false)
524                     || isDeviceLocked(intendingUserId)) {
525                 return false;
526             }
527             synchronized (mLock) {
528                 return getClipboardLocked(intendingUserId).primaryClip != null;
529             }
530         }
531 
532         @Override
addPrimaryClipChangedListener( IOnPrimaryClipChangedListener listener, String callingPackage, String attributionTag, @UserIdInt int userId)533         public void addPrimaryClipChangedListener(
534                 IOnPrimaryClipChangedListener listener,
535                 String callingPackage,
536                 String attributionTag,
537                 @UserIdInt int userId) {
538             final int intendingUid = getIntendingUid(callingPackage, userId);
539             final int intendingUserId = UserHandle.getUserId(intendingUid);
540             synchronized (mLock) {
541                 getClipboardLocked(intendingUserId)
542                         .primaryClipListeners
543                         .register(
544                                 listener,
545                                 new ListenerInfo(intendingUid, callingPackage, attributionTag));
546             }
547         }
548 
549         @Override
removePrimaryClipChangedListener( IOnPrimaryClipChangedListener listener, String callingPackage, String attributionTag, @UserIdInt int userId)550         public void removePrimaryClipChangedListener(
551                 IOnPrimaryClipChangedListener listener,
552                 String callingPackage,
553                 String attributionTag,
554                 @UserIdInt int userId) {
555             final int intendingUserId = getIntendingUserId(callingPackage, userId);
556             synchronized (mLock) {
557                 getClipboardLocked(intendingUserId).primaryClipListeners.unregister(listener);
558             }
559         }
560 
561         @Override
hasClipboardText(String callingPackage, String attributionTag, int userId)562         public boolean hasClipboardText(String callingPackage, String attributionTag, int userId) {
563             final int intendingUid = getIntendingUid(callingPackage, userId);
564             final int intendingUserId = UserHandle.getUserId(intendingUid);
565             if (!clipboardAccessAllowed(
566                             AppOpsManager.OP_READ_CLIPBOARD,
567                             callingPackage,
568                             attributionTag,
569                             intendingUid,
570                             intendingUserId,
571                             false)
572                     || isDeviceLocked(intendingUserId)) {
573                 return false;
574             }
575             synchronized (mLock) {
576                 PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
577                 if (clipboard.primaryClip != null) {
578                     CharSequence text = clipboard.primaryClip.getItemAt(0).getText();
579                     return text != null && text.length() > 0;
580                 }
581                 return false;
582             }
583         }
584 
585         @Override
getPrimaryClipSource( String callingPackage, String attributionTag, int userId)586         public String getPrimaryClipSource(
587                 String callingPackage, String attributionTag, int userId) {
588             getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
589                     "Requires SET_CLIP_SOURCE permission");
590             final int intendingUid = getIntendingUid(callingPackage, userId);
591             final int intendingUserId = UserHandle.getUserId(intendingUid);
592             if (!clipboardAccessAllowed(
593                             AppOpsManager.OP_READ_CLIPBOARD,
594                             callingPackage,
595                             attributionTag,
596                             intendingUid,
597                             intendingUserId,
598                             false)
599                     || isDeviceLocked(intendingUserId)) {
600                 return null;
601             }
602             synchronized (mLock) {
603                 PerUserClipboard clipboard = getClipboardLocked(intendingUserId);
604                 if (clipboard.primaryClip != null) {
605                     return clipboard.mPrimaryClipPackage;
606                 }
607                 return null;
608             }
609         }
610 
611         private class ClipboardClearHandler extends Handler {
612 
613             public static final int MSG_CLEAR = 101;
614 
ClipboardClearHandler(Looper looper)615             ClipboardClearHandler(Looper looper) {
616                 super(looper);
617             }
618 
handleMessage(@onNull Message msg)619             public void handleMessage(@NonNull Message msg) {
620                 switch (msg.what) {
621                     case MSG_CLEAR:
622                         final int userId = msg.arg1;
623                         final int intendingUid = msg.arg2;
624                         synchronized (mLock) {
625                             if (getClipboardLocked(userId).primaryClip != null) {
626                                 FrameworkStatsLog.write(FrameworkStatsLog.CLIPBOARD_CLEARED,
627                                         FrameworkStatsLog.CLIPBOARD_CLEARED__SOURCE__AUTO_CLEAR);
628                                 setPrimaryClipInternalLocked(null, intendingUid, null);
629                             }
630                         }
631                         break;
632                     default:
633                         Slog.wtf(TAG, "ClipboardClearHandler received unknown message " + msg.what);
634                 }
635             }
636         }
637     };
638 
639     @GuardedBy("mLock")
getClipboardLocked(@serIdInt int userId)640     private PerUserClipboard getClipboardLocked(@UserIdInt int userId) {
641         PerUserClipboard puc = mClipboards.get(userId);
642         if (puc == null) {
643             puc = new PerUserClipboard(userId);
644             mClipboards.put(userId, puc);
645         }
646         return puc;
647     }
648 
getRelatedProfiles(@serIdInt int userId)649     List<UserInfo> getRelatedProfiles(@UserIdInt int userId) {
650         final List<UserInfo> related;
651         final long origId = Binder.clearCallingIdentity();
652         try {
653             related = mUm.getProfiles(userId, true);
654         } catch (RemoteException e) {
655             Slog.e(TAG, "Remote Exception calling UserManager: " + e);
656             return null;
657         } finally{
658             Binder.restoreCallingIdentity(origId);
659         }
660         return related;
661     }
662 
663     /** Check if the user has the given restriction set. Default to true if error occured during
664      * calling UserManager, so it fails safe.
665      */
hasRestriction(String restriction, int userId)666     private boolean hasRestriction(String restriction, int userId) {
667         try {
668             return mUm.hasUserRestriction(restriction, userId);
669         } catch (RemoteException e) {
670             Slog.e(TAG, "Remote Exception calling UserManager.getUserRestrictions: ", e);
671             // Fails safe
672             return true;
673         }
674     }
675 
setPrimaryClipInternal(@ullable ClipData clip, int uid)676     void setPrimaryClipInternal(@Nullable ClipData clip, int uid) {
677         synchronized (mLock) {
678             setPrimaryClipInternalLocked(clip, uid, null);
679         }
680     }
681 
682     @GuardedBy("mLock")
setPrimaryClipInternalLocked( @ullable ClipData clip, int uid, @Nullable String sourcePackage)683     private void setPrimaryClipInternalLocked(
684             @Nullable ClipData clip, int uid, @Nullable String sourcePackage) {
685         mEmulatorClipboardMonitor.accept(clip);
686 
687         final int userId = UserHandle.getUserId(uid);
688 
689         // Update this user
690         setPrimaryClipInternalLocked(getClipboardLocked(userId), clip, uid, sourcePackage);
691 
692         // Update related users
693         List<UserInfo> related = getRelatedProfiles(userId);
694         if (related != null) {
695             int size = related.size();
696             if (size > 1) { // Related profiles list include the current profile.
697                 final boolean canCopy = !hasRestriction(
698                         UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, userId);
699                 // Copy clip data to related users if allowed. If disallowed, then remove
700                 // primary clip in related users to prevent pasting stale content.
701                 if (!canCopy) {
702                     clip = null;
703                 } else if (clip == null) {
704                     // do nothing for canCopy == true and clip == null case
705                     // To prevent from NPE happen in 'new ClipData(clip)' when run
706                     // android.content.cts.ClipboardManagerTest#testClearPrimaryClip
707                 } else {
708                     // We want to fix the uris of the related user's clip without changing the
709                     // uris of the current user's clip.
710                     // So, copy the ClipData, and then copy all the items, so that nothing
711                     // is shared in memory.
712                     clip = new ClipData(clip);
713                     for (int i = clip.getItemCount() - 1; i >= 0; i--) {
714                         clip.setItemAt(i, new ClipData.Item(clip.getItemAt(i)));
715                     }
716                     clip.fixUrisLight(userId);
717                 }
718                 for (int i = 0; i < size; i++) {
719                     int id = related.get(i).id;
720                     if (id != userId) {
721                         final boolean canCopyIntoProfile = !hasRestriction(
722                                 UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
723                         if (canCopyIntoProfile) {
724                             setPrimaryClipInternalNoClassifyLocked(
725                                     getClipboardLocked(id), clip, uid, sourcePackage);
726                         }
727                     }
728                 }
729             }
730         }
731     }
732 
setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip, int uid)733     void setPrimaryClipInternal(PerUserClipboard clipboard, @Nullable ClipData clip,
734             int uid) {
735         synchronized (mLock) {
736             setPrimaryClipInternalLocked(clipboard, clip, uid, null);
737         }
738     }
739 
740     @GuardedBy("mLock")
setPrimaryClipInternalLocked(PerUserClipboard clipboard, @Nullable ClipData clip, int uid, @Nullable String sourcePackage)741     private void setPrimaryClipInternalLocked(PerUserClipboard clipboard, @Nullable ClipData clip,
742             int uid, @Nullable String sourcePackage) {
743         final int userId = UserHandle.getUserId(uid);
744         if (clip != null) {
745             startClassificationLocked(clip, userId);
746         }
747 
748         setPrimaryClipInternalNoClassifyLocked(clipboard, clip, uid, sourcePackage);
749     }
750 
751     @GuardedBy("mLock")
setPrimaryClipInternalNoClassifyLocked(PerUserClipboard clipboard, @Nullable ClipData clip, int uid, @Nullable String sourcePackage)752     private void setPrimaryClipInternalNoClassifyLocked(PerUserClipboard clipboard,
753             @Nullable ClipData clip, int uid, @Nullable String sourcePackage) {
754         revokeUris(clipboard);
755         clipboard.activePermissionOwners.clear();
756         if (clip == null && clipboard.primaryClip == null) {
757             return;
758         }
759         clipboard.primaryClip = clip;
760         clipboard.mNotifiedUids.clear();
761         clipboard.mNotifiedTextClassifierUids.clear();
762         if (clip != null) {
763             clipboard.primaryClipUid = uid;
764             clipboard.mPrimaryClipPackage = sourcePackage;
765         } else {
766             clipboard.primaryClipUid = android.os.Process.NOBODY_UID;
767             clipboard.mPrimaryClipPackage = null;
768         }
769         if (clip != null) {
770             final ClipDescription description = clip.getDescription();
771             if (description != null) {
772                 description.setTimestamp(System.currentTimeMillis());
773             }
774         }
775         sendClipChangedBroadcast(clipboard);
776     }
777 
sendClipChangedBroadcast(PerUserClipboard clipboard)778     private void sendClipChangedBroadcast(PerUserClipboard clipboard) {
779         final long ident = Binder.clearCallingIdentity();
780         final int n = clipboard.primaryClipListeners.beginBroadcast();
781         try {
782             for (int i = 0; i < n; i++) {
783                 try {
784                     ListenerInfo li = (ListenerInfo)
785                             clipboard.primaryClipListeners.getBroadcastCookie(i);
786 
787                     if (clipboardAccessAllowed(
788                             AppOpsManager.OP_READ_CLIPBOARD,
789                             li.mPackageName,
790                             li.mAttributionTag,
791                             li.mUid,
792                             UserHandle.getUserId(li.mUid))) {
793                         clipboard.primaryClipListeners.getBroadcastItem(i)
794                                 .dispatchPrimaryClipChanged();
795                     }
796                 } catch (RemoteException | SecurityException e) {
797                     // The RemoteCallbackList will take care of removing
798                     // the dead object for us.
799                 }
800             }
801         } finally {
802             clipboard.primaryClipListeners.finishBroadcast();
803             Binder.restoreCallingIdentity(ident);
804         }
805     }
806 
807     @GuardedBy("mLock")
startClassificationLocked(@onNull ClipData clip, @UserIdInt int userId)808     private void startClassificationLocked(@NonNull ClipData clip, @UserIdInt int userId) {
809         CharSequence text = (clip.getItemCount() == 0) ? null : clip.getItemAt(0).getText();
810         if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength) {
811             clip.getDescription().setClassificationStatus(
812                     ClipDescription.CLASSIFICATION_NOT_PERFORMED);
813             return;
814         }
815         TextClassifier classifier;
816         final long ident = Binder.clearCallingIdentity();
817         try {
818             classifier = createTextClassificationManagerAsUser(userId)
819                     .createTextClassificationSession(
820                             new TextClassificationContext.Builder(
821                                     getContext().getPackageName(),
822                                     TextClassifier.WIDGET_TYPE_CLIPBOARD
823                             ).build()
824                     );
825         } finally {
826             Binder.restoreCallingIdentity(ident);
827         }
828         if (text.length() > classifier.getMaxGenerateLinksTextLength()) {
829             clip.getDescription().setClassificationStatus(
830                     ClipDescription.CLASSIFICATION_NOT_PERFORMED);
831             return;
832         }
833         mWorkerHandler.post(() -> doClassification(text, clip, classifier, userId));
834     }
835 
836     @WorkerThread
doClassification( CharSequence text, ClipData clip, TextClassifier classifier, @UserIdInt int userId)837     private void doClassification(
838             CharSequence text, ClipData clip, TextClassifier classifier, @UserIdInt int userId) {
839         TextLinks.Request request = new TextLinks.Request.Builder(text).build();
840         TextLinks links = classifier.generateLinks(request);
841 
842         // Find the highest confidence for each entity in the text.
843         ArrayMap<String, Float> confidences = new ArrayMap<>();
844         for (TextLinks.TextLink link : links.getLinks()) {
845             for (int i = 0; i < link.getEntityCount(); i++) {
846                 String entity = link.getEntity(i);
847                 float conf = link.getConfidenceScore(entity);
848                 if (conf > confidences.getOrDefault(entity, 0f)) {
849                     confidences.put(entity, conf);
850                 }
851             }
852         }
853 
854         synchronized (mLock) {
855             PerUserClipboard clipboard = getClipboardLocked(userId);
856             if (clipboard.primaryClip == clip) {
857                 applyClassificationAndSendBroadcastLocked(
858                         clipboard, confidences, links, classifier);
859 
860                 // Also apply to related profiles if needed
861                 List<UserInfo> related = getRelatedProfiles(userId);
862                 if (related != null) {
863                     int size = related.size();
864                     for (int i = 0; i < size; i++) {
865                         int id = related.get(i).id;
866                         if (id != userId) {
867                             final boolean canCopyIntoProfile = !hasRestriction(
868                                     UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id);
869                             if (canCopyIntoProfile) {
870                                 PerUserClipboard relatedClipboard = getClipboardLocked(id);
871                                 if (hasTextLocked(relatedClipboard, text)) {
872                                     applyClassificationAndSendBroadcastLocked(
873                                             relatedClipboard, confidences, links, classifier);
874                                 }
875                             }
876                         }
877                     }
878                 }
879             }
880         }
881     }
882 
883     @GuardedBy("mLock")
applyClassificationAndSendBroadcastLocked( PerUserClipboard clipboard, ArrayMap<String, Float> confidences, TextLinks links, TextClassifier classifier)884     private void applyClassificationAndSendBroadcastLocked(
885             PerUserClipboard clipboard, ArrayMap<String, Float> confidences, TextLinks links,
886             TextClassifier classifier) {
887         clipboard.mTextClassifier = classifier;
888         clipboard.primaryClip.getDescription().setConfidenceScores(confidences);
889         if (!links.getLinks().isEmpty()) {
890             clipboard.primaryClip.getItemAt(0).setTextLinks(links);
891         }
892         sendClipChangedBroadcast(clipboard);
893     }
894 
895     @GuardedBy("mLock")
hasTextLocked(PerUserClipboard clipboard, @NonNull CharSequence text)896     private boolean hasTextLocked(PerUserClipboard clipboard, @NonNull CharSequence text) {
897         return clipboard.primaryClip != null
898                 && clipboard.primaryClip.getItemCount() > 0
899                 && text.equals(clipboard.primaryClip.getItemAt(0).getText());
900     }
901 
isDeviceLocked(@serIdInt int userId)902     private boolean isDeviceLocked(@UserIdInt int userId) {
903         final long token = Binder.clearCallingIdentity();
904         try {
905             final KeyguardManager keyguardManager = getContext().getSystemService(
906                     KeyguardManager.class);
907             return keyguardManager != null && keyguardManager.isDeviceLocked(userId);
908         } finally {
909             Binder.restoreCallingIdentity(token);
910         }
911     }
912 
checkUriOwner(Uri uri, int sourceUid)913     private void checkUriOwner(Uri uri, int sourceUid) {
914         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
915 
916         final long ident = Binder.clearCallingIdentity();
917         try {
918             // This will throw SecurityException if caller can't grant
919             mUgmInternal.checkGrantUriPermission(sourceUid, null,
920                     ContentProvider.getUriWithoutUserId(uri),
921                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
922                     ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
923         } finally {
924             Binder.restoreCallingIdentity(ident);
925         }
926     }
927 
checkItemOwner(ClipData.Item item, int uid)928     private void checkItemOwner(ClipData.Item item, int uid) {
929         if (item.getUri() != null) {
930             checkUriOwner(item.getUri(), uid);
931         }
932         Intent intent = item.getIntent();
933         if (intent != null && intent.getData() != null) {
934             checkUriOwner(intent.getData(), uid);
935         }
936     }
937 
checkDataOwner(ClipData data, int uid)938     private void checkDataOwner(ClipData data, int uid) {
939         final int N = data.getItemCount();
940         for (int i=0; i<N; i++) {
941             checkItemOwner(data.getItemAt(i), uid);
942         }
943     }
944 
grantUriPermission(Uri uri, int sourceUid, String targetPkg, int targetUserId)945     private void grantUriPermission(Uri uri, int sourceUid, String targetPkg,
946             int targetUserId) {
947         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
948 
949         final long ident = Binder.clearCallingIdentity();
950         try {
951             mUgm.grantUriPermissionFromOwner(mPermissionOwner, sourceUid, targetPkg,
952                     ContentProvider.getUriWithoutUserId(uri),
953                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
954                     ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)),
955                     targetUserId);
956         } catch (RemoteException ignored) {
957             // Ignored because we're in same process
958         } finally {
959             Binder.restoreCallingIdentity(ident);
960         }
961     }
962 
grantItemPermission(ClipData.Item item, int sourceUid, String targetPkg, int targetUserId)963     private void grantItemPermission(ClipData.Item item, int sourceUid, String targetPkg,
964             int targetUserId) {
965         if (item.getUri() != null) {
966             grantUriPermission(item.getUri(), sourceUid, targetPkg, targetUserId);
967         }
968         Intent intent = item.getIntent();
969         if (intent != null && intent.getData() != null) {
970             grantUriPermission(intent.getData(), sourceUid, targetPkg, targetUserId);
971         }
972     }
973 
974     @GuardedBy("mLock")
addActiveOwnerLocked(int uid, String pkg)975     private void addActiveOwnerLocked(int uid, String pkg) {
976         final IPackageManager pm = AppGlobals.getPackageManager();
977         final int targetUserHandle = UserHandle.getCallingUserId();
978         final long oldIdentity = Binder.clearCallingIdentity();
979         try {
980             PackageInfo pi = pm.getPackageInfo(pkg, 0, targetUserHandle);
981             if (pi == null) {
982                 throw new IllegalArgumentException("Unknown package " + pkg);
983             }
984             if (!UserHandle.isSameApp(pi.applicationInfo.uid, uid)) {
985                 throw new SecurityException("Calling uid " + uid
986                         + " does not own package " + pkg);
987             }
988         } catch (RemoteException e) {
989             // Can't happen; the package manager is in the same process
990         } finally {
991             Binder.restoreCallingIdentity(oldIdentity);
992         }
993         PerUserClipboard clipboard = getClipboardLocked(UserHandle.getUserId(uid));
994         if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) {
995             final int N = clipboard.primaryClip.getItemCount();
996             for (int i=0; i<N; i++) {
997                 grantItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid,
998                         pkg, UserHandle.getUserId(uid));
999             }
1000             clipboard.activePermissionOwners.add(pkg);
1001         }
1002     }
1003 
revokeUriPermission(Uri uri, int sourceUid)1004     private void revokeUriPermission(Uri uri, int sourceUid) {
1005         if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
1006 
1007         final long ident = Binder.clearCallingIdentity();
1008         try {
1009             mUgmInternal.revokeUriPermissionFromOwner(mPermissionOwner,
1010                     ContentProvider.getUriWithoutUserId(uri),
1011                     Intent.FLAG_GRANT_READ_URI_PERMISSION,
1012                     ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
1013         } finally {
1014             Binder.restoreCallingIdentity(ident);
1015         }
1016     }
1017 
revokeItemPermission(ClipData.Item item, int sourceUid)1018     private void revokeItemPermission(ClipData.Item item, int sourceUid) {
1019         if (item.getUri() != null) {
1020             revokeUriPermission(item.getUri(), sourceUid);
1021         }
1022         Intent intent = item.getIntent();
1023         if (intent != null && intent.getData() != null) {
1024             revokeUriPermission(intent.getData(), sourceUid);
1025         }
1026     }
1027 
revokeUris(PerUserClipboard clipboard)1028     private void revokeUris(PerUserClipboard clipboard) {
1029         if (clipboard.primaryClip == null) {
1030             return;
1031         }
1032         final int N = clipboard.primaryClip.getItemCount();
1033         for (int i=0; i<N; i++) {
1034             revokeItemPermission(clipboard.primaryClip.getItemAt(i), clipboard.primaryClipUid);
1035         }
1036     }
1037 
clipboardAccessAllowed( int op, String callingPackage, String attributionTag, int uid, @UserIdInt int userId)1038     private boolean clipboardAccessAllowed(
1039             int op, String callingPackage, String attributionTag, int uid, @UserIdInt int userId) {
1040         return clipboardAccessAllowed(op, callingPackage, attributionTag, uid, userId, true);
1041     }
1042 
clipboardAccessAllowed( int op, String callingPackage, String attributionTag, int uid, @UserIdInt int userId, boolean shouldNoteOp)1043     private boolean clipboardAccessAllowed(
1044             int op,
1045             String callingPackage,
1046             String attributionTag,
1047             int uid,
1048             @UserIdInt int userId,
1049             boolean shouldNoteOp) {
1050 
1051         boolean allowed;
1052 
1053         // First, verify package ownership to ensure use below is safe.
1054         mAppOps.checkPackage(uid, callingPackage);
1055 
1056         // Nothing in a virtual session is permitted to touch clipboard contents
1057         if (mVdm != null && mVdm.isAppRunningOnAnyVirtualDevice(uid)) {
1058             Slog.w(TAG, "Clipboard access denied to " + uid + "/" + callingPackage
1059                     + " within a virtual device session");
1060             return false;
1061         }
1062 
1063         // Shell can access the clipboard for testing purposes.
1064         if (mPm.checkPermission(android.Manifest.permission.READ_CLIPBOARD_IN_BACKGROUND,
1065                     callingPackage) == PackageManager.PERMISSION_GRANTED) {
1066             allowed = true;
1067         } else {
1068             // The default IME is always allowed to access the clipboard.
1069             allowed = isDefaultIme(userId, callingPackage);
1070         }
1071 
1072         switch (op) {
1073             case AppOpsManager.OP_READ_CLIPBOARD:
1074                 // Clipboard can only be read by applications with focus..
1075                 // or the application have the INTERNAL_SYSTEM_WINDOW and INTERACT_ACROSS_USERS_FULL
1076                 // at the same time. e.x. SystemUI. It needs to check the window focus of
1077                 // Binder.getCallingUid(). Without checking, the user X can't copy any thing from
1078                 // INTERNAL_SYSTEM_WINDOW to the other applications.
1079                 if (!allowed) {
1080                     allowed = mWm.isUidFocused(uid)
1081                             || isInternalSysWindowAppWithWindowFocus(callingPackage);
1082                 }
1083                 if (!allowed && mContentCaptureInternal != null) {
1084                     // ...or the Content Capture Service
1085                     // The uid parameter of mContentCaptureInternal.isContentCaptureServiceForUser
1086                     // is used to check if the uid has the permission BIND_CONTENT_CAPTURE_SERVICE.
1087                     // if the application has the permission, let it to access user's clipboard.
1088                     // To passed synthesized uid user#10_app#systemui may not tell the real uid.
1089                     // userId must pass intending userId. i.e. user#10.
1090                     allowed = mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId);
1091                 }
1092                 if (!allowed && mAutofillInternal != null) {
1093                     // ...or the Augmented Autofill Service
1094                     // The uid parameter of mAutofillInternal.isAugmentedAutofillServiceForUser
1095                     // is used to check if the uid has the permission BIND_AUTOFILL_SERVICE.
1096                     // if the application has the permission, let it to access user's clipboard.
1097                     // To passed synthesized uid user#10_app#systemui may not tell the real uid.
1098                     // userId must pass intending userId. i.e. user#10.
1099                     allowed = mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId);
1100                 }
1101                 break;
1102             case AppOpsManager.OP_WRITE_CLIPBOARD:
1103                 // Writing is allowed without focus.
1104                 allowed = true;
1105                 break;
1106             default:
1107                 throw new IllegalArgumentException("Unknown clipboard appop " + op);
1108         }
1109         if (!allowed) {
1110             Slog.e(TAG, "Denying clipboard access to " + callingPackage
1111                     + ", application is not in focus nor is it a system service for "
1112                     + "user " + userId);
1113             return false;
1114         }
1115         // Finally, check the app op.
1116         int appOpsResult;
1117         if (shouldNoteOp) {
1118             appOpsResult = mAppOps.noteOp(op, uid, callingPackage, attributionTag, null);
1119         } else {
1120             appOpsResult = mAppOps.checkOp(op, uid, callingPackage);
1121         }
1122 
1123         return appOpsResult == AppOpsManager.MODE_ALLOWED;
1124     }
1125 
isDefaultIme(int userId, String packageName)1126     private boolean isDefaultIme(int userId, String packageName) {
1127         String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
1128                 Settings.Secure.DEFAULT_INPUT_METHOD, userId);
1129         if (!TextUtils.isEmpty(defaultIme)) {
1130             final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
1131             return imePkg.equals(packageName);
1132         }
1133         return false;
1134     }
1135 
1136     /**
1137      * Shows a toast to inform the user that an app has accessed the clipboard. This is only done if
1138      * the setting is enabled, and if the accessing app is not the source of the data and is not the
1139      * IME, the content capture service, or the autofill service. The notification is also only
1140      * shown once per clip for each app.
1141      */
1142     @GuardedBy("mLock")
showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId, PerUserClipboard clipboard)1143     private void showAccessNotificationLocked(String callingPackage, int uid, @UserIdInt int userId,
1144             PerUserClipboard clipboard) {
1145         if (clipboard.primaryClip == null) {
1146             return;
1147         }
1148         if (Settings.Secure.getInt(getContext().getContentResolver(),
1149                 Settings.Secure.CLIPBOARD_SHOW_ACCESS_NOTIFICATIONS,
1150                 (mShowAccessNotifications ? 1 : 0)) == 0) {
1151             return;
1152         }
1153         // Don't notify if the app accessing the clipboard is the same as the current owner.
1154         if (UserHandle.isSameApp(uid, clipboard.primaryClipUid)) {
1155             return;
1156         }
1157         // Exclude special cases: IME, ContentCapture, Autofill.
1158         if (isDefaultIme(userId, callingPackage)) {
1159             return;
1160         }
1161         if (mContentCaptureInternal != null
1162                 && mContentCaptureInternal.isContentCaptureServiceForUser(uid, userId)) {
1163             return;
1164         }
1165         if (mAutofillInternal != null
1166                 && mAutofillInternal.isAugmentedAutofillServiceForUser(uid, userId)) {
1167             return;
1168         }
1169         if (mPm.checkPermission(Manifest.permission.SUPPRESS_CLIPBOARD_ACCESS_NOTIFICATION,
1170                 callingPackage) == PackageManager.PERMISSION_GRANTED) {
1171             return;
1172         }
1173         // Don't notify if already notified for this uid and clip.
1174         if (clipboard.mNotifiedUids.get(uid)) {
1175             return;
1176         }
1177         clipboard.mNotifiedUids.put(uid, true);
1178 
1179         Binder.withCleanCallingIdentity(() -> {
1180             try {
1181                 CharSequence callingAppLabel = mPm.getApplicationLabel(
1182                         mPm.getApplicationInfoAsUser(callingPackage, 0, userId));
1183                 String message =
1184                         getContext().getString(R.string.pasted_from_clipboard, callingAppLabel);
1185                 Slog.i(TAG, message);
1186                 Toast toastToShow;
1187                 if (SafetyProtectionUtils.shouldShowSafetyProtectionResources(getContext())) {
1188                     Drawable safetyProtectionIcon = getContext()
1189                             .getDrawable(R.drawable.ic_safety_protection);
1190                     toastToShow = Toast.makeCustomToastWithIcon(getContext(),
1191                             UiThread.get().getLooper(), message,
1192                             Toast.LENGTH_SHORT, safetyProtectionIcon);
1193                 } else {
1194                     toastToShow = Toast.makeText(
1195                             getContext(), UiThread.get().getLooper(), message,
1196                             Toast.LENGTH_SHORT);
1197                 }
1198                 toastToShow.show();
1199             } catch (PackageManager.NameNotFoundException e) {
1200                 // do nothing
1201             }
1202         });
1203     }
1204 
1205     /**
1206      * Returns true if the provided {@link ClipData} represents a single piece of text. That is, if
1207      * there is only on {@link ClipData.Item}, and that item contains a non-empty piece of text and
1208      * no URI or Intent. Note that HTML may be provided along with text so the presence of
1209      * HtmlText in the clip does not prevent this method returning true.
1210      */
isText(@onNull ClipData data)1211     private static boolean isText(@NonNull ClipData data) {
1212         if (data.getItemCount() > 1) {
1213             return false;
1214         }
1215         ClipData.Item item = data.getItemAt(0);
1216 
1217         return !TextUtils.isEmpty(item.getText()) && item.getUri() == null
1218                 && item.getIntent() == null;
1219     }
1220 
1221     /** Potentially notifies the text classifier that an app is accessing a text clip. */
1222     @GuardedBy("mLock")
notifyTextClassifierLocked( PerUserClipboard clipboard, String callingPackage, int callingUid)1223     private void notifyTextClassifierLocked(
1224             PerUserClipboard clipboard, String callingPackage, int callingUid) {
1225         if (clipboard.primaryClip == null) {
1226             return;
1227         }
1228         ClipData.Item item = clipboard.primaryClip.getItemAt(0);
1229         if (item == null) {
1230             return;
1231         }
1232         if (!isText(clipboard.primaryClip)) {
1233             return;
1234         }
1235         TextClassifier textClassifier = clipboard.mTextClassifier;
1236         // Don't notify text classifier if we haven't used it to annotate the text in the clip.
1237         if (textClassifier == null) {
1238             return;
1239         }
1240         // Don't notify text classifier if the app reading the clipboard does not have the focus.
1241         if (!mWm.isUidFocused(callingUid)) {
1242             return;
1243         }
1244         // Don't notify text classifier again if already notified for this uid and clip.
1245         if (clipboard.mNotifiedTextClassifierUids.get(callingUid)) {
1246             return;
1247         }
1248         clipboard.mNotifiedTextClassifierUids.put(callingUid, true);
1249         Binder.withCleanCallingIdentity(() -> {
1250             TextClassifierEvent.TextLinkifyEvent pasteEvent =
1251                     new TextClassifierEvent.TextLinkifyEvent.Builder(
1252                             TextClassifierEvent.TYPE_READ_CLIPBOARD)
1253                             .setEventContext(new TextClassificationContext.Builder(
1254                                     callingPackage, TextClassifier.WIDGET_TYPE_CLIPBOARD)
1255                                     .build())
1256                             .setExtras(
1257                                     Bundle.forPair("source_package", clipboard.mPrimaryClipPackage))
1258                             .build();
1259             textClassifier.onTextClassifierEvent(pasteEvent);
1260         });
1261     }
1262 
createTextClassificationManagerAsUser(@serIdInt int userId)1263     private TextClassificationManager createTextClassificationManagerAsUser(@UserIdInt int userId) {
1264         Context context = getContext().createContextAsUser(UserHandle.of(userId), /* flags= */ 0);
1265         return context.getSystemService(TextClassificationManager.class);
1266     }
1267 }
1268