Merge "Block clicks on smart actions and replies just after creation/update." into qt-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
8daf5927ff
@@ -81,6 +81,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}.
|
||||
|
||||
@@ -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) -->
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 */);
|
||||
}
|
||||
|
||||
@@ -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 */);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -203,6 +206,48 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
||||
assertEquals(View.GONE, mContainer.getVisibility());
|
||||
}
|
||||
|
||||
@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);
|
||||
@@ -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) {
|
||||
@@ -575,6 +637,40 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
||||
verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any());
|
||||
}
|
||||
|
||||
@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"};
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user