Merge "Add onSuggestedReplySent in NotificationAssistantService"
This commit is contained in:
committed by
Android (Google) Code Review
commit
20dbbf22b8
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -174,6 +185,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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1376,6 +1376,11 @@ public abstract class NotificationListenerService extends Service {
|
||||
// no-op in the listener
|
||||
}
|
||||
|
||||
@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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -369,6 +369,14 @@ public class Assistant extends NotificationAssistantService {
|
||||
if (DEBUG) Log.i(TAG, "onNotificationDirectReply " + key);
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
remoteInputActionPair.first,
|
||||
remoteInputActionPair.second.actionIntent,
|
||||
false /* fromAssistant */);
|
||||
}
|
||||
// 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;
|
||||
if (appGeneratedSmartActionsExist) {
|
||||
smartActions = new SmartReplyView.SmartActions(appGeneratedSmartActions,
|
||||
false /* fromAssistant */);
|
||||
}
|
||||
|
||||
boolean smartRepliesExist() {
|
||||
return remoteInputWithChoices != null
|
||||
&& pendingIntentForSmartReplies != null
|
||||
&& !ArrayUtils.isEmpty(smartReplies);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_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_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()));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user