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