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