summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java6
-rw-r--r--packages/SystemUI/res/values/config.xml4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java60
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java69
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java129
12 files changed, 352 insertions, 46 deletions
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 495a5fbb6665..6d0a8646b3a7 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -82,6 +82,12 @@ public final class SystemUiDeviceConfigFlags {
public static final String SSIN_MAX_NUM_ACTIONS = "ssin_max_num_actions";
/**
+ * (int) The amount of time (ms) before smart suggestions are clickable, since the suggestions
+ * were added.
+ */
+ public static final String SSIN_ONCLICK_INIT_DELAY = "ssin_onclick_init_delay";
+
+ /**
* The default component of
* {@link android.service.notification.NotificationAssistantService}.
*/
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d6906f3988cd..73386879a20d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -469,6 +469,10 @@
-->
<integer name="config_smart_replies_in_notifications_max_num_actions">-1</integer>
+ <!-- Smart replies in notifications: Delay (ms) before smart suggestions are clickable, since
+ they were added. -->
+ <integer name="config_smart_replies_in_notifications_onclick_init_delay">200</integer>
+
<!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents.
Blank sends the user to the Chooser first.
This name is in the ComponentName flattened format (package/class) -->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index efdcd053bc54..6b2efaab0a64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -100,6 +100,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -3194,6 +3195,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mAmbientGoingAway = goingAway;
}
+ /**
+ * Returns the Smart Suggestions backing the smart suggestion buttons in the notification.
+ */
+ public SmartRepliesAndActions getExistingSmartRepliesAndActions() {
+ return mPrivateLayout.getCurrentSmartRepliesAndActions();
+ }
+
@VisibleForTesting
protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
mChildrenContainer = childrenContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 18dcf4c55d21..396cd73f9a22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -44,6 +44,7 @@ import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewW
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
+import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.util.Assert;
@@ -282,7 +283,8 @@ public class NotificationContentInflater {
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
mRedactAmbient, packageContext);
result = inflateSmartReplyViews(result, reInflateFlags, mRow.getEntry(),
- mRow.getContext(), mRow.getHeadsUpManager());
+ mRow.getContext(), mRow.getHeadsUpManager(),
+ mRow.getExistingSmartRepliesAndActions());
apply(
inflateSynchronously,
result,
@@ -344,20 +346,20 @@ public class NotificationContentInflater {
private static InflationProgress inflateSmartReplyViews(InflationProgress result,
@InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
- HeadsUpManager headsUpManager) {
+ HeadsUpManager headsUpManager, SmartRepliesAndActions previousSmartRepliesAndActions) {
SmartReplyConstants smartReplyConstants = Dependency.get(SmartReplyConstants.class);
SmartReplyController smartReplyController = Dependency.get(SmartReplyController.class);
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
result.expandedInflatedSmartReplies =
InflatedSmartReplies.inflate(
context, entry, smartReplyConstants, smartReplyController,
- headsUpManager);
+ headsUpManager, previousSmartRepliesAndActions);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 && result.newHeadsUpView != null) {
result.headsUpInflatedSmartReplies =
InflatedSmartReplies.inflate(
context, entry, smartReplyConstants, smartReplyController,
- headsUpManager);
+ headsUpManager, previousSmartRepliesAndActions);
}
return result;
}
@@ -905,7 +907,8 @@ public class NotificationContentInflater {
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
mRedactAmbient, packageContext);
return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
- mRow.getContext(), mRow.getHeadsUpManager());
+ mRow.getContext(), mRow.getHeadsUpManager(),
+ mRow.getExistingSmartRepliesAndActions());
} catch (Exception e) {
mError = e;
return null;
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 78500357f41f..b81d81438ea3 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
@@ -93,6 +93,7 @@ public class NotificationContentView extends FrameLayout {
private SmartReplyController mSmartReplyController;
private InflatedSmartReplies mExpandedInflatedSmartReplies;
private InflatedSmartReplies mHeadsUpInflatedSmartReplies;
+ private SmartRepliesAndActions mCurrentSmartRepliesAndActions;
private NotificationViewWrapper mContractedWrapper;
private NotificationViewWrapper mExpandedWrapper;
@@ -1259,18 +1260,18 @@ public class NotificationContentView extends FrameLayout {
// the same SmartRepliesAndActions to avoid discrepancies between the two views. We here
// reuse that object for our local SmartRepliesAndActions to avoid discrepancies between
// this class and the InflatedSmartReplies classes.
- SmartRepliesAndActions smartRepliesAndActions = mExpandedInflatedSmartReplies != null
+ mCurrentSmartRepliesAndActions = mExpandedInflatedSmartReplies != null
? mExpandedInflatedSmartReplies.getSmartRepliesAndActions()
: mHeadsUpInflatedSmartReplies.getSmartRepliesAndActions();
if (DEBUG) {
Log.d(TAG, String.format("Adding suggestions for %s, %d actions, and %d replies.",
entry.notification.getKey(),
- smartRepliesAndActions.smartActions == null ? 0 :
- smartRepliesAndActions.smartActions.actions.size(),
- smartRepliesAndActions.smartReplies == null ? 0 :
- smartRepliesAndActions.smartReplies.choices.length));
+ mCurrentSmartRepliesAndActions.smartActions == null ? 0 :
+ mCurrentSmartRepliesAndActions.smartActions.actions.size(),
+ mCurrentSmartRepliesAndActions.smartReplies == null ? 0 :
+ mCurrentSmartRepliesAndActions.smartReplies.choices.length));
}
- applySmartReplyView(smartRepliesAndActions, entry);
+ applySmartReplyView(mCurrentSmartRepliesAndActions, entry);
}
private void applyRemoteInput(NotificationEntry entry, boolean hasFreeformRemoteInput) {
@@ -1472,6 +1473,13 @@ public class NotificationContentView extends FrameLayout {
}
}
+ /**
+ * Returns the smart replies and actions currently shown in the notification.
+ */
+ @Nullable public SmartRepliesAndActions getCurrentSmartRepliesAndActions() {
+ return mCurrentSmartRepliesAndActions;
+ }
+
public void closeRemoteInput() {
if (mHeadsUpRemoteInput != null) {
mHeadsUpRemoteInput.close();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
index 5b2e398b66e1..ee78a723a49c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
@@ -28,15 +28,19 @@ import android.util.Log;
import android.util.Pair;
import android.widget.Button;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.systemui.Dependency;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
+import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -79,29 +83,52 @@ public class InflatedSmartReplies {
NotificationEntry entry,
SmartReplyConstants smartReplyConstants,
SmartReplyController smartReplyController,
- HeadsUpManager headsUpManager) {
- SmartRepliesAndActions smartRepliesAndActions =
+ HeadsUpManager headsUpManager,
+ SmartRepliesAndActions existingSmartRepliesAndActions) {
+ SmartRepliesAndActions newSmartRepliesAndActions =
chooseSmartRepliesAndActions(smartReplyConstants, entry);
- if (!shouldShowSmartReplyView(entry, smartRepliesAndActions)) {
+ if (!shouldShowSmartReplyView(entry, newSmartRepliesAndActions)) {
return new InflatedSmartReplies(null /* smartReplyView */,
- null /* smartSuggestionButtons */, smartRepliesAndActions);
+ null /* smartSuggestionButtons */, newSmartRepliesAndActions);
}
+ // Only block clicks if the smart buttons are different from the previous set - to avoid
+ // scenarios where a user incorrectly cannot click smart buttons because the notification is
+ // updated.
+ boolean delayOnClickListener =
+ !areSuggestionsSimilar(existingSmartRepliesAndActions, newSmartRepliesAndActions);
+
SmartReplyView smartReplyView = SmartReplyView.inflate(context);
List<Button> suggestionButtons = new ArrayList<>();
- if (smartRepliesAndActions.smartReplies != null) {
+ if (newSmartRepliesAndActions.smartReplies != null) {
suggestionButtons.addAll(smartReplyView.inflateRepliesFromRemoteInput(
- smartRepliesAndActions.smartReplies, smartReplyController, entry));
+ newSmartRepliesAndActions.smartReplies, smartReplyController, entry,
+ delayOnClickListener));
}
- if (smartRepliesAndActions.smartActions != null) {
+ if (newSmartRepliesAndActions.smartActions != null) {
suggestionButtons.addAll(
- smartReplyView.inflateSmartActions(smartRepliesAndActions.smartActions,
- smartReplyController, entry, headsUpManager));
+ smartReplyView.inflateSmartActions(newSmartRepliesAndActions.smartActions,
+ smartReplyController, entry, headsUpManager,
+ delayOnClickListener));
}
return new InflatedSmartReplies(smartReplyView, suggestionButtons,
- smartRepliesAndActions);
+ newSmartRepliesAndActions);
+ }
+
+ @VisibleForTesting
+ static boolean areSuggestionsSimilar(
+ SmartRepliesAndActions left, SmartRepliesAndActions right) {
+ if (left == right) return true;
+ if (left == null || right == null) return false;
+
+ if (!Arrays.equals(left.getSmartReplies(), right.getSmartReplies())) {
+ return false;
+ }
+
+ return !NotificationUiAdjustment.areDifferent(
+ left.getSmartActions(), right.getSmartActions());
}
/**
@@ -260,5 +287,13 @@ public class InflatedSmartReplies {
this.smartReplies = smartReplies;
this.smartActions = smartActions;
}
+
+ @NonNull public CharSequence[] getSmartReplies() {
+ return smartReplies == null ? new CharSequence[0] : smartReplies.choices;
+ }
+
+ @NonNull public List<Notification.Action> getSmartActions() {
+ return smartActions == null ? Collections.emptyList() : smartActions.actions;
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
index e57cff789a23..f02b5441a737 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyConstants.java
@@ -47,6 +47,7 @@ public final class SmartReplyConstants {
private final boolean mDefaultShowInHeadsUp;
private final int mDefaultMinNumSystemGeneratedReplies;
private final int mDefaultMaxNumActions;
+ private final int mDefaultOnClickInitDelay;
// These fields are updated on the UI thread but can be accessed on both the UI thread and
// background threads. We use the volatile keyword here instead of synchronization blocks since
@@ -59,6 +60,7 @@ public final class SmartReplyConstants {
private volatile boolean mShowInHeadsUp;
private volatile int mMinNumSystemGeneratedReplies;
private volatile int mMaxNumActions;
+ private volatile long mOnClickInitDelay;
private final Handler mHandler;
private final Context mContext;
@@ -83,6 +85,8 @@ public final class SmartReplyConstants {
R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies);
mDefaultMaxNumActions = resources.getInteger(
R.integer.config_smart_replies_in_notifications_max_num_actions);
+ mDefaultOnClickInitDelay = resources.getInteger(
+ R.integer.config_smart_replies_in_notifications_onclick_init_delay);
registerDeviceConfigListener();
updateConstants();
@@ -136,6 +140,10 @@ public final class SmartReplyConstants {
DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS,
mDefaultMaxNumActions);
+ mOnClickInitDelay = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.SSIN_ONCLICK_INIT_DELAY,
+ mDefaultOnClickInitDelay);
}
}
@@ -218,4 +226,12 @@ public final class SmartReplyConstants {
public int getMaxNumActions() {
return mMaxNumActions;
}
+
+ /**
+ * Returns the amount of time (ms) before smart suggestions are clickable, since the suggestions
+ * were added.
+ */
+ public long getOnClickInitDelay() {
+ return mOnClickInitDelay;
+ }
}
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 ed5487f74356..0f7a0f09b2e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -16,6 +16,7 @@ import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.RippleDrawable;
import android.os.Bundle;
+import android.os.SystemClock;
import android.text.Layout;
import android.text.TextPaint;
import android.text.method.TransformationMethod;
@@ -213,14 +214,16 @@ public class SmartReplyView extends ViewGroup {
*/
public List<Button> inflateRepliesFromRemoteInput(
@NonNull SmartReplies smartReplies,
- SmartReplyController smartReplyController, NotificationEntry entry) {
+ SmartReplyController smartReplyController, NotificationEntry entry,
+ boolean delayOnClickListener) {
List<Button> buttons = new ArrayList<>();
if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) {
if (smartReplies.choices != null) {
for (int i = 0; i < smartReplies.choices.length; ++i) {
buttons.add(inflateReplyButton(
- this, getContext(), i, smartReplies, smartReplyController, entry));
+ this, getContext(), i, smartReplies, smartReplyController, entry,
+ delayOnClickListener));
}
this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant;
}
@@ -234,7 +237,7 @@ public class SmartReplyView extends ViewGroup {
*/
public List<Button> inflateSmartActions(@NonNull SmartActions smartActions,
SmartReplyController smartReplyController, NotificationEntry entry,
- HeadsUpManager headsUpManager) {
+ HeadsUpManager headsUpManager, boolean delayOnClickListener) {
List<Button> buttons = new ArrayList<>();
int numSmartActions = smartActions.actions.size();
for (int n = 0; n < numSmartActions; n++) {
@@ -242,7 +245,7 @@ public class SmartReplyView extends ViewGroup {
if (action.actionIntent != null) {
buttons.add(inflateActionButton(
this, getContext(), n, smartActions, smartReplyController, entry,
- headsUpManager));
+ headsUpManager, delayOnClickListener));
}
}
return buttons;
@@ -259,7 +262,7 @@ public class SmartReplyView extends ViewGroup {
@VisibleForTesting
static Button inflateReplyButton(SmartReplyView smartReplyView, Context context,
int replyIndex, SmartReplies smartReplies, SmartReplyController smartReplyController,
- NotificationEntry entry) {
+ NotificationEntry entry, boolean useDelayedOnClickListener) {
Button b = (Button) LayoutInflater.from(context).inflate(
R.layout.smart_reply_button, smartReplyView, false);
CharSequence choice = smartReplies.choices[replyIndex];
@@ -299,9 +302,13 @@ public class SmartReplyView extends ViewGroup {
return false; // do not defer
};
- b.setOnClickListener(view -> {
+ OnClickListener onClickListener = view ->
smartReplyView.mKeyguardDismissUtil.executeWhenUnlocked(action);
- });
+ if (useDelayedOnClickListener) {
+ onClickListener = new DelayedOnClickListener(onClickListener,
+ smartReplyView.mConstants.getOnClickInitDelay());
+ }
+ b.setOnClickListener(onClickListener);
b.setAccessibilityDelegate(new AccessibilityDelegate() {
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
@@ -322,7 +329,7 @@ public class SmartReplyView extends ViewGroup {
static Button inflateActionButton(SmartReplyView smartReplyView, Context context,
int actionIndex, SmartActions smartActions,
SmartReplyController smartReplyController, NotificationEntry entry,
- HeadsUpManager headsUpManager) {
+ HeadsUpManager headsUpManager, boolean useDelayedOnClickListener) {
Notification.Action action = smartActions.actions.get(actionIndex);
Button button = (Button) LayoutInflater.from(context).inflate(
R.layout.smart_action_button, smartReplyView, false);
@@ -335,14 +342,19 @@ public class SmartReplyView extends ViewGroup {
iconDrawable.setBounds(0, 0, newIconSize, newIconSize);
button.setCompoundDrawables(iconDrawable, null, null, null);
- button.setOnClickListener(view ->
+ OnClickListener onClickListener = view ->
smartReplyView.getActivityStarter().startPendingIntentDismissingKeyguard(
action.actionIntent,
() -> {
smartReplyController.smartActionClicked(
entry, actionIndex, action, smartActions.fromAssistant);
headsUpManager.removeNotification(entry.key, true);
- }));
+ });
+ if (useDelayedOnClickListener) {
+ onClickListener = new DelayedOnClickListener(onClickListener,
+ smartReplyView.mConstants.getOnClickInitDelay());
+ }
+ button.setOnClickListener(onClickListener);
// Mark this as an Action button
final LayoutParams lp = (LayoutParams) button.getLayoutParams();
@@ -958,4 +970,32 @@ public class SmartReplyView extends ViewGroup {
this.fromAssistant = fromAssistant;
}
}
+
+ /**
+ * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of
+ * time.
+ */
+ private static class DelayedOnClickListener implements OnClickListener {
+ private final OnClickListener mActualListener;
+ private final long mInitDelayMs;
+ private final long mInitTimeMs;
+
+ DelayedOnClickListener(OnClickListener actualOnClickListener, long initDelayMs) {
+ mActualListener = actualOnClickListener;
+ mInitDelayMs = initDelayMs;
+ mInitTimeMs = SystemClock.elapsedRealtime();
+ }
+
+ public void onClick(View v) {
+ if (hasFinishedInitialization()) {
+ mActualListener.onClick(v);
+ } else {
+ Log.i(TAG, "Accidental Smart Suggestion click registered, delay: " + mInitDelayMs);
+ }
+ }
+
+ private boolean hasFinishedInitialization() {
+ return SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs;
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
index 76a3c95cad0a..7c4629871658 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BlockingQueueIntentReceiver.java
@@ -34,4 +34,8 @@ public class BlockingQueueIntentReceiver extends BroadcastReceiver {
public Intent waitForIntent() throws InterruptedException {
return mQueue.poll(10, TimeUnit.SECONDS);
}
+
+ public Intent waitForIntentShortDelay() throws InterruptedException {
+ return mQueue.poll(3, TimeUnit.SECONDS);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
index de1072d450f7..2462fb3ca589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
@@ -43,6 +43,8 @@ import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
+import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions;
+import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies;
import org.junit.Before;
import org.junit.Test;
@@ -51,6 +53,7 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
@SmallTest
@@ -322,6 +325,72 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
mEntry.systemGeneratedSmartActions);
}
+ @Test
+ public void areSuggestionsSimilar_trueForSimilar() {
+ CharSequence[] leftReplies = new CharSequence[] { "first reply", "second reply"};
+ CharSequence[] rightReplies = new CharSequence[] { "first reply", "second reply"};
+ List<Notification.Action> leftActions = Arrays.asList(
+ createAction("firstAction"),
+ createAction("secondAction"));
+ List<Notification.Action> rightActions = Arrays.asList(
+ createAction("firstAction"),
+ createAction("secondAction"));
+
+ SmartRepliesAndActions leftRepliesAndActions = new SmartRepliesAndActions(
+ new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
+ new SmartActions(leftActions, false /* fromAssistant */));
+ SmartRepliesAndActions rightRepliesAndActions = new SmartRepliesAndActions(
+ new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
+ new SmartActions(rightActions, false /* fromAssistant */));
+
+ assertThat(InflatedSmartReplies.areSuggestionsSimilar(
+ leftRepliesAndActions, rightRepliesAndActions)).isTrue();
+ }
+
+ @Test
+ public void areSuggestionsSimilar_falseForDifferentReplies() {
+ CharSequence[] leftReplies = new CharSequence[] { "first reply"};
+ CharSequence[] rightReplies = new CharSequence[] { "first reply", "second reply"};
+ List<Notification.Action> leftActions = Arrays.asList(
+ createAction("firstAction"),
+ createAction("secondAction"));
+ List<Notification.Action> rightActions = Arrays.asList(
+ createAction("firstAction"),
+ createAction("secondAction"));
+
+ SmartRepliesAndActions leftRepliesAndActions = new SmartRepliesAndActions(
+ new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
+ new SmartActions(leftActions, false /* fromAssistant */));
+ SmartRepliesAndActions rightRepliesAndActions = new SmartRepliesAndActions(
+ new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
+ new SmartActions(rightActions, false /* fromAssistant */));
+
+ assertThat(InflatedSmartReplies.areSuggestionsSimilar(
+ leftRepliesAndActions, rightRepliesAndActions)).isFalse();
+ }
+
+ @Test
+ public void areSuggestionsSimilar_falseForDifferentActions() {
+ CharSequence[] leftReplies = new CharSequence[] { "first reply", "second reply"};
+ CharSequence[] rightReplies = new CharSequence[] { "first reply", "second reply"};
+ List<Notification.Action> leftActions = Arrays.asList(
+ createAction("firstAction"),
+ createAction("secondAction"));
+ List<Notification.Action> rightActions = Arrays.asList(
+ createAction("firstAction"),
+ createAction("not secondAction"));
+
+ SmartRepliesAndActions leftRepliesAndActions = new SmartRepliesAndActions(
+ new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
+ new SmartActions(leftActions, false /* fromAssistant */));
+ SmartRepliesAndActions rightRepliesAndActions = new SmartRepliesAndActions(
+ new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
+ new SmartActions(rightActions, false /* fromAssistant */));
+
+ assertThat(InflatedSmartReplies.areSuggestionsSimilar(
+ leftRepliesAndActions, rightRepliesAndActions)).isFalse();
+ }
+
private void setupAppGeneratedReplies(CharSequence[] smartReplies) {
setupAppGeneratedReplies(smartReplies, true /* allowSystemGeneratedReplies */);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
index 3edfb56fbaac..fb2b7dced7c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyConstantsTest.java
@@ -211,6 +211,18 @@ public class SmartReplyConstantsTest extends SysuiTestCase {
assertEquals(10, mConstants.getMaxNumActions());
}
+ @Test
+ public void testOnClickInitDelayWithNoConfig() {
+ assertEquals(200, mConstants.getOnClickInitDelay());
+ }
+
+ @Test
+ public void testOnClickInitDelaySet() {
+ overrideSetting(SystemUiDeviceConfigFlags.SSIN_ONCLICK_INIT_DELAY, "50");
+ triggerConstantsOnChange();
+ assertEquals(50, mConstants.getOnClickInitDelay());
+ }
+
private void overrideSetting(String propertyName, String value) {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
propertyName, value, false /* makeDefault */);
@@ -239,5 +251,7 @@ public class SmartReplyConstantsTest extends SysuiTestCase {
false /* makeDefault */);
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS, null, false /* makeDefault */);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.SSIN_ONCLICK_INIT_DELAY, null, false /* makeDefault */);
}
}
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 0ce1df3d19f4..01f3c923832f 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
@@ -24,6 +24,7 @@ import static junit.framework.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -120,6 +121,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
when(mConstants.getMaxNumActions()).thenReturn(-1);
+ // Ensure there's no delay before we can click smart suggestions.
+ when(mConstants.getOnClickInitDelay()).thenReturn(0L);
final Resources res = mContext.getResources();
mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
@@ -164,7 +167,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
mView.getChildAt(2).performClick();
- assertNull(mReceiver.waitForIntent());
+ assertNull(mReceiver.waitForIntentShortDelay());
}
@Test
@@ -176,7 +179,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
mView.getChildAt(2).performClick();
// No intent until the screen is unlocked.
- assertNull(mReceiver.waitForIntent());
+ assertNull(mReceiver.waitForIntentShortDelay());
actionRef.get().onDismiss();
@@ -204,6 +207,48 @@ public class SmartReplyViewTest extends SysuiTestCase {
}
@Test
+ public void testTapSmartReply_beforeInitDelay_blocked() throws InterruptedException {
+ // 100 seconds is easily enough for our click to always be blocked.
+ when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
+ setSmartReplies(TEST_CHOICES);
+
+ mView.getChildAt(2).performClick();
+
+ assertNull(mReceiver.waitForIntentShortDelay());
+ }
+
+ @Test
+ public void testTapSmartReply_afterInitDelay_clickReceived() throws InterruptedException {
+ final long delayMs = 50L; // Using a small delay to not delay the test suite too much.
+ when(mConstants.getOnClickInitDelay()).thenReturn(delayMs);
+ setSmartReplies(TEST_CHOICES);
+
+ Thread.sleep(delayMs);
+ mView.getChildAt(2).performClick();
+
+ // Now the intent should arrive.
+ Intent resultIntent = mReceiver.waitForIntent();
+ assertEquals(TEST_CHOICES[2],
+ RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
+ assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
+ }
+
+ @Test
+ public void testTapSmartReply_withoutDelayedOnClickListener_bypassesDelay()
+ throws InterruptedException {
+ // 100 seconds is easily enough for our click to always be blocked.
+ when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
+ setSmartReplies(TEST_CHOICES, false /* useDelayedOnClickListener */);
+
+ mView.getChildAt(2).performClick();
+
+ Intent resultIntent = mReceiver.waitForIntent();
+ assertEquals(TEST_CHOICES[2],
+ RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
+ assertEquals(RemoteInput.SOURCE_CHOICE, RemoteInput.getResultsSource(resultIntent));
+ }
+
+ @Test
public void testMeasure_empty() {
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
assertEquals(500, mView.getMeasuredWidthAndState());
@@ -403,18 +448,25 @@ public class SmartReplyViewTest extends SysuiTestCase {
}
private void setSmartReplies(CharSequence[] choices) {
+ setSmartReplies(choices, true /* useDelayedOnClickListener */);
+ }
+
+ private void setSmartReplies(CharSequence[] choices, boolean useDelayedOnClickListener) {
mView.resetSmartSuggestions(mContainer);
- List<Button> replyButtons = inflateSmartReplies(choices, false /* fromAssistant */);
+ List<Button> replyButtons = inflateSmartReplies(choices, false /* fromAssistant */,
+ useDelayedOnClickListener);
mView.addPreInflatedButtons(replyButtons);
}
- private List<Button> inflateSmartReplies(CharSequence[] choices, boolean fromAssistant) {
+ private List<Button> inflateSmartReplies(CharSequence[] choices, boolean fromAssistant,
+ boolean useDelayedOnClickListener) {
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);
- return mView.inflateRepliesFromRemoteInput(smartReplies, mLogger, mEntry);
+ return mView.inflateRepliesFromRemoteInput(smartReplies, mLogger, mEntry,
+ useDelayedOnClickListener);
}
private Notification.Action createAction(String actionTitle) {
@@ -432,28 +484,37 @@ public class SmartReplyViewTest extends SysuiTestCase {
}
private void setSmartActions(String[] actionTitles) {
+ setSmartActions(actionTitles, true /* useDelayedOnClickListener */);
+ }
+
+ private void setSmartActions(String[] actionTitles, boolean useDelayedOnClickListener) {
mView.resetSmartSuggestions(mContainer);
List<Button> actions = mView.inflateSmartActions(
new SmartReplyView.SmartActions(createActions(actionTitles), false),
mLogger,
mEntry,
- mHeadsUpManager);
+ mHeadsUpManager,
+ useDelayedOnClickListener);
mView.addPreInflatedButtons(actions);
}
private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
- setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */);
+ setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */,
+ true /* useDelayedOnClickListener */);
}
private void setSmartRepliesAndActions(
- CharSequence[] choices, String[] actionTitles, boolean fromAssistant) {
+ CharSequence[] choices, String[] actionTitles, boolean fromAssistant,
+ boolean useDelayedOnClickListener) {
mView.resetSmartSuggestions(mContainer);
- List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant);
+ List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant,
+ useDelayedOnClickListener);
smartSuggestions.addAll(mView.inflateSmartActions(
new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
mLogger,
mEntry,
- mHeadsUpManager));
+ mHeadsUpManager,
+ useDelayedOnClickListener));
mView.addPreInflatedButtons(smartSuggestions);
}
@@ -491,7 +552,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
new SmartReplyView.SmartReplies(choices, null, null, false);
for (int i = 0; i < choices.length; ++i) {
Button current = SmartReplyView.inflateReplyButton(mView, mContext, i, smartReplies,
- null /* SmartReplyController */, null /* NotificationEntry */);
+ null /* SmartReplyController */, null /* NotificationEntry */,
+ true /* useDelayedOnClickListener */);
current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
current.getPaddingBottom());
if (previous != null) {
@@ -576,6 +638,40 @@ public class SmartReplyViewTest extends SysuiTestCase {
}
@Test
+ public void testTapSmartAction_beforeInitDelay_blocked() throws InterruptedException {
+ // 100 seconds is easily enough for our click to always be blocked.
+ when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
+ setSmartActions(TEST_ACTION_TITLES);
+
+ mView.getChildAt(2).performClick();
+
+ verify(mActivityStarter, never()).startPendingIntentDismissingKeyguard(any(), any());
+ }
+
+ @Test
+ public void testTapSmartAction_afterInitDelay_clickReceived() throws InterruptedException {
+ final long delayMs = 50L; // Using a small delay to not delay the test suite too much.
+ when(mConstants.getOnClickInitDelay()).thenReturn(delayMs);
+ setSmartActions(TEST_ACTION_TITLES);
+
+ Thread.sleep(delayMs);
+ mView.getChildAt(2).performClick();
+
+ verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any());
+ }
+
+ @Test
+ public void testTapSmartAction_withoutDelayedOnClickListener_bypassesDelay() {
+ // 100 seconds is easily enough for our click to always be blocked.
+ when(mConstants.getOnClickInitDelay()).thenReturn(100L * 1000L);
+ setSmartActions(TEST_ACTION_TITLES, false /* useDelayedOnClickListener */);
+
+ mView.getChildAt(2).performClick();
+
+ verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any());
+ }
+
+ @Test
public void testMeasure_shortSmartActions() {
String[] actions = new String[] {"Hi", "Hello", "Bye"};
// All choices should be displayed as SINGLE-line smart action buttons.
@@ -759,7 +855,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
private Button inflateActionButton(Notification.Action action) {
return SmartReplyView.inflateActionButton(mView, getContext(), 0,
new SmartReplyView.SmartActions(Collections.singletonList(action), false),
- mLogger, mEntry, mHeadsUpManager);
+ mLogger, mEntry, mHeadsUpManager, true /* useDelayedOnClickListener */);
}
@Test
@@ -965,7 +1061,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
createActions(new String[] {"action1"}));
expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ setSmartRepliesAndActions(
+ choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
assertEqualMeasures(expectedView, mView);
@@ -988,7 +1085,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
createActions(new String[] {"action1"}));
expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ setSmartRepliesAndActions(
+ choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
assertEqualMeasures(expectedView, mView);
@@ -1017,7 +1115,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
createActions(new String[] {"Short action"}));
expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
- setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
+ setSmartRepliesAndActions(
+ choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
assertEqualMeasures(expectedView, mView);