void onNotificationExpansionChanged(in String key, in boolean userAction, in boolean expanded, in int notificationLocation);
void onNotificationDirectReplied(String key);
void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount,
- boolean generatedByAsssistant);
- void onNotificationSmartReplySent(in String key, in int replyIndex, in CharSequence reply, boolean generatedByAssistant, in int notificationLocation);
+ boolean generatedByAsssistant, boolean editBeforeSending);
+ void onNotificationSmartReplySent(in String key, in int replyIndex, in CharSequence reply,
+ in int notificationLocation, boolean modifiedBeforeSending);
void onNotificationSettingsViewed(String key);
void setSystemUiVisibility(int displayId, int vis, int mask, String cause);
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.ShadeController;
return false;
}
- return activateRemoteInput(view, inputs, input, pendingIntent);
+ return activateRemoteInput(view, inputs, input, pendingIntent,
+ null /* editedSuggestionInfo */);
}
};
}
try {
mBarService.onNotificationDirectReplied(entry.notification.getKey());
+ if (entry.editedSuggestionInfo != null) {
+ boolean modifiedBeforeSending =
+ !TextUtils.equals(entry.remoteInputText,
+ entry.editedSuggestionInfo.originalText);
+ mBarService.onNotificationSmartReplySent(
+ entry.notification.getKey(),
+ entry.editedSuggestionInfo.index,
+ entry.editedSuggestionInfo.originalText,
+ NotificationLogger
+ .getNotificationLocation(entry)
+ .toMetricsEventEnum(),
+ modifiedBeforeSending);
+ }
} catch (RemoteException e) {
// Nothing to do, system going down
}
* @param inputs The remote inputs that need to be sent to the app.
* @param input The remote input that needs to be activated.
* @param pendingIntent The pending intent to be sent to the app.
+ * @param editedSuggestionInfo The smart reply that should be inserted in the remote input, or
+ * {@code null} if the user is not editing a smart reply.
* @return Whether the {@link RemoteInput} was activated.
*/
public boolean activateRemoteInput(View view, RemoteInput[] inputs, RemoteInput input,
- PendingIntent pendingIntent) {
+ PendingIntent pendingIntent, @Nullable EditedSuggestionInfo editedSuggestionInfo) {
ViewParent p = view.getParent();
RemoteInputView riv = null;
riv.setRevealParameters(cx, cy, r);
riv.setPendingIntent(pendingIntent);
- riv.setRemoteInput(inputs, input);
+ riv.setRemoteInput(inputs, input, editedSuggestionInfo);
riv.focusAnimated();
return true;
* Notifies StatusBarService a smart reply is sent.
*/
public void smartReplySent(NotificationEntry entry, int replyIndex, CharSequence reply,
- boolean generatedByAssistant, int notificationLocation) {
+ int notificationLocation, boolean modifiedBeforeSending) {
mCallback.onSmartReplySent(entry, reply);
mSendingKeys.add(entry.key);
try {
- mBarService.onNotificationSmartReplySent(
- entry.notification.getKey(), replyIndex, reply, generatedByAssistant,
- notificationLocation);
+ mBarService.onNotificationSmartReplySent(entry.notification.getKey(), replyIndex, reply,
+ notificationLocation, modifiedBeforeSending);
} catch (RemoteException e) {
// Nothing to do, system going down
}
* Smart Replies and Actions have been added to the UI.
*/
public void smartSuggestionsAdded(final NotificationEntry entry, int replyCount,
- int actionCount, boolean generatedByAssistant) {
+ int actionCount, boolean generatedByAssistant, boolean editBeforeSending) {
try {
- mBarService.onNotificationSmartSuggestionsAdded(
- entry.notification.getKey(), replyCount, actionCount, generatedByAssistant);
+ mBarService.onNotificationSmartSuggestionsAdded(entry.notification.getKey(), replyCount,
+ actionCount, generatedByAssistant, editBeforeSending);
} catch (RemoteException e) {
// Nothing to do, system going down
}
/** Smart replies provided by the NotificationAssistantService. */
@NonNull
public CharSequence[] systemGeneratedSmartReplies = new CharSequence[0];
+
+ /**
+ * If {@link android.app.RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
+ * currently editing a choice (smart reply), then this field contains the information about the
+ * suggestion being edited. Otherwise <code>null</code>.
+ */
+ public EditedSuggestionInfo editedSuggestionInfo;
+
@VisibleForTesting
public int suppressedVisualEffects;
public boolean suspended;
private static boolean isCategory(String category, Notification n) {
return Objects.equals(n.category, category);
}
+
+ /** Information about a suggestion that is being edited. */
+ public static class EditedSuggestionInfo {
+
+ /**
+ * The value of the suggestion (before any user edits).
+ */
+ public final CharSequence originalText;
+
+ /**
+ * The index of the suggestion that is being edited.
+ */
+ public final int index;
+
+ public EditedSuggestionInfo(CharSequence originalText, int index) {
+ this.originalText = originalText;
+ this.index = index;
+ }
+ }
}
boolean fromAssistant = smartRepliesAndActions.smartReplies == null
? smartRepliesAndActions.smartActions.fromAssistant
: smartRepliesAndActions.smartReplies.fromAssistant;
+ boolean editBeforeSending = smartRepliesAndActions.smartReplies != null
+ && mSmartReplyConstants.getEffectiveEditChoicesBeforeSending(
+ smartRepliesAndActions.smartReplies.remoteInput
+ .getEditChoicesBeforeSending());
+
mSmartReplyController.smartSuggestionsAdded(entry, numSmartReplies,
- numSmartActions, fromAssistant);
+ numSmartActions, fromAssistant, editBeforeSending);
}
}
}
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import com.android.systemui.R;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
mPendingIntent = pendingIntent;
}
- public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput) {
+ /**
+ * Sets the remote input for this view.
+ *
+ * @param remoteInputs The remote inputs that need to be sent to the app.
+ * @param remoteInput The remote input that needs to be activated.
+ * @param editedSuggestionInfo The smart reply that should be inserted in the remote input, or
+ * {@code null} if the user is not editing a smart reply.
+ */
+ public void setRemoteInput(RemoteInput[] remoteInputs, RemoteInput remoteInput,
+ @Nullable EditedSuggestionInfo editedSuggestionInfo) {
mRemoteInputs = remoteInputs;
mRemoteInput = remoteInput;
mEditText.setHint(mRemoteInput.getLabel());
+
+ mEntry.editedSuggestionInfo = editedSuggestionInfo;
+ if (editedSuggestionInfo != null) {
+ mEntry.remoteInputText = editedSuggestionInfo.originalText;
+ }
}
public void focusAnimated() {
public void stealFocusFrom(RemoteInputView other) {
other.close();
setPendingIntent(other.mPendingIntent);
- setRemoteInput(other.mRemoteInputs, other.mRemoteInput);
+ setRemoteInput(other.mRemoteInputs, other.mRemoteInput, mEntry.editedSuggestionInfo);
setRevealParameters(other.mRevealCx, other.mRevealCy, other.mRevealR);
focus();
}
continue;
}
setPendingIntent(a.actionIntent);
- setRemoteInput(inputs, input);
+ setRemoteInput(inputs, input, null /* editedSuggestionInfo*/);
return true;
}
return false;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
OnDismissAction action = () -> {
if (mConstants.getEffectiveEditChoicesBeforeSending(
smartReplies.remoteInput.getEditChoicesBeforeSending())) {
- entry.remoteInputText = choice;
+ EditedSuggestionInfo editedSuggestionInfo =
+ new EditedSuggestionInfo(choice, replyIndex);
mRemoteInputManager.activateRemoteInput(b,
new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput,
- smartReplies.pendingIntent);
+ smartReplies.pendingIntent, editedSuggestionInfo);
return false;
}
smartReplyController.smartReplySent(entry, replyIndex, b.getText(),
- smartReplies.fromAssistant,
- NotificationLogger.getNotificationLocation(entry).toMetricsEventEnum());
+ NotificationLogger.getNotificationLocation(entry).toMetricsEventEnum(),
+ false /* modifiedBeforeSending */);
Bundle results = new Bundle();
results.putString(smartReplies.remoteInput.getResultKey(), choice.toString());
Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@Test
public void testSendSmartReply_updatesRemoteInput() {
- mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, false,
- MetricsEvent.LOCATION_UNKNOWN);
+ mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT,
+ MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
// Sending smart reply should make calls to NotificationEntryManager
// to update the notification with reply and spinner.
@Test
public void testSendSmartReply_logsToStatusBar() throws RemoteException {
- mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, false,
- MetricsEvent.LOCATION_UNKNOWN);
+ mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT,
+ MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
// Check we log the result to the status bar service.
verify(mIStatusBarService).onNotificationSmartReplySent(mSbn.getKey(),
- TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, false, MetricsEvent.LOCATION_UNKNOWN);
+ TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, MetricsEvent.LOCATION_UNKNOWN, false);
}
@Test
- public void testSendSmartReply_logsToStatusBar_generatedByAssistant() throws RemoteException {
- mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, true,
- MetricsEvent.LOCATION_UNKNOWN);
+ public void testSendSmartReply_logsToStatusBar_modifiedBeforeSending() throws RemoteException {
+ mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT,
+ MetricsEvent.LOCATION_UNKNOWN, true /* modifiedBeforeSending */);
// Check we log the result to the status bar service.
verify(mIStatusBarService).onNotificationSmartReplySent(mSbn.getKey(),
- TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, true, MetricsEvent.LOCATION_UNKNOWN);
+ TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, MetricsEvent.LOCATION_UNKNOWN, true);
}
@Test
public void testShowSmartSuggestions_logsToStatusBar() throws RemoteException {
final boolean generatedByAsssistant = true;
+ final boolean editBeforeSending = true;
mSmartReplyController.smartSuggestionsAdded(mEntry, TEST_CHOICE_COUNT, TEST_ACTION_COUNT,
- generatedByAsssistant);
+ generatedByAsssistant, editBeforeSending);
// Check we log the result to the status bar service.
verify(mIStatusBarService).onNotificationSmartSuggestionsAdded(mSbn.getKey(),
- TEST_CHOICE_COUNT, TEST_ACTION_COUNT, generatedByAsssistant);
+ TEST_CHOICE_COUNT, TEST_ACTION_COUNT, generatedByAsssistant, editBeforeSending);
}
@Test
public void testSendSmartReply_reportsSending() {
- mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, false,
- MetricsEvent.LOCATION_UNKNOWN);
+ mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT,
+ MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
assertTrue(mSmartReplyController.isSendingSmartReply(mSbn.getKey()));
}
@Test
public void testSendingSmartReply_afterRemove_shouldReturnFalse() {
- mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT, false,
- MetricsEvent.LOCATION_UNKNOWN);
+ mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT,
+ MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
mSmartReplyController.stopSending(mEntry);
assertFalse(mSmartReplyController.isSendingSmartReply(mSbn.getKey()));
RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).build();
view.setPendingIntent(pendingIntent);
- view.setRemoteInput(new RemoteInput[]{input}, input);
+ view.setRemoteInput(new RemoteInput[]{input}, input, null /* editedSuggestionInfo */);
}
@Test
setSmartReplies(TEST_CHOICES);
mView.getChildAt(2).performClick();
verify(mLogger).smartReplySent(mEntry, 2, TEST_CHOICES[2],
- false /* generatedByAsssitant */, MetricsEvent.LOCATION_UNKNOWN);
- }
-
- @Test
- public void testSendSmartReply_controllerCalled_generatedByAssistant() {
- setSmartReplies(TEST_CHOICES, true);
- mView.getChildAt(2).performClick();
- verify(mLogger).smartReplySent(mEntry, 2, TEST_CHOICES[2],
- true /* generatedByAsssitant */, MetricsEvent.LOCATION_UNKNOWN);
+ MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
}
@Test
// OS: Q
ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = 1646;
+ // Tagged data for SMART_REPLY_VISIBLE and SMART_REPLY_ACTION.
+ // Whether the smart reply was / is to be sent via direct reply because
+ // getEditChoicesBeforeSending was enabled.
+ // OS: Q
+ NOTIFICATION_SMART_REPLY_EDIT_BEFORE_SENDING = 1647;
+
+ // Tagged data for SMART_REPLY_ACTION.
+ // Whether the smart reply was modified by the user via the direct reply field (implies that
+ // getEditChoicesBeforeSending was enabled).
+ // actions/replies.
+ // OS: Q
+ NOTIFICATION_SMART_REPLY_MODIFIED_BEFORE_SENDING = 1648;
+
// ---- End Q Constants, all Q constants go above this line ----
// Add new aosp constants above this line.
// END OF AOSP CONSTANTS
* Notifies that smart replies and actions have been added to the UI.
*/
void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount, int smartActionCount,
- boolean generatedByAssistant);
+ boolean generatedByAssistant, boolean editBeforeSending);
/**
* 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
* @param notificationLocation the location of the notification containing the smart reply
+ * @param modifiedBeforeSending whether the user changed the smart reply before sending
*/
void onNotificationSmartReplySent(String key, int clickedIndex, CharSequence reply,
- boolean generatedByAssistant, int notificationLocation);
+ int notificationLocation, boolean modifiedBeforeSending);
}
@Override
public void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount,
- int smartActionCount, boolean generatedByAssistant) {
+ int smartActionCount, boolean generatedByAssistant, boolean editBeforeSending) {
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
r.setNumSmartRepliesAdded(smartReplyCount);
r.setNumSmartActionsAdded(smartActionCount);
r.setSuggestionsGeneratedByAssistant(generatedByAssistant);
+ r.setEditChoicesBeforeSending(editBeforeSending);
}
}
}
@Override
public void onNotificationSmartReplySent(String key, int replyIndex, CharSequence reply,
- boolean generatedByAssistant, int notificationLocation) {
+ int notificationLocation, boolean modifiedBeforeSending) {
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
.setSubtype(replyIndex)
.addTaggedData(
MetricsEvent.NOTIFICATION_SMART_SUGGESTION_ASSISTANT_GENERATED,
- generatedByAssistant ? 1 : 0)
+ r.getSuggestionsGeneratedByAssistant() ? 1 : 0)
.addTaggedData(MetricsEvent.NOTIFICATION_LOCATION,
- notificationLocation);
+ notificationLocation)
+ .addTaggedData(
+ MetricsEvent.NOTIFICATION_SMART_REPLY_EDIT_BEFORE_SENDING,
+ r.getEditChoicesBeforeSending() ? 1 : 0)
+ .addTaggedData(
+ MetricsEvent.NOTIFICATION_SMART_REPLY_MODIFIED_BEFORE_SENDING,
+ modifiedBeforeSending ? 1 : 0);
mMetricsLogger.write(logMaker);
// Treat clicking on a smart reply as a user interaction.
reportUserInteraction(r);
mAssistants.notifyAssistantSuggestedReplySent(
- r.sbn, reply, generatedByAssistant);
+ r.sbn, reply, r.getSuggestionsGeneratedByAssistant());
}
}
}
r.getSuggestionsGeneratedByAssistant() ? 1 : 0)
// The fields in the NotificationVisibility.NotificationLocation enum map
// directly to the fields in the MetricsEvent.NotificationLocation enum.
- .addTaggedData(MetricsEvent.NOTIFICATION_LOCATION, notificationLocation);
+ .addTaggedData(MetricsEvent.NOTIFICATION_LOCATION, notificationLocation)
+ .addTaggedData(
+ MetricsEvent.NOTIFICATION_SMART_REPLY_EDIT_BEFORE_SENDING,
+ r.getEditChoicesBeforeSending() ? 1 : 0);
mMetricsLogger.write(logMaker);
}
}
private int mNumberOfSmartRepliesAdded;
private int mNumberOfSmartActionsAdded;
private boolean mSuggestionsGeneratedByAssistant;
+ private boolean mEditChoicesBeforeSending;
private boolean mHasSeenSmartReplies;
/**
* Whether this notification (and its channels) should be considered user locked. Used in
return mSuggestionsGeneratedByAssistant;
}
+ public boolean getEditChoicesBeforeSending() {
+ return mEditChoicesBeforeSending;
+ }
+
+ public void setEditChoicesBeforeSending(boolean editChoicesBeforeSending) {
+ mEditChoicesBeforeSending = editChoicesBeforeSending;
+ }
+
public boolean hasSeenSmartReplies() {
return mHasSeenSmartReplies;
}
@Override
public void onNotificationSmartSuggestionsAdded(String key, int smartReplyCount,
- int smartActionCount, boolean generatedByAssistant) {
+ int smartActionCount, boolean generatedByAssistant, boolean editBeforeSending) {
enforceStatusBarService();
long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationSmartSuggestionsAdded(key, smartReplyCount,
- smartActionCount, generatedByAssistant);
+ smartActionCount, generatedByAssistant, editBeforeSending);
} finally {
Binder.restoreCallingIdentity(identity);
}
@Override
public void onNotificationSmartReplySent(
- String key, int replyIndex, CharSequence reply, boolean generatedByAssistant,
- int notificationLocation) throws RemoteException {
+ String key, int replyIndex, CharSequence reply, int notificationLocation,
+ boolean modifiedBeforeSending) throws RemoteException {
enforceStatusBarService();
long identity = Binder.clearCallingIdentity();
try {
mNotificationDelegate.onNotificationSmartReplySent(key, replyIndex, reply,
- generatedByAssistant, notificationLocation);
+ notificationLocation, modifiedBeforeSending);
} finally {
Binder.restoreCallingIdentity(identity);
}
public void testOnNotificationSmartReplySent() {
final int replyIndex = 2;
final String reply = "Hello";
+ final boolean modifiedBeforeSending = true;
final boolean generatedByAssistant = true;
NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+ r.setSuggestionsGeneratedByAssistant(generatedByAssistant);
mService.addNotification(r);
mService.mNotificationDelegate.onNotificationSmartReplySent(
- r.getKey(), replyIndex, reply, generatedByAssistant, NOTIFICATION_LOCATION_UNKNOWN);
+ r.getKey(), replyIndex, reply, NOTIFICATION_LOCATION_UNKNOWN,
+ modifiedBeforeSending);
verify(mAssistants).notifyAssistantSuggestedReplySent(
eq(r.sbn), eq(reply), eq(generatedByAssistant));
}