Merge "Update ExtService to use suggestConversationActions API to generate replies"
diff --git a/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java b/packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java
index 8fee822..6f437bd5 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 @@
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 @@
}
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 37a98fd..b2fc417 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.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<Notification.Action> EMPTY_ACTION_LIST = new ArrayList<>();
@@ -50,12 +49,18 @@
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<String> HINTS =
+ Collections.singletonList(ConversationActions.HINT_FOR_NOTIFICATION);
+
+ SmartActionsHelper() {
+ }
/**
* Adds action adjustments based on the notification contents.
@@ -92,8 +97,31 @@
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.ConversationAction> 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 @@
}
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;
}
- return entry.hasInlineReply();
+ // 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 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];