Loading packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java +16 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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() { Loading packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +53 −15 Original line number Diff line number Diff line Loading @@ -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<>(); Loading @@ -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. Loading Loading @@ -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)); } /** Loading Loading @@ -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]; Loading Loading
packages/ExtServices/src/android/ext/services/notification/NotificationEntry.java +16 −3 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading Loading @@ -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() { Loading
packages/ExtServices/src/android/ext/services/notification/SmartActionsHelper.java +53 −15 Original line number Diff line number Diff line Loading @@ -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<>(); Loading @@ -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. Loading Loading @@ -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)); } /** Loading Loading @@ -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]; Loading