diff --git a/core/java/android/view/textclassifier/TextClassifierEvent.java b/core/java/android/view/textclassifier/TextClassifierEvent.java index db8801150f0d8..90667cf54f930 100644 --- a/core/java/android/view/textclassifier/TextClassifierEvent.java +++ b/core/java/android/view/textclassifier/TextClassifierEvent.java @@ -86,7 +86,8 @@ public abstract class TextClassifierEvent implements Parcelable { TYPE_ACTIONS_SHOWN, TYPE_LINK_CLICKED, TYPE_OVERTYPE, TYPE_COPY_ACTION, TYPE_PASTE_ACTION, TYPE_CUT_ACTION, TYPE_SHARE_ACTION, TYPE_SMART_ACTION, TYPE_SELECTION_DRAG, TYPE_SELECTION_DESTROYED, TYPE_OTHER_ACTION, TYPE_SELECT_ALL, - TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED, TYPE_LINKS_GENERATED}) + TYPE_SELECTION_RESET, TYPE_MANUAL_REPLY, TYPE_ACTIONS_GENERATED, TYPE_LINKS_GENERATED, + TYPE_READ_CLIPBOARD}) public @interface Type { // For custom event types, use range 1,000,000+. } @@ -135,6 +136,13 @@ public abstract class TextClassifierEvent implements Parcelable { public static final int TYPE_ACTIONS_GENERATED = 20; /** Some text links were generated.*/ public static final int TYPE_LINKS_GENERATED = 21; + /** + * Read a clipboard. + * TODO: Make this public. + * + * @hide + */ + public static final int TYPE_READ_CLIPBOARD = 22; @Category private final int mEventCategory; diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 9c10814c7591c..47c7e39025569 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java @@ -45,6 +45,7 @@ import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -67,6 +68,7 @@ import android.view.autofill.AutofillManagerInternal; import android.view.textclassifier.TextClassificationContext; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; +import android.view.textclassifier.TextClassifierEvent; import android.view.textclassifier.TextLinks; import android.widget.Toast; @@ -211,9 +213,18 @@ public class ClipboardService extends SystemService { /** Uids that have already triggered a toast notification for {@link #primaryClip} */ final SparseBooleanArray mNotifiedUids = new SparseBooleanArray(); + /** + * Uids that have already triggered a notification to text classifier for + * {@link #primaryClip}. + */ + final SparseBooleanArray mNotifiedTextClassifierUids = new SparseBooleanArray(); + final HashSet activePermissionOwners = new HashSet(); + /** The text classifier session that is used to annotate the text in the primary clip. */ + TextClassifier mTextClassifier; + PerUserClipboard(int userId) { this.userId = userId; } @@ -371,6 +382,7 @@ public class ClipboardService extends SystemService { addActiveOwnerLocked(intendingUid, pkg); PerUserClipboard clipboard = getClipboardLocked(intendingUserId); showAccessNotificationLocked(pkg, intendingUid, intendingUserId, clipboard); + notifyTextClassifierLocked(clipboard, pkg, intendingUid); return clipboard.primaryClip; } } @@ -580,6 +592,7 @@ public class ClipboardService extends SystemService { } clipboard.primaryClip = clip; clipboard.mNotifiedUids.clear(); + clipboard.mNotifiedTextClassifierUids.clear(); if (clip != null) { clipboard.primaryClipUid = uid; clipboard.mPrimaryClipPackage = sourcePackage; @@ -619,6 +632,11 @@ public class ClipboardService extends SystemService { @GuardedBy("mLock") private void startClassificationLocked(@NonNull ClipData clip, @UserIdInt int userId) { + if (clip.getItemCount() == 0) { + clip.getDescription().setClassificationStatus( + ClipDescription.CLASSIFICATION_NOT_PERFORMED); + return; + } TextClassifier classifier; final long ident = Binder.clearCallingIdentity(); try { @@ -627,17 +645,11 @@ public class ClipboardService extends SystemService { new TextClassificationContext.Builder( getContext().getPackageName(), TextClassifier.WIDGET_TYPE_CLIPBOARD - ).build() - ); + ).build() + ); } finally { Binder.restoreCallingIdentity(ident); } - - if (clip.getItemCount() == 0) { - clip.getDescription().setClassificationStatus( - ClipDescription.CLASSIFICATION_NOT_PERFORMED); - return; - } CharSequence text = clip.getItemAt(0).getText(); if (TextUtils.isEmpty(text) || text.length() > mMaxClassificationLength || text.length() > classifier.getMaxGenerateLinksTextLength()) { @@ -645,7 +657,7 @@ public class ClipboardService extends SystemService { ClipDescription.CLASSIFICATION_NOT_PERFORMED); return; } - + getClipboardLocked(userId).mTextClassifier = classifier; mWorkerHandler.post(() -> doClassification(text, clip, classifier)); } @@ -653,12 +665,7 @@ public class ClipboardService extends SystemService { private void doClassification( CharSequence text, ClipData clip, TextClassifier classifier) { TextLinks.Request request = new TextLinks.Request.Builder(text).build(); - TextLinks links; - try { - links = classifier.generateLinks(request); - } finally { - classifier.destroy(); - } + TextLinks links = classifier.generateLinks(request); // Find the highest confidence for each entity in the text. ArrayMap confidences = new ArrayMap<>(); @@ -979,6 +986,48 @@ public class ClipboardService extends SystemService { && item.getIntent() == null; } + /** Potentially notifies the text classifier that an app is accessing a text clip. */ + @GuardedBy("mLock") + private void notifyTextClassifierLocked( + PerUserClipboard clipboard, String callingPackage, int callingUid) { + if (clipboard.primaryClip == null) { + return; + } + ClipData.Item item = clipboard.primaryClip.getItemAt(0); + if (item == null) { + return; + } + if (!isText(clipboard.primaryClip)) { + return; + } + TextClassifier textClassifier = clipboard.mTextClassifier; + // Don't notify text classifier if we haven't used it to annotate the text in the clip. + if (textClassifier == null) { + return; + } + // Don't notify text classifier if the app reading the clipboard does not have the focus. + if (!mWm.isUidFocused(callingUid)) { + return; + } + // Don't notify text classifier again if already notified for this uid and clip. + if (clipboard.mNotifiedTextClassifierUids.get(callingUid)) { + return; + } + clipboard.mNotifiedTextClassifierUids.put(callingUid, true); + Binder.withCleanCallingIdentity(() -> { + TextClassifierEvent.TextLinkifyEvent pasteEvent = + new TextClassifierEvent.TextLinkifyEvent.Builder( + TextClassifierEvent.TYPE_READ_CLIPBOARD) + .setEventContext(new TextClassificationContext.Builder( + callingPackage, TextClassifier.WIDGET_TYPE_CLIPBOARD) + .build()) + .setExtras( + Bundle.forPair("source_package", clipboard.mPrimaryClipPackage)) + .build(); + textClassifier.onTextClassifierEvent(pasteEvent); + }); + } + private TextClassificationManager createTextClassificationManagerAsUser(@UserIdInt int userId) { Context context = getContext().createContextAsUser(UserHandle.of(userId), /* flags= */ 0); return context.getSystemService(TextClassificationManager.class);