diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java index 11adfa3cecc0d..d41cda1021033 100644 --- a/core/java/android/content/ClipboardManager.java +++ b/core/java/android/content/ClipboardManager.java @@ -100,6 +100,10 @@ public class ClipboardManager extends android.text.ClipboardManager { /** * Callback that is invoked by {@link android.content.ClipboardManager} when the primary * clip changes. + * + *

This is called when the result of {@link ClipDescription#getClassificationStatus()} + * changes, as well as when new clip data is set. So in cases where text classification is + * performed, this callback may be invoked multiple times for the same clip. */ void onPrimaryClipChanged(); } diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index e2aa07102ba3e..ab67b138fdbfc 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -606,6 +606,10 @@ public class ClipboardService extends SystemService { description.setTimestamp(System.currentTimeMillis()); } } + sendClipChangedBroadcast(clipboard); + } + + private void sendClipChangedBroadcast(PerUserClipboard clipboard) { final long ident = Binder.clearCallingIdentity(); final int n = clipboard.primaryClipListeners.beginBroadcast(); try { @@ -615,7 +619,7 @@ public class ClipboardService extends SystemService { clipboard.primaryClipListeners.getBroadcastCookie(i); if (clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, li.mPackageName, - li.mUid, UserHandle.getUserId(li.mUid))) { + li.mUid, UserHandle.getUserId(li.mUid))) { clipboard.primaryClipListeners.getBroadcastItem(i) .dispatchPrimaryClipChanged(); } @@ -632,7 +636,8 @@ public class ClipboardService extends SystemService { @GuardedBy("mLock") private void startClassificationLocked(@NonNull ClipData clip, @UserIdInt int userId) { - if (clip.getItemCount() == 0) { + CharSequence text = (clip.getItemCount() == 0) ? null : clip.getItemAt(0).getText(); + if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength) { clip.getDescription().setClassificationStatus( ClipDescription.CLASSIFICATION_NOT_PERFORMED); return; @@ -650,20 +655,17 @@ public class ClipboardService extends SystemService { } finally { Binder.restoreCallingIdentity(ident); } - CharSequence text = clip.getItemAt(0).getText(); - if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength - || text.length() > classifier.getMaxGenerateLinksTextLength()) { + if (text.length() > classifier.getMaxGenerateLinksTextLength()) { clip.getDescription().setClassificationStatus( ClipDescription.CLASSIFICATION_NOT_PERFORMED); return; } - getClipboardLocked(userId).mTextClassifier = classifier; - mWorkerHandler.post(() -> doClassification(text, clip, classifier)); + mWorkerHandler.post(() -> doClassification(text, clip, classifier, userId)); } @WorkerThread private void doClassification( - CharSequence text, ClipData clip, TextClassifier classifier) { + CharSequence text, ClipData clip, TextClassifier classifier, @UserIdInt int userId) { TextLinks.Request request = new TextLinks.Request.Builder(text).build(); TextLinks links = classifier.generateLinks(request); @@ -680,13 +682,53 @@ public class ClipboardService extends SystemService { } synchronized (mLock) { - clip.getDescription().setConfidenceScores(confidences); - if (!links.getLinks().isEmpty()) { - clip.getItemAt(0).setTextLinks(links); + PerUserClipboard clipboard = getClipboardLocked(userId); + if (clipboard.primaryClip == clip) { + applyClassificationAndSendBroadcastLocked( + clipboard, confidences, links, classifier); + + // Also apply to related profiles if needed + List related = getRelatedProfiles(userId); + if (related != null) { + int size = related.size(); + for (int i = 0; i < size; i++) { + int id = related.get(i).id; + if (id != userId) { + final boolean canCopyIntoProfile = !hasRestriction( + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, id); + if (canCopyIntoProfile) { + PerUserClipboard relatedClipboard = getClipboardLocked(id); + if (hasTextLocked(relatedClipboard, text)) { + applyClassificationAndSendBroadcastLocked( + relatedClipboard, confidences, links, classifier); + } + } + } + } + } } } } + @GuardedBy("mLock") + private void applyClassificationAndSendBroadcastLocked( + PerUserClipboard clipboard, ArrayMap confidences, TextLinks links, + TextClassifier classifier) { + clipboard.mTextClassifier = classifier; + clipboard.primaryClip.getDescription().setConfidenceScores(confidences); + if (!links.getLinks().isEmpty()) { + clipboard.primaryClip.getItemAt(0).setTextLinks(links); + } + sendClipChangedBroadcast(clipboard); + } + + @GuardedBy("mLock") + private boolean hasTextLocked(PerUserClipboard clipboard, @NonNull CharSequence text) { + return clipboard.primaryClip != null + && clipboard.primaryClip.getItemCount() > 0 + && text.equals(clipboard.primaryClip.getItemAt(0).getText()); + } + private boolean isDeviceLocked(@UserIdInt int userId) { final long token = Binder.clearCallingIdentity(); try {