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