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
This commit is contained in:
Tony Mak
2018-10-23 14:27:40 +01:00
parent c6f53ea93e
commit d287485e17
2 changed files with 69 additions and 18 deletions

View File

@@ -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() {

View File

@@ -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<Notification.Action> 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<String> 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.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 @@ 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;
}
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];