From d287485e1734a59a6492df349c7d6bc715035349 Mon Sep 17 00:00:00 2001 From: Tony Mak Date: Tue, 23 Oct 2018 14:27:40 +0100 Subject: Update ExtService to use suggestConversationActions API to generate replies Also removed the SYS_PROP_SMART_REPLIES_EXPERIMENT checking, as we are now hooking up to the real smart replies model. BUG: 111437455 BUG: 111406942 Test: Sent a message to myself, observe the generated replies. Change-Id: I521c5bf5ad010c462c5ea8e1ada565fdf9cab87d --- .../services/notification/NotificationEntry.java | 19 +++++- .../services/notification/SmartActionsHelper.java | 68 +++++++++++++++++----- 2 files changed, 69 insertions(+), 18 deletions(-) diff --git a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java index 8fee822f11c0..6f437bd5d96f 100644 --- a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java +++ b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java @@ -15,6 +15,7 @@ */ package android.ext.services.notification; +import static android.app.Notification.CATEGORY_MESSAGE; import static android.app.NotificationChannel.USER_LOCKED_IMPORTANCE; import static android.app.NotificationManager.IMPORTANCE_DEFAULT; import static android.app.NotificationManager.IMPORTANCE_HIGH; @@ -148,6 +149,18 @@ public class NotificationEntry { return Objects.equals(getNotification().category, category); } + /** + * Similar to {@link #isCategory(String)}, but checking the public version of the notification, + * if available. + */ + public boolean isPublicVersionCategory(String category) { + Notification publicVersion = getNotification().publicVersion; + if (publicVersion == null) { + return false; + } + return Objects.equals(publicVersion.category, category); + } + public boolean isAudioAttributesUsage(int usage) { return mAttributes != null && mAttributes.getUsage() == usage; } @@ -175,9 +188,9 @@ public class NotificationEntry { } protected boolean isMessaging() { - return isCategory(Notification.CATEGORY_MESSAGE) - || hasStyle(Notification.MessagingStyle.class) - || hasInlineReply(); + return isCategory(CATEGORY_MESSAGE) + || isPublicVersionCategory(CATEGORY_MESSAGE) + || hasStyle(Notification.MessagingStyle.class); } public boolean hasInlineReply() { diff --git a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java index 37a98fd1dd6c..b2fc41783516 100644 --- a/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +++ b/packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java @@ -19,23 +19,22 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; import android.app.RemoteAction; -import android.app.RemoteInput; import android.content.Context; import android.os.Bundle; import android.os.Parcelable; import android.os.Process; -import android.os.SystemProperties; -import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.ArrayMap; +import android.view.textclassifier.ConversationActions; import android.view.textclassifier.TextClassification; import android.view.textclassifier.TextClassificationManager; import android.view.textclassifier.TextClassifier; import android.view.textclassifier.TextLinks; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; public class SmartActionsHelper { private static final ArrayList EMPTY_ACTION_LIST = new ArrayList<>(); @@ -50,12 +49,18 @@ public class SmartActionsHelper { private static final int MAX_ACTION_EXTRACTION_TEXT_LENGTH = 400; private static final int MAX_ACTIONS_PER_LINK = 1; private static final int MAX_SMART_ACTIONS = Notification.MAX_ACTION_BUTTONS; - // Allow us to test out smart reply with dumb suggestions, it is disabled by default. - // TODO: Removed this once we have the model. - private static final String SYS_PROP_SMART_REPLIES_EXPERIMENT = - "persist.sys.smart_replies_experiment"; + private static final int MAX_SUGGESTED_REPLIES = 3; - SmartActionsHelper() {} + private static final ConversationActions.TypeConfig TYPE_CONFIG = + new ConversationActions.TypeConfig.Builder().setIncludedTypes( + Collections.singletonList(ConversationActions.TYPE_TEXT_REPLY)) + .includeTypesFromTextClassifier(false) + .build(); + private static final List HINTS = + Collections.singletonList(ConversationActions.HINT_FOR_NOTIFICATION); + + SmartActionsHelper() { + } /** * Adds action adjustments based on the notification contents. @@ -92,8 +97,31 @@ public class SmartActionsHelper { if (context == null) { return EMPTY_REPLY_LIST; } - // TODO: replaced this with our model when it is ready. - return new ArrayList<>(Arrays.asList("Yes, please", "No, thanks")); + TextClassificationManager tcm = context.getSystemService(TextClassificationManager.class); + if (tcm == null) { + return EMPTY_REPLY_LIST; + } + CharSequence text = getMostSalientActionText(entry.getNotification()); + ConversationActions.Message message = + new ConversationActions.Message.Builder() + .setText(text) + .build(); + + ConversationActions.Request request = + new ConversationActions.Request.Builder(Collections.singletonList(message)) + .setMaxSuggestions(MAX_SUGGESTED_REPLIES) + .setHints(HINTS) + .setTypeConfig(TYPE_CONFIG) + .build(); + + TextClassifier textClassifier = tcm.getTextClassifier(); + List conversationActions = + textClassifier.suggestConversationActions(request).getConversationActions(); + + return conversationActions.stream() + .map(conversationAction -> conversationAction.getTextReply()) + .filter(textReply -> !TextUtils.isEmpty(textReply)) + .collect(Collectors.toCollection(ArrayList::new)); } /** @@ -124,20 +152,30 @@ public class SmartActionsHelper { } private boolean isEligibleForReplyAdjustment(@NonNull NotificationEntry entry) { - if (!SystemProperties.getBoolean(SYS_PROP_SMART_REPLIES_EXPERIMENT, false)) { + if (!Process.myUserHandle().equals(entry.getSbn().getUser())) { return false; } - Notification notification = entry.getNotification(); - if (notification.actions == null) { + String pkg = entry.getSbn().getPackageName(); + if (TextUtils.isEmpty(pkg) || pkg.equals("android")) { + return false; + } + // For now, we are only interested in messages. + if (!entry.isMessaging()) { + return false; + } + // Does not make sense to provide suggested replies if it is not something that can be + // replied. + if (!entry.hasInlineReply()) { return false; } - return entry.hasInlineReply(); + return true; } /** Returns the text most salient for action extraction in a notification. */ @Nullable private CharSequence getMostSalientActionText(@NonNull Notification notification) { /* If it's messaging style, use the most recent message. */ + // TODO: Use the last few X messages instead and take the Person object into consideration. Parcelable[] messages = notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES); if (messages != null && messages.length != 0) { Bundle lastMessage = (Bundle) messages[messages.length - 1]; -- cgit v1.2.3-59-g8ed1b