diff options
| author | 2018-11-27 19:51:55 +0000 | |
|---|---|---|
| committer | 2018-11-27 19:51:55 +0000 | |
| commit | 20dbbf22b8ef69a9556ae729c2c60a12c60a3d8f (patch) | |
| tree | 82dcd2554bc3719d834213a93c21242de9feaf35 | |
| parent | d30510904ed2297f3eb2381b59c0dc5cab0a3d5e (diff) | |
| parent | 299967040661f0722585c9a3a180069cdfb14d75 (diff) | |
Merge "Add onSuggestedReplySent in NotificationAssistantService"
17 files changed, 311 insertions, 124 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index 7be995ce8703..2b41927740cb 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5082,8 +5082,11 @@ package android.service.notification { method public void onNotificationRemoved(android.service.notification.StatusBarNotification, android.service.notification.NotificationListenerService.RankingMap, android.service.notification.NotificationStats, int); method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String); method public void onNotificationsSeen(java.util.List<java.lang.String>); + method public void onSuggestedReplySent(java.lang.String, java.lang.CharSequence, int); method public final void unsnoozeNotification(java.lang.String); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; + field public static final int SOURCE_FROM_APP = 0; // 0x0 + field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1 } public final class NotificationStats implements android.os.Parcelable { diff --git a/api/test-current.txt b/api/test-current.txt index 0fa83f11a4ba..738caeca1b64 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1162,8 +1162,11 @@ package android.service.notification { method public void onNotificationExpansionChanged(java.lang.String, boolean, boolean); method public abstract void onNotificationSnoozedUntilContext(android.service.notification.StatusBarNotification, java.lang.String); method public void onNotificationsSeen(java.util.List<java.lang.String>); + method public void onSuggestedReplySent(java.lang.String, java.lang.CharSequence, int); method public final void unsnoozeNotification(java.lang.String); field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; + field public static final int SOURCE_FROM_APP = 0; // 0x0 + field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1 } public abstract class NotificationListenerService extends android.app.Service { diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index 0988510f5503..ab94f432968c 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -49,4 +49,5 @@ oneway interface INotificationListener void onNotificationsSeen(in List<String> keys); void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded); void onNotificationDirectReply(String key); + void onSuggestedReplySent(String key, in CharSequence reply, int source); } diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index 90f4792face9..68da83f9e7d8 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -16,6 +16,9 @@ package android.service.notification; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; import android.annotation.SdkConstant; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -33,6 +36,7 @@ import android.util.Log; import com.android.internal.os.SomeArgs; +import java.lang.annotation.Retention; import java.util.List; /** @@ -63,6 +67,13 @@ import java.util.List; public abstract class NotificationAssistantService extends NotificationListenerService { private static final String TAG = "NotificationAssistants"; + /** @hide */ + @Retention(SOURCE) + @IntDef({SOURCE_FROM_APP, SOURCE_FROM_ASSISTANT}) + public @interface Source {} + public static final int SOURCE_FROM_APP = 0; + public static final int SOURCE_FROM_ASSISTANT = 1; + /** * The {@link Intent} that must be declared as handled by the service. */ @@ -175,6 +186,14 @@ public abstract class NotificationAssistantService extends NotificationListenerS public void onNotificationDirectReply(String key) {} /** + * Implement this to know when a suggested reply is sent. + * @param key the notification key + * @param reply the reply that is just sent + * @param source the source of the reply, e.g. SOURCE_FROM_APP + */ + public void onSuggestedReplySent(String key, CharSequence reply, @Source int source) {} + + /** * Updates a notification. N.B. this won’t cause * an existing notification to alert, but might allow a future update to * this notification to alert. @@ -289,6 +308,15 @@ public abstract class NotificationAssistantService extends NotificationListenerS mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT, args) .sendToTarget(); } + + @Override + public void onSuggestedReplySent(String key, CharSequence reply, int source) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = key; + args.arg2 = reply; + args.argi2 = source; + mHandler.obtainMessage(MyHandler.MSG_ON_SUGGESTED_REPLY_SENT, args).sendToTarget(); + } } private final class MyHandler extends Handler { @@ -297,6 +325,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS public static final int MSG_ON_NOTIFICATIONS_SEEN = 3; public static final int MSG_ON_NOTIFICATION_EXPANSION_CHANGED = 4; public static final int MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT = 5; + public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6; public MyHandler(Looper looper) { super(looper, null, false); @@ -357,6 +386,15 @@ public abstract class NotificationAssistantService extends NotificationListenerS onNotificationDirectReply(key); break; } + case MSG_ON_SUGGESTED_REPLY_SENT: { + SomeArgs args = (SomeArgs) msg.obj; + String key = (String) args.arg1; + CharSequence reply = (CharSequence) args.arg2; + int source = args.argi2; + args.recycle(); + onSuggestedReplySent(key, reply, source); + break; + } } } } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index a4db4517bd9a..756a7c6a54b0 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1377,6 +1377,11 @@ public abstract class NotificationListenerService extends Service { } @Override + public void onSuggestedReplySent(String key, CharSequence reply, int source) { + // no-op in the listener + } + + @Override public void onNotificationChannelModification(String pkgName, UserHandle user, NotificationChannel channel, @ChannelOrGroupModificationTypes int modificationType) { diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 9a7094accfae..69ba07090284 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -66,7 +66,7 @@ interface IStatusBarService void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded); void onNotificationDirectReplied(String key); void onNotificationSmartRepliesAdded(in String key, in int replyCount); - void onNotificationSmartReplySent(in String key, in int replyIndex); + void onNotificationSmartReplySent(in String key, in int replyIndex, in CharSequence reply, boolean generatedByAssistant); void onNotificationSettingsViewed(String key); void setSystemUiVisibility(int vis, int mask, String cause); diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java index 0e4900ab2d37..22d0e3b7bdc9 100644 --- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java +++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java @@ -370,6 +370,14 @@ public class Assistant extends NotificationAssistantService { } @Override + public void onSuggestedReplySent(String key, CharSequence reply, int source) { + if (DEBUG) { + Log.d(TAG, "onSuggestedReplySent() called with: key = [" + key + "], reply = [" + reply + + "], source = [" + source + "]"); + } + } + + @Override public void onListenerConnected() { if (DEBUG) Log.i(TAG, "CONNECTED"); try { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java index 758c33a5aabe..37bdc1ce7cb9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/SmartReplyController.java @@ -41,12 +41,16 @@ public class SmartReplyController { mCallback = callback; } - public void smartReplySent(NotificationData.Entry entry, int replyIndex, CharSequence reply) { + /** + * Notifies StatusBarService a smart reply is sent. + */ + public void smartReplySent(NotificationData.Entry entry, int replyIndex, CharSequence reply, + boolean generatedByAssistant) { mCallback.onSmartReplySent(entry, reply); mSendingKeys.add(entry.key); try { - mBarService.onNotificationSmartReplySent(entry.notification.getKey(), - replyIndex); + mBarService.onNotificationSmartReplySent( + entry.notification.getKey(), replyIndex, reply, generatedByAssistant); } catch (RemoteException e) { // Nothing to do, system going down } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 88edc0d8f17d..aa5ab71f8eb8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1290,10 +1290,10 @@ public class NotificationContentView extends FrameLayout { return; } - SmartRepliesAndActions smartRepliesAndActions = chooseSmartRepliesAndActions( - mSmartReplyConstants, entry); + SmartRepliesAndActions smartRepliesAndActions = + chooseSmartRepliesAndActions(mSmartReplyConstants, entry); - applyRemoteInput(entry, smartRepliesAndActions.freeformRemoteInputActionPair != null); + applyRemoteInput(entry, smartRepliesAndActions.hasFreeformRemoteInput); applySmartReplyView(smartRepliesAndActions, entry); } @@ -1320,62 +1320,47 @@ public class NotificationContentView extends FrameLayout { boolean appGeneratedSmartRepliesExist = enableAppGeneratedSmartReplies && remoteInputActionPair != null - && !ArrayUtils.isEmpty(remoteInputActionPair.first.getChoices()); + && !ArrayUtils.isEmpty(remoteInputActionPair.first.getChoices()) + && remoteInputActionPair.second.actionIntent != null; List<Notification.Action> appGeneratedSmartActions = notification.getContextualActions(); boolean appGeneratedSmartActionsExist = !appGeneratedSmartActions.isEmpty(); - List<Notification.Action> sysGeneratedSmartActions = - notification.getAllowSystemGeneratedContextualActions() - ? entry.systemGeneratedSmartActions : Collections.emptyList(); - + SmartReplyView.SmartReplies smartReplies = null; + SmartReplyView.SmartActions smartActions = null; if (appGeneratedSmartRepliesExist) { - return new SmartRepliesAndActions(remoteInputActionPair.first, - remoteInputActionPair.second.actionIntent, + smartReplies = new SmartReplyView.SmartReplies( remoteInputActionPair.first.getChoices(), - appGeneratedSmartActions, - freeformRemoteInputActionPair); - } else if (appGeneratedSmartActionsExist) { - return new SmartRepliesAndActions(null, null, null, appGeneratedSmartActions, - freeformRemoteInputActionPair); - } else if (!ArrayUtils.isEmpty(entry.smartReplies) - && freeformRemoteInputActionPair != null - && freeformRemoteInputActionPair.second.getAllowGeneratedReplies()) { - // App didn't generate anything, use NAS-generated replies and actions - return new SmartRepliesAndActions(freeformRemoteInputActionPair.first, - freeformRemoteInputActionPair.second.actionIntent, - entry.smartReplies, - sysGeneratedSmartActions, - freeformRemoteInputActionPair); - } - // App didn't generate anything, and there are no NAS-generated smart replies. - return new SmartRepliesAndActions(null, null, null, sysGeneratedSmartActions, - freeformRemoteInputActionPair); - } - - @VisibleForTesting - static class SmartRepliesAndActions { - public final RemoteInput remoteInputWithChoices; - public final PendingIntent pendingIntentForSmartReplies; - public final CharSequence[] smartReplies; - public final List<Notification.Action> smartActions; - public final Pair<RemoteInput, Notification.Action> freeformRemoteInputActionPair; - - SmartRepliesAndActions(RemoteInput remoteInput, PendingIntent pendingIntent, - CharSequence[] choices, List<Notification.Action> smartActions, - Pair<RemoteInput, Notification.Action> freeformRemoteInputActionPair) { - this.remoteInputWithChoices = remoteInput; - this.pendingIntentForSmartReplies = pendingIntent; - this.smartReplies = choices; - this.smartActions = smartActions; - this.freeformRemoteInputActionPair = freeformRemoteInputActionPair; - } - - boolean smartRepliesExist() { - return remoteInputWithChoices != null - && pendingIntentForSmartReplies != null - && !ArrayUtils.isEmpty(smartReplies); + remoteInputActionPair.first, + remoteInputActionPair.second.actionIntent, + false /* fromAssistant */); + } + if (appGeneratedSmartActionsExist) { + smartActions = new SmartReplyView.SmartActions(appGeneratedSmartActions, + false /* fromAssistant */); + } + // Apps didn't provide any smart replies / actions, use those from NAS (if any). + if (!appGeneratedSmartRepliesExist && !appGeneratedSmartActionsExist) { + boolean useGeneratedReplies = !ArrayUtils.isEmpty(entry.smartReplies) + && freeformRemoteInputActionPair != null + && freeformRemoteInputActionPair.second.getAllowGeneratedReplies() + && freeformRemoteInputActionPair.second.actionIntent != null; + if (useGeneratedReplies) { + smartReplies = new SmartReplyView.SmartReplies( + entry.smartReplies, + freeformRemoteInputActionPair.first, + freeformRemoteInputActionPair.second.actionIntent, + true /* fromAssistant */); + } + boolean useSmartActions = !ArrayUtils.isEmpty(entry.systemGeneratedSmartActions) + && notification.getAllowSystemGeneratedContextualActions(); + if (useSmartActions) { + smartActions = new SmartReplyView.SmartActions( + entry.systemGeneratedSmartActions, true /* fromAssistant */); + } } + return new SmartRepliesAndActions( + smartReplies, smartActions, freeformRemoteInputActionPair != null); } private void applyRemoteInput(NotificationData.Entry entry, boolean hasFreeformRemoteInput) { @@ -1482,12 +1467,9 @@ public class NotificationContentView extends FrameLayout { if (mExpandedChild != null) { mExpandedSmartReplyView = applySmartReplyView(mExpandedChild, smartRepliesAndActions, entry); - if (mExpandedSmartReplyView != null - && smartRepliesAndActions.remoteInputWithChoices != null - && smartRepliesAndActions.smartReplies != null - && smartRepliesAndActions.smartReplies.length > 0) { - mSmartReplyController.smartRepliesAdded(entry, - smartRepliesAndActions.smartReplies.length); + if (mExpandedSmartReplyView != null && smartRepliesAndActions.smartReplies != null) { + mSmartReplyController.smartRepliesAdded( + entry, smartRepliesAndActions.smartReplies.choices.length); } } } @@ -1501,8 +1483,8 @@ public class NotificationContentView extends FrameLayout { } LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate; // If there are no smart replies and no smart actions - early out. - if (!smartRepliesAndActions.smartRepliesExist() - && smartRepliesAndActions.smartActions.isEmpty()) { + if (smartRepliesAndActions.smartReplies == null + && smartRepliesAndActions.smartActions == null) { smartReplyContainer.setVisibility(View.GONE); return null; } @@ -1532,10 +1514,13 @@ public class NotificationContentView extends FrameLayout { } if (smartReplyView != null) { smartReplyView.resetSmartSuggestions(smartReplyContainer); - smartReplyView.addRepliesFromRemoteInput(smartRepliesAndActions.remoteInputWithChoices, - smartRepliesAndActions.pendingIntentForSmartReplies, mSmartReplyController, - entry, smartRepliesAndActions.smartReplies); - smartReplyView.addSmartActions(smartRepliesAndActions.smartActions); + if (smartRepliesAndActions.smartReplies != null) { + smartReplyView.addRepliesFromRemoteInput( + smartRepliesAndActions.smartReplies, mSmartReplyController, entry); + } + if (smartRepliesAndActions.smartActions != null) { + smartReplyView.addSmartActions(smartRepliesAndActions.smartActions); + } smartReplyContainer.setVisibility(View.VISIBLE); } return smartReplyView; @@ -1954,4 +1939,22 @@ public class NotificationContentView extends FrameLayout { } pw.println(); } + + @VisibleForTesting + static class SmartRepliesAndActions { + @Nullable + public final SmartReplyView.SmartReplies smartReplies; + @Nullable + public final SmartReplyView.SmartActions smartActions; + public final boolean hasFreeformRemoteInput; + + SmartRepliesAndActions( + @Nullable SmartReplyView.SmartReplies smartReplies, + @Nullable SmartReplyView.SmartActions smartActions, + boolean hasFreeformRemoteInput) { + this.smartReplies = smartReplies; + this.smartActions = smartActions; + this.hasFreeformRemoteInput = hasFreeformRemoteInput; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java index 018668377a50..88ff0780c974 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java @@ -1,6 +1,7 @@ package com.android.systemui.statusbar.policy; import android.annotation.ColorInt; +import android.annotation.NonNull; import android.app.Notification; import android.app.PendingIntent; import android.app.RemoteInput; @@ -189,15 +190,13 @@ public class SmartReplyView extends ViewGroup { * into the notification are shown. */ public void addRepliesFromRemoteInput( - RemoteInput remoteInput, PendingIntent pendingIntent, - SmartReplyController smartReplyController, NotificationData.Entry entry, - CharSequence[] choices) { - if (remoteInput != null && pendingIntent != null) { - if (choices != null) { - for (int i = 0; i < choices.length; ++i) { + SmartReplies smartReplies, + SmartReplyController smartReplyController, NotificationData.Entry entry) { + if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) { + if (smartReplies.choices != null) { + for (int i = 0; i < smartReplies.choices.length; ++i) { Button replyButton = inflateReplyButton( - getContext(), this, i, choices[i], remoteInput, pendingIntent, - smartReplyController, entry); + getContext(), this, i, smartReplies, smartReplyController, entry); addView(replyButton); } } @@ -209,10 +208,10 @@ public class SmartReplyView extends ViewGroup { * Add smart actions to be shown next to smart replies. Only the actions that fit into the * notification are shown. */ - public void addSmartActions(List<Notification.Action> smartActions) { - int numSmartActions = smartActions.size(); + public void addSmartActions(SmartActions smartActions) { + int numSmartActions = smartActions.actions.size(); for (int n = 0; n < numSmartActions; n++) { - Notification.Action action = smartActions.get(n); + Notification.Action action = smartActions.actions.get(n); if (action.actionIntent != null) { Button actionButton = inflateActionButton(getContext(), this, action); addView(actionButton); @@ -228,22 +227,25 @@ public class SmartReplyView extends ViewGroup { @VisibleForTesting Button inflateReplyButton(Context context, ViewGroup root, int replyIndex, - CharSequence choice, RemoteInput remoteInput, PendingIntent pendingIntent, - SmartReplyController smartReplyController, NotificationData.Entry entry) { + SmartReplies smartReplies, SmartReplyController smartReplyController, + NotificationData.Entry entry) { Button b = (Button) LayoutInflater.from(context).inflate( R.layout.smart_reply_button, root, false); + CharSequence choice = smartReplies.choices[replyIndex]; b.setText(choice); OnDismissAction action = () -> { - smartReplyController.smartReplySent(entry, replyIndex, b.getText()); + smartReplyController.smartReplySent( + entry, replyIndex, b.getText(), smartReplies.fromAssistant); Bundle results = new Bundle(); - results.putString(remoteInput.getResultKey(), choice.toString()); + results.putString(smartReplies.remoteInput.getResultKey(), choice.toString()); Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - RemoteInput.addResultsToIntent(new RemoteInput[]{remoteInput}, intent, results); + RemoteInput.addResultsToIntent(new RemoteInput[]{smartReplies.remoteInput}, intent, + results); RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE); entry.setHasSentReply(); try { - pendingIntent.send(context, 0, intent); + smartReplies.pendingIntent.send(context, 0, intent); } catch (PendingIntent.CanceledException e) { Log.w(TAG, "Unable to send smart reply", e); } @@ -741,4 +743,40 @@ public class SmartReplyView extends ViewGroup { return show; } } + + /** + * Data class for smart replies. + */ + public static class SmartReplies { + @NonNull + public final RemoteInput remoteInput; + @NonNull + public final PendingIntent pendingIntent; + @NonNull + public final CharSequence[] choices; + public final boolean fromAssistant; + + public SmartReplies(CharSequence[] choices, RemoteInput remoteInput, + PendingIntent pendingIntent, boolean fromAssistant) { + this.choices = choices; + this.remoteInput = remoteInput; + this.pendingIntent = pendingIntent; + this.fromAssistant = fromAssistant; + } + } + + + /** + * Data class for smart actions. + */ + public static class SmartActions { + @NonNull + public final List<Notification.Action> actions; + public final boolean fromAssistant; + + public SmartActions(List<Notification.Action> actions, boolean fromAssistant) { + this.actions = actions; + this.fromAssistant = fromAssistant; + } + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java index 1d977d8c5dad..76b992f42090 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java @@ -16,18 +16,15 @@ package com.android.systemui.statusbar; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; + import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.Notification; import android.os.RemoteException; import android.os.UserHandle; -import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.support.test.filters.SmallTest; import android.testing.AndroidTestingRunner; @@ -38,7 +35,6 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.notification.NotificationData; import com.android.systemui.statusbar.notification.NotificationEntryManager; -import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import org.junit.Before; import org.junit.Test; @@ -93,7 +89,7 @@ public class SmartReplyControllerTest extends SysuiTestCase { @Test public void testSendSmartReply_updatesRemoteInput() { - mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); + mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, false); // Sending smart reply should make calls to NotificationEntryManager // to update the notification with reply and spinner. @@ -103,11 +99,21 @@ public class SmartReplyControllerTest extends SysuiTestCase { @Test public void testSendSmartReply_logsToStatusBar() throws RemoteException { - mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); + mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, false); + + // Check we log the result to the status bar service. + verify(mIStatusBarService).onNotificationSmartReplySent(mSbn.getKey(), + TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, false); + } + + + @Test + public void testSendSmartReply_logsToStatusBar_generatedByAssistant() throws RemoteException { + mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, true); // Check we log the result to the status bar service. verify(mIStatusBarService).onNotificationSmartReplySent(mSbn.getKey(), - TEST_CHOICE_INDEX); + TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, true); } @Test @@ -121,14 +127,14 @@ public class SmartReplyControllerTest extends SysuiTestCase { @Test public void testSendSmartReply_reportsSending() { - mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); + mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, false); assertTrue(mSmartReplyController.isSendingSmartReply(mSbn.getKey())); } @Test public void testSendingSmartReply_afterRemove_shouldReturnFalse() { - mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT); + mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, false); mSmartReplyController.stopSending(mEntry); assertFalse(mSmartReplyController.isSendingSmartReply(mSbn.getKey())); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java index 5e137a7de65a..7fee0ee8c664 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java @@ -16,11 +16,10 @@ package com.android.systemui.statusbar.notification.row; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.is; +import static com.google.common.truth.Truth.assertThat; + +import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.Mockito.doNothing; @@ -170,8 +169,10 @@ public class NotificationContentViewTest extends SysuiTestCase { private void setupAppGeneratedReplies( CharSequence[] smartReplyTitles, Notification.Action freeFormRemoteInputAction) { + PendingIntent pendingIntent = + PendingIntent.getBroadcast(mContext, 0, new Intent(TEST_ACTION), 0); Notification.Action action = - new Notification.Action.Builder(null, "Test Action", null).build(); + new Notification.Action.Builder(null, "Test Action", pendingIntent).build(); when(mRemoteInput.getChoices()).thenReturn(smartReplyTitles); Pair<RemoteInput, Notification.Action> remoteInputActionPair = Pair.create(mRemoteInput, action); @@ -191,7 +192,7 @@ public class NotificationContentViewTest extends SysuiTestCase { NotificationContentView.SmartRepliesAndActions repliesAndActions = NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - assertFalse(repliesAndActions.smartRepliesExist()); + assertThat(repliesAndActions.smartReplies).isNull(); } @Test @@ -203,7 +204,9 @@ public class NotificationContentViewTest extends SysuiTestCase { NotificationContentView.SmartRepliesAndActions repliesAndActions = NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - assertThat(repliesAndActions.smartReplies, equalTo(smartReplies)); + assertThat(repliesAndActions.smartReplies.choices).isEqualTo(smartReplies); + assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse(); + assertThat(repliesAndActions.smartActions).isNull(); } @Test @@ -219,8 +222,10 @@ public class NotificationContentViewTest extends SysuiTestCase { NotificationContentView.SmartRepliesAndActions repliesAndActions = NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - assertThat(repliesAndActions.smartReplies, equalTo(smartReplies)); - assertThat(repliesAndActions.smartActions, equalTo(smartActions)); + assertThat(repliesAndActions.smartReplies.choices).isEqualTo(smartReplies); + assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse(); + assertThat(repliesAndActions.smartActions.actions).isEqualTo(smartActions); + assertThat(repliesAndActions.smartActions.fromAssistant).isFalse(); } @Test @@ -237,8 +242,9 @@ public class NotificationContentViewTest extends SysuiTestCase { NotificationContentView.SmartRepliesAndActions repliesAndActions = NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - assertThat(repliesAndActions.smartReplies, equalTo(mEntry.smartReplies)); - assertThat(repliesAndActions.smartActions, is(empty())); + assertThat(repliesAndActions.smartReplies.choices).isEqualTo(mEntry.smartReplies); + assertThat(repliesAndActions.smartReplies.fromAssistant).isTrue(); + assertThat(repliesAndActions.smartActions).isNull(); } @Test @@ -255,8 +261,8 @@ public class NotificationContentViewTest extends SysuiTestCase { NotificationContentView.SmartRepliesAndActions repliesAndActions = NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - assertThat(repliesAndActions.smartReplies, equalTo(null)); - assertThat(repliesAndActions.smartActions, is(empty())); + assertThat(repliesAndActions.smartReplies).isNull(); + assertThat(repliesAndActions.smartActions).isNull(); } @Test @@ -270,8 +276,10 @@ public class NotificationContentViewTest extends SysuiTestCase { NotificationContentView.SmartRepliesAndActions repliesAndActions = NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - assertThat(repliesAndActions.smartReplies, equalTo(null)); - assertThat(repliesAndActions.smartActions, equalTo(mEntry.systemGeneratedSmartActions)); + assertThat(repliesAndActions.smartReplies).isNull(); + assertThat(repliesAndActions.smartActions.actions) + .isEqualTo(mEntry.systemGeneratedSmartActions); + assertThat(repliesAndActions.smartActions.fromAssistant).isTrue(); } @Test @@ -296,8 +304,10 @@ public class NotificationContentViewTest extends SysuiTestCase { NotificationContentView.SmartRepliesAndActions repliesAndActions = NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - assertThat(repliesAndActions.smartReplies, equalTo(appGenSmartReplies)); - assertThat(repliesAndActions.smartActions, equalTo(appGenSmartActions)); + assertThat(repliesAndActions.smartReplies.choices).isEqualTo(appGenSmartReplies); + assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse(); + assertThat(repliesAndActions.smartActions.actions).isEqualTo(appGenSmartActions); + assertThat(repliesAndActions.smartActions.fromAssistant).isFalse(); } @Test @@ -313,8 +323,8 @@ public class NotificationContentViewTest extends SysuiTestCase { NotificationContentView.SmartRepliesAndActions repliesAndActions = NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - assertThat(repliesAndActions.smartReplies, equalTo(null)); - assertThat(repliesAndActions.smartActions, is(empty())); + assertThat(repliesAndActions.smartActions).isNull(); + assertThat(repliesAndActions.smartReplies).isNull(); } private Notification.Action.Builder createActionBuilder(String actionTitle) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java index 9e659c880b9f..b6e3fc172c69 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java @@ -181,7 +181,16 @@ public class SmartReplyViewTest extends SysuiTestCase { public void testSendSmartReply_controllerCalled() { setSmartReplies(TEST_CHOICES); mView.getChildAt(2).performClick(); - verify(mLogger).smartReplySent(mEntry, 2, TEST_CHOICES[2]); + verify(mLogger).smartReplySent(mEntry, 2, TEST_CHOICES[2], + false /* generatedByAsssitant */); + } + + @Test + public void testSendSmartReply_controllerCalled_generatedByAssistant() { + setSmartReplies(TEST_CHOICES, true); + mView.getChildAt(2).performClick(); + verify(mLogger).smartReplySent(mEntry, 2, TEST_CHOICES[2], + true /* generatedByAsssitant */); } @Test @@ -392,11 +401,17 @@ public class SmartReplyViewTest extends SysuiTestCase { } private void setSmartReplies(CharSequence[] choices) { + setSmartReplies(choices, false); + } + + private void setSmartReplies(CharSequence[] choices, boolean fromAssistant) { PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(TEST_ACTION), 0); RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build(); + SmartReplyView.SmartReplies smartReplies = + new SmartReplyView.SmartReplies(choices, input, pendingIntent, fromAssistant); mView.resetSmartSuggestions(mContainer); - mView.addRepliesFromRemoteInput(input, pendingIntent, mLogger, mEntry, choices); + mView.addRepliesFromRemoteInput(smartReplies, mLogger, mEntry); } private Notification.Action createAction(String actionTitle) { @@ -415,12 +430,12 @@ public class SmartReplyViewTest extends SysuiTestCase { private void setSmartActions(String[] actionTitles) { mView.resetSmartSuggestions(mContainer); - mView.addSmartActions(createActions(actionTitles)); + mView.addSmartActions(new SmartReplyView.SmartActions(createActions(actionTitles), false)); } private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) { setSmartReplies(choices); - mView.addSmartActions(createActions(actionTitles)); + mView.addSmartActions(new SmartReplyView.SmartActions(createActions(actionTitles), false)); } private ViewGroup buildExpectedView(CharSequence[] choices, int lineCount) { @@ -453,9 +468,11 @@ public class SmartReplyViewTest extends SysuiTestCase { // Add smart replies Button previous = null; + SmartReplyView.SmartReplies smartReplies = + new SmartReplyView.SmartReplies(choices, null, null, false); for (int i = 0; i < choices.length; ++i) { - Button current = mView.inflateReplyButton(mContext, mView, i, choices[i], - null, null, null, null); + Button current = mView.inflateReplyButton(mContext, mView, i, smartReplies, + null, null); current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal, current.getPaddingBottom()); if (previous != null) { diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index decdac6cbfce..9222740e0506 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -45,5 +45,15 @@ public interface NotificationDelegate { void onNotificationDirectReplied(String key); void onNotificationSettingsViewed(String key); void onNotificationSmartRepliesAdded(String key, int replyCount); - void onNotificationSmartReplySent(String key, int replyIndex); + + /** + * Notifies a smart reply is sent. + * + * @param key the notification key + * @param clickedIndex the index of clicked reply + * @param reply the reply that is sent + * @param generatedByAssistant specifies is the reply generated by NAS + */ + void onNotificationSmartReplySent(String key, int clickedIndex, CharSequence reply, + boolean generatedByAssistant); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 2048d5fc9890..60058724e9a7 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -920,7 +920,8 @@ public class NotificationManagerService extends SystemService { } @Override - public void onNotificationSmartReplySent(String key, int replyIndex) { + public void onNotificationSmartReplySent(String key, int replyIndex, CharSequence reply, + boolean generatedByAssistant) { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { @@ -930,6 +931,8 @@ public class NotificationManagerService extends SystemService { mMetricsLogger.write(logMaker); // Treat clicking on a smart reply as a user interaction. reportUserInteraction(r); + mAssistants.notifyAssistantSuggestedReplySent( + r.sbn, reply, generatedByAssistant); } } } @@ -6899,6 +6902,27 @@ public class NotificationManagerService extends SystemService { }); } + @GuardedBy("mNotificationLock") + void notifyAssistantSuggestedReplySent( + final StatusBarNotification sbn, CharSequence reply, boolean generatedByAssistant) { + final String key = sbn.getKey(); + notifyAssistantLocked( + sbn, + false /* sameUserOnly */, + (assistant, sbnHolder) -> { + try { + assistant.onSuggestedReplySent( + key, + reply, + generatedByAssistant + ? NotificationAssistantService.SOURCE_FROM_ASSISTANT + : NotificationAssistantService.SOURCE_FROM_APP); + } catch (RemoteException ex) { + Log.e(TAG, "unable to notify assistant (snoozed): " + assistant, ex); + } + }); + } + /** * asynchronously notify the assistant that a notification has been snoozed until a diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 1eb44a058d61..361622fd2934 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1177,12 +1177,14 @@ public class StatusBarManagerService extends IStatusBarService.Stub { } @Override - public void onNotificationSmartReplySent(String key, int replyIndex) + public void onNotificationSmartReplySent( + String key, int replyIndex, CharSequence reply, boolean generatedByAssistant) throws RemoteException { enforceStatusBarService(); long identity = Binder.clearCallingIdentity(); try { - mNotificationDelegate.onNotificationSmartReplySent(key, replyIndex); + mNotificationDelegate.onNotificationSmartReplySent(key, replyIndex, reply, + generatedByAssistant); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index f11492aa59e5..fcd29e13a07b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -3696,4 +3696,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { new TestableToastCallback(), 2000, 0); assertEquals(1, mService.mToastQueue.size()); } + + @Test + public void testOnNotificationSmartReplySent() { + final int replyIndex = 2; + final String reply = "Hello"; + final boolean generatedByAssistant = true; + + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + mService.addNotification(r); + + mService.mNotificationDelegate.onNotificationSmartReplySent( + r.getKey(), replyIndex, reply, generatedByAssistant); + verify(mAssistants).notifyAssistantSuggestedReplySent( + eq(r.sbn), eq(reply), eq(generatedByAssistant)); + } } |