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";
|
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
|
* The default component of
|
||||||
* {@link android.service.notification.NotificationAssistantService}.
|
* {@link android.service.notification.NotificationAssistantService}.
|
||||||
|
|||||||
@@ -469,6 +469,10 @@
|
|||||||
-->
|
-->
|
||||||
<integer name="config_smart_replies_in_notifications_max_num_actions">-1</integer>
|
<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.
|
<!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents.
|
||||||
Blank sends the user to the Chooser first.
|
Blank sends the user to the Chooser first.
|
||||||
This name is in the ComponentName flattened format (package/class) -->
|
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.NotificationGroupManager;
|
||||||
import com.android.systemui.statusbar.phone.StatusBar;
|
import com.android.systemui.statusbar.phone.StatusBar;
|
||||||
import com.android.systemui.statusbar.policy.HeadsUpManager;
|
import com.android.systemui.statusbar.policy.HeadsUpManager;
|
||||||
|
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
|
||||||
|
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
@@ -3194,6 +3195,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
|||||||
mAmbientGoingAway = goingAway;
|
mAmbientGoingAway = goingAway;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Smart Suggestions backing the smart suggestion buttons in the notification.
|
||||||
|
*/
|
||||||
|
public SmartRepliesAndActions getExistingSmartRepliesAndActions() {
|
||||||
|
return mPrivateLayout.getCurrentSmartRepliesAndActions();
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
|
protected void setChildrenContainer(NotificationChildrenContainer childrenContainer) {
|
||||||
mChildrenContainer = 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.phone.StatusBar;
|
||||||
import com.android.systemui.statusbar.policy.HeadsUpManager;
|
import com.android.systemui.statusbar.policy.HeadsUpManager;
|
||||||
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
|
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.statusbar.policy.SmartReplyConstants;
|
||||||
import com.android.systemui.util.Assert;
|
import com.android.systemui.util.Assert;
|
||||||
|
|
||||||
@@ -282,7 +283,8 @@ public class NotificationContentInflater {
|
|||||||
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
|
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
|
||||||
mRedactAmbient, packageContext);
|
mRedactAmbient, packageContext);
|
||||||
result = inflateSmartReplyViews(result, reInflateFlags, mRow.getEntry(),
|
result = inflateSmartReplyViews(result, reInflateFlags, mRow.getEntry(),
|
||||||
mRow.getContext(), mRow.getHeadsUpManager());
|
mRow.getContext(), mRow.getHeadsUpManager(),
|
||||||
|
mRow.getExistingSmartRepliesAndActions());
|
||||||
apply(
|
apply(
|
||||||
inflateSynchronously,
|
inflateSynchronously,
|
||||||
result,
|
result,
|
||||||
@@ -344,20 +346,20 @@ public class NotificationContentInflater {
|
|||||||
|
|
||||||
private static InflationProgress inflateSmartReplyViews(InflationProgress result,
|
private static InflationProgress inflateSmartReplyViews(InflationProgress result,
|
||||||
@InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
|
@InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
|
||||||
HeadsUpManager headsUpManager) {
|
HeadsUpManager headsUpManager, SmartRepliesAndActions previousSmartRepliesAndActions) {
|
||||||
SmartReplyConstants smartReplyConstants = Dependency.get(SmartReplyConstants.class);
|
SmartReplyConstants smartReplyConstants = Dependency.get(SmartReplyConstants.class);
|
||||||
SmartReplyController smartReplyController = Dependency.get(SmartReplyController.class);
|
SmartReplyController smartReplyController = Dependency.get(SmartReplyController.class);
|
||||||
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
|
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
|
||||||
result.expandedInflatedSmartReplies =
|
result.expandedInflatedSmartReplies =
|
||||||
InflatedSmartReplies.inflate(
|
InflatedSmartReplies.inflate(
|
||||||
context, entry, smartReplyConstants, smartReplyController,
|
context, entry, smartReplyConstants, smartReplyController,
|
||||||
headsUpManager);
|
headsUpManager, previousSmartRepliesAndActions);
|
||||||
}
|
}
|
||||||
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 && result.newHeadsUpView != null) {
|
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 && result.newHeadsUpView != null) {
|
||||||
result.headsUpInflatedSmartReplies =
|
result.headsUpInflatedSmartReplies =
|
||||||
InflatedSmartReplies.inflate(
|
InflatedSmartReplies.inflate(
|
||||||
context, entry, smartReplyConstants, smartReplyController,
|
context, entry, smartReplyConstants, smartReplyController,
|
||||||
headsUpManager);
|
headsUpManager, previousSmartRepliesAndActions);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -905,7 +907,8 @@ public class NotificationContentInflater {
|
|||||||
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
|
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
|
||||||
mRedactAmbient, packageContext);
|
mRedactAmbient, packageContext);
|
||||||
return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
|
return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
|
||||||
mRow.getContext(), mRow.getHeadsUpManager());
|
mRow.getContext(), mRow.getHeadsUpManager(),
|
||||||
|
mRow.getExistingSmartRepliesAndActions());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
mError = e;
|
mError = e;
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ public class NotificationContentView extends FrameLayout {
|
|||||||
private SmartReplyController mSmartReplyController;
|
private SmartReplyController mSmartReplyController;
|
||||||
private InflatedSmartReplies mExpandedInflatedSmartReplies;
|
private InflatedSmartReplies mExpandedInflatedSmartReplies;
|
||||||
private InflatedSmartReplies mHeadsUpInflatedSmartReplies;
|
private InflatedSmartReplies mHeadsUpInflatedSmartReplies;
|
||||||
|
private SmartRepliesAndActions mCurrentSmartRepliesAndActions;
|
||||||
|
|
||||||
private NotificationViewWrapper mContractedWrapper;
|
private NotificationViewWrapper mContractedWrapper;
|
||||||
private NotificationViewWrapper mExpandedWrapper;
|
private NotificationViewWrapper mExpandedWrapper;
|
||||||
@@ -1259,18 +1260,18 @@ public class NotificationContentView extends FrameLayout {
|
|||||||
// the same SmartRepliesAndActions to avoid discrepancies between the two views. We here
|
// the same SmartRepliesAndActions to avoid discrepancies between the two views. We here
|
||||||
// reuse that object for our local SmartRepliesAndActions to avoid discrepancies between
|
// reuse that object for our local SmartRepliesAndActions to avoid discrepancies between
|
||||||
// this class and the InflatedSmartReplies classes.
|
// this class and the InflatedSmartReplies classes.
|
||||||
SmartRepliesAndActions smartRepliesAndActions = mExpandedInflatedSmartReplies != null
|
mCurrentSmartRepliesAndActions = mExpandedInflatedSmartReplies != null
|
||||||
? mExpandedInflatedSmartReplies.getSmartRepliesAndActions()
|
? mExpandedInflatedSmartReplies.getSmartRepliesAndActions()
|
||||||
: mHeadsUpInflatedSmartReplies.getSmartRepliesAndActions();
|
: mHeadsUpInflatedSmartReplies.getSmartRepliesAndActions();
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, String.format("Adding suggestions for %s, %d actions, and %d replies.",
|
Log.d(TAG, String.format("Adding suggestions for %s, %d actions, and %d replies.",
|
||||||
entry.notification.getKey(),
|
entry.notification.getKey(),
|
||||||
smartRepliesAndActions.smartActions == null ? 0 :
|
mCurrentSmartRepliesAndActions.smartActions == null ? 0 :
|
||||||
smartRepliesAndActions.smartActions.actions.size(),
|
mCurrentSmartRepliesAndActions.smartActions.actions.size(),
|
||||||
smartRepliesAndActions.smartReplies == null ? 0 :
|
mCurrentSmartRepliesAndActions.smartReplies == null ? 0 :
|
||||||
smartRepliesAndActions.smartReplies.choices.length));
|
mCurrentSmartRepliesAndActions.smartReplies.choices.length));
|
||||||
}
|
}
|
||||||
applySmartReplyView(smartRepliesAndActions, entry);
|
applySmartReplyView(mCurrentSmartRepliesAndActions, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyRemoteInput(NotificationEntry entry, boolean hasFreeformRemoteInput) {
|
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() {
|
public void closeRemoteInput() {
|
||||||
if (mHeadsUpRemoteInput != null) {
|
if (mHeadsUpRemoteInput != null) {
|
||||||
mHeadsUpRemoteInput.close();
|
mHeadsUpRemoteInput.close();
|
||||||
|
|||||||
@@ -28,15 +28,19 @@ import android.util.Log;
|
|||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.util.ArrayUtils;
|
import com.android.internal.util.ArrayUtils;
|
||||||
import com.android.systemui.Dependency;
|
import com.android.systemui.Dependency;
|
||||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||||
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
|
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
|
||||||
import com.android.systemui.shared.system.PackageManagerWrapper;
|
import com.android.systemui.shared.system.PackageManagerWrapper;
|
||||||
|
import com.android.systemui.statusbar.NotificationUiAdjustment;
|
||||||
import com.android.systemui.statusbar.SmartReplyController;
|
import com.android.systemui.statusbar.SmartReplyController;
|
||||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,29 +83,52 @@ public class InflatedSmartReplies {
|
|||||||
NotificationEntry entry,
|
NotificationEntry entry,
|
||||||
SmartReplyConstants smartReplyConstants,
|
SmartReplyConstants smartReplyConstants,
|
||||||
SmartReplyController smartReplyController,
|
SmartReplyController smartReplyController,
|
||||||
HeadsUpManager headsUpManager) {
|
HeadsUpManager headsUpManager,
|
||||||
SmartRepliesAndActions smartRepliesAndActions =
|
SmartRepliesAndActions existingSmartRepliesAndActions) {
|
||||||
|
SmartRepliesAndActions newSmartRepliesAndActions =
|
||||||
chooseSmartRepliesAndActions(smartReplyConstants, entry);
|
chooseSmartRepliesAndActions(smartReplyConstants, entry);
|
||||||
if (!shouldShowSmartReplyView(entry, smartRepliesAndActions)) {
|
if (!shouldShowSmartReplyView(entry, newSmartRepliesAndActions)) {
|
||||||
return new InflatedSmartReplies(null /* smartReplyView */,
|
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);
|
SmartReplyView smartReplyView = SmartReplyView.inflate(context);
|
||||||
|
|
||||||
List<Button> suggestionButtons = new ArrayList<>();
|
List<Button> suggestionButtons = new ArrayList<>();
|
||||||
if (smartRepliesAndActions.smartReplies != null) {
|
if (newSmartRepliesAndActions.smartReplies != null) {
|
||||||
suggestionButtons.addAll(smartReplyView.inflateRepliesFromRemoteInput(
|
suggestionButtons.addAll(smartReplyView.inflateRepliesFromRemoteInput(
|
||||||
smartRepliesAndActions.smartReplies, smartReplyController, entry));
|
newSmartRepliesAndActions.smartReplies, smartReplyController, entry,
|
||||||
|
delayOnClickListener));
|
||||||
}
|
}
|
||||||
if (smartRepliesAndActions.smartActions != null) {
|
if (newSmartRepliesAndActions.smartActions != null) {
|
||||||
suggestionButtons.addAll(
|
suggestionButtons.addAll(
|
||||||
smartReplyView.inflateSmartActions(smartRepliesAndActions.smartActions,
|
smartReplyView.inflateSmartActions(newSmartRepliesAndActions.smartActions,
|
||||||
smartReplyController, entry, headsUpManager));
|
smartReplyController, entry, headsUpManager,
|
||||||
|
delayOnClickListener));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new InflatedSmartReplies(smartReplyView, suggestionButtons,
|
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.smartReplies = smartReplies;
|
||||||
this.smartActions = smartActions;
|
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 boolean mDefaultShowInHeadsUp;
|
||||||
private final int mDefaultMinNumSystemGeneratedReplies;
|
private final int mDefaultMinNumSystemGeneratedReplies;
|
||||||
private final int mDefaultMaxNumActions;
|
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
|
// 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
|
// 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 boolean mShowInHeadsUp;
|
||||||
private volatile int mMinNumSystemGeneratedReplies;
|
private volatile int mMinNumSystemGeneratedReplies;
|
||||||
private volatile int mMaxNumActions;
|
private volatile int mMaxNumActions;
|
||||||
|
private volatile long mOnClickInitDelay;
|
||||||
|
|
||||||
private final Handler mHandler;
|
private final Handler mHandler;
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
@@ -83,6 +85,8 @@ public final class SmartReplyConstants {
|
|||||||
R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies);
|
R.integer.config_smart_replies_in_notifications_min_num_system_generated_replies);
|
||||||
mDefaultMaxNumActions = resources.getInteger(
|
mDefaultMaxNumActions = resources.getInteger(
|
||||||
R.integer.config_smart_replies_in_notifications_max_num_actions);
|
R.integer.config_smart_replies_in_notifications_max_num_actions);
|
||||||
|
mDefaultOnClickInitDelay = resources.getInteger(
|
||||||
|
R.integer.config_smart_replies_in_notifications_onclick_init_delay);
|
||||||
|
|
||||||
registerDeviceConfigListener();
|
registerDeviceConfigListener();
|
||||||
updateConstants();
|
updateConstants();
|
||||||
@@ -136,6 +140,10 @@ public final class SmartReplyConstants {
|
|||||||
DeviceConfig.NAMESPACE_SYSTEMUI,
|
DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||||
SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS,
|
SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS,
|
||||||
mDefaultMaxNumActions);
|
mDefaultMaxNumActions);
|
||||||
|
mOnClickInitDelay = DeviceConfig.getInt(
|
||||||
|
DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||||
|
SystemUiDeviceConfigFlags.SSIN_ONCLICK_INIT_DELAY,
|
||||||
|
mDefaultOnClickInitDelay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,4 +226,12 @@ public final class SmartReplyConstants {
|
|||||||
public int getMaxNumActions() {
|
public int getMaxNumActions() {
|
||||||
return mMaxNumActions;
|
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.InsetDrawable;
|
||||||
import android.graphics.drawable.RippleDrawable;
|
import android.graphics.drawable.RippleDrawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
import android.text.method.TransformationMethod;
|
import android.text.method.TransformationMethod;
|
||||||
@@ -213,14 +214,16 @@ public class SmartReplyView extends ViewGroup {
|
|||||||
*/
|
*/
|
||||||
public List<Button> inflateRepliesFromRemoteInput(
|
public List<Button> inflateRepliesFromRemoteInput(
|
||||||
@NonNull SmartReplies smartReplies,
|
@NonNull SmartReplies smartReplies,
|
||||||
SmartReplyController smartReplyController, NotificationEntry entry) {
|
SmartReplyController smartReplyController, NotificationEntry entry,
|
||||||
|
boolean delayOnClickListener) {
|
||||||
List<Button> buttons = new ArrayList<>();
|
List<Button> buttons = new ArrayList<>();
|
||||||
|
|
||||||
if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) {
|
if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) {
|
||||||
if (smartReplies.choices != null) {
|
if (smartReplies.choices != null) {
|
||||||
for (int i = 0; i < smartReplies.choices.length; ++i) {
|
for (int i = 0; i < smartReplies.choices.length; ++i) {
|
||||||
buttons.add(inflateReplyButton(
|
buttons.add(inflateReplyButton(
|
||||||
this, getContext(), i, smartReplies, smartReplyController, entry));
|
this, getContext(), i, smartReplies, smartReplyController, entry,
|
||||||
|
delayOnClickListener));
|
||||||
}
|
}
|
||||||
this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant;
|
this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant;
|
||||||
}
|
}
|
||||||
@@ -234,7 +237,7 @@ public class SmartReplyView extends ViewGroup {
|
|||||||
*/
|
*/
|
||||||
public List<Button> inflateSmartActions(@NonNull SmartActions smartActions,
|
public List<Button> inflateSmartActions(@NonNull SmartActions smartActions,
|
||||||
SmartReplyController smartReplyController, NotificationEntry entry,
|
SmartReplyController smartReplyController, NotificationEntry entry,
|
||||||
HeadsUpManager headsUpManager) {
|
HeadsUpManager headsUpManager, boolean delayOnClickListener) {
|
||||||
List<Button> buttons = new ArrayList<>();
|
List<Button> buttons = new ArrayList<>();
|
||||||
int numSmartActions = smartActions.actions.size();
|
int numSmartActions = smartActions.actions.size();
|
||||||
for (int n = 0; n < numSmartActions; n++) {
|
for (int n = 0; n < numSmartActions; n++) {
|
||||||
@@ -242,7 +245,7 @@ public class SmartReplyView extends ViewGroup {
|
|||||||
if (action.actionIntent != null) {
|
if (action.actionIntent != null) {
|
||||||
buttons.add(inflateActionButton(
|
buttons.add(inflateActionButton(
|
||||||
this, getContext(), n, smartActions, smartReplyController, entry,
|
this, getContext(), n, smartActions, smartReplyController, entry,
|
||||||
headsUpManager));
|
headsUpManager, delayOnClickListener));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return buttons;
|
return buttons;
|
||||||
@@ -259,7 +262,7 @@ public class SmartReplyView extends ViewGroup {
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static Button inflateReplyButton(SmartReplyView smartReplyView, Context context,
|
static Button inflateReplyButton(SmartReplyView smartReplyView, Context context,
|
||||||
int replyIndex, SmartReplies smartReplies, SmartReplyController smartReplyController,
|
int replyIndex, SmartReplies smartReplies, SmartReplyController smartReplyController,
|
||||||
NotificationEntry entry) {
|
NotificationEntry entry, boolean useDelayedOnClickListener) {
|
||||||
Button b = (Button) LayoutInflater.from(context).inflate(
|
Button b = (Button) LayoutInflater.from(context).inflate(
|
||||||
R.layout.smart_reply_button, smartReplyView, false);
|
R.layout.smart_reply_button, smartReplyView, false);
|
||||||
CharSequence choice = smartReplies.choices[replyIndex];
|
CharSequence choice = smartReplies.choices[replyIndex];
|
||||||
@@ -299,9 +302,13 @@ public class SmartReplyView extends ViewGroup {
|
|||||||
return false; // do not defer
|
return false; // do not defer
|
||||||
};
|
};
|
||||||
|
|
||||||
b.setOnClickListener(view -> {
|
OnClickListener onClickListener = view ->
|
||||||
smartReplyView.mKeyguardDismissUtil.executeWhenUnlocked(action);
|
smartReplyView.mKeyguardDismissUtil.executeWhenUnlocked(action);
|
||||||
});
|
if (useDelayedOnClickListener) {
|
||||||
|
onClickListener = new DelayedOnClickListener(onClickListener,
|
||||||
|
smartReplyView.mConstants.getOnClickInitDelay());
|
||||||
|
}
|
||||||
|
b.setOnClickListener(onClickListener);
|
||||||
|
|
||||||
b.setAccessibilityDelegate(new AccessibilityDelegate() {
|
b.setAccessibilityDelegate(new AccessibilityDelegate() {
|
||||||
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
|
||||||
@@ -322,7 +329,7 @@ public class SmartReplyView extends ViewGroup {
|
|||||||
static Button inflateActionButton(SmartReplyView smartReplyView, Context context,
|
static Button inflateActionButton(SmartReplyView smartReplyView, Context context,
|
||||||
int actionIndex, SmartActions smartActions,
|
int actionIndex, SmartActions smartActions,
|
||||||
SmartReplyController smartReplyController, NotificationEntry entry,
|
SmartReplyController smartReplyController, NotificationEntry entry,
|
||||||
HeadsUpManager headsUpManager) {
|
HeadsUpManager headsUpManager, boolean useDelayedOnClickListener) {
|
||||||
Notification.Action action = smartActions.actions.get(actionIndex);
|
Notification.Action action = smartActions.actions.get(actionIndex);
|
||||||
Button button = (Button) LayoutInflater.from(context).inflate(
|
Button button = (Button) LayoutInflater.from(context).inflate(
|
||||||
R.layout.smart_action_button, smartReplyView, false);
|
R.layout.smart_action_button, smartReplyView, false);
|
||||||
@@ -335,14 +342,19 @@ public class SmartReplyView extends ViewGroup {
|
|||||||
iconDrawable.setBounds(0, 0, newIconSize, newIconSize);
|
iconDrawable.setBounds(0, 0, newIconSize, newIconSize);
|
||||||
button.setCompoundDrawables(iconDrawable, null, null, null);
|
button.setCompoundDrawables(iconDrawable, null, null, null);
|
||||||
|
|
||||||
button.setOnClickListener(view ->
|
OnClickListener onClickListener = view ->
|
||||||
smartReplyView.getActivityStarter().startPendingIntentDismissingKeyguard(
|
smartReplyView.getActivityStarter().startPendingIntentDismissingKeyguard(
|
||||||
action.actionIntent,
|
action.actionIntent,
|
||||||
() -> {
|
() -> {
|
||||||
smartReplyController.smartActionClicked(
|
smartReplyController.smartActionClicked(
|
||||||
entry, actionIndex, action, smartActions.fromAssistant);
|
entry, actionIndex, action, smartActions.fromAssistant);
|
||||||
headsUpManager.removeNotification(entry.key, true);
|
headsUpManager.removeNotification(entry.key, true);
|
||||||
}));
|
});
|
||||||
|
if (useDelayedOnClickListener) {
|
||||||
|
onClickListener = new DelayedOnClickListener(onClickListener,
|
||||||
|
smartReplyView.mConstants.getOnClickInitDelay());
|
||||||
|
}
|
||||||
|
button.setOnClickListener(onClickListener);
|
||||||
|
|
||||||
// Mark this as an Action button
|
// Mark this as an Action button
|
||||||
final LayoutParams lp = (LayoutParams) button.getLayoutParams();
|
final LayoutParams lp = (LayoutParams) button.getLayoutParams();
|
||||||
@@ -958,4 +970,32 @@ public class SmartReplyView extends ViewGroup {
|
|||||||
this.fromAssistant = fromAssistant;
|
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 {
|
public Intent waitForIntent() throws InterruptedException {
|
||||||
return mQueue.poll(10, TimeUnit.SECONDS);
|
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.shared.system.PackageManagerWrapper;
|
||||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||||
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
|
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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -51,6 +53,7 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.MockitoAnnotations;
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -322,6 +325,72 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
|
|||||||
mEntry.systemGeneratedSmartActions);
|
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) {
|
private void setupAppGeneratedReplies(CharSequence[] smartReplies) {
|
||||||
setupAppGeneratedReplies(smartReplies, true /* allowSystemGeneratedReplies */);
|
setupAppGeneratedReplies(smartReplies, true /* allowSystemGeneratedReplies */);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,6 +211,18 @@ public class SmartReplyConstantsTest extends SysuiTestCase {
|
|||||||
assertEquals(10, mConstants.getMaxNumActions());
|
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) {
|
private void overrideSetting(String propertyName, String value) {
|
||||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||||
propertyName, value, false /* makeDefault */);
|
propertyName, value, false /* makeDefault */);
|
||||||
@@ -239,5 +251,7 @@ public class SmartReplyConstantsTest extends SysuiTestCase {
|
|||||||
false /* makeDefault */);
|
false /* makeDefault */);
|
||||||
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||||
SystemUiDeviceConfigFlags.SSIN_MAX_NUM_ACTIONS, null, false /* makeDefault */);
|
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.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -120,6 +121,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
|
when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
|
||||||
when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
|
when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
|
||||||
when(mConstants.getMaxNumActions()).thenReturn(-1);
|
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();
|
final Resources res = mContext.getResources();
|
||||||
mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
|
mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
|
||||||
@@ -164,7 +167,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
|
|
||||||
mView.getChildAt(2).performClick();
|
mView.getChildAt(2).performClick();
|
||||||
|
|
||||||
assertNull(mReceiver.waitForIntent());
|
assertNull(mReceiver.waitForIntentShortDelay());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -176,7 +179,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
mView.getChildAt(2).performClick();
|
mView.getChildAt(2).performClick();
|
||||||
|
|
||||||
// No intent until the screen is unlocked.
|
// No intent until the screen is unlocked.
|
||||||
assertNull(mReceiver.waitForIntent());
|
assertNull(mReceiver.waitForIntentShortDelay());
|
||||||
|
|
||||||
actionRef.get().onDismiss();
|
actionRef.get().onDismiss();
|
||||||
|
|
||||||
@@ -203,6 +206,48 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
assertEquals(View.GONE, mContainer.getVisibility());
|
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
|
@Test
|
||||||
public void testMeasure_empty() {
|
public void testMeasure_empty() {
|
||||||
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
|
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
|
||||||
@@ -403,18 +448,25 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setSmartReplies(CharSequence[] choices) {
|
private void setSmartReplies(CharSequence[] choices) {
|
||||||
|
setSmartReplies(choices, true /* useDelayedOnClickListener */);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSmartReplies(CharSequence[] choices, boolean useDelayedOnClickListener) {
|
||||||
mView.resetSmartSuggestions(mContainer);
|
mView.resetSmartSuggestions(mContainer);
|
||||||
List<Button> replyButtons = inflateSmartReplies(choices, false /* fromAssistant */);
|
List<Button> replyButtons = inflateSmartReplies(choices, false /* fromAssistant */,
|
||||||
|
useDelayedOnClickListener);
|
||||||
mView.addPreInflatedButtons(replyButtons);
|
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,
|
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
|
||||||
new Intent(TEST_ACTION), 0);
|
new Intent(TEST_ACTION), 0);
|
||||||
RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
|
RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
|
||||||
SmartReplyView.SmartReplies smartReplies =
|
SmartReplyView.SmartReplies smartReplies =
|
||||||
new SmartReplyView.SmartReplies(choices, input, pendingIntent, fromAssistant);
|
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) {
|
private Notification.Action createAction(String actionTitle) {
|
||||||
@@ -432,28 +484,37 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setSmartActions(String[] actionTitles) {
|
private void setSmartActions(String[] actionTitles) {
|
||||||
|
setSmartActions(actionTitles, true /* useDelayedOnClickListener */);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSmartActions(String[] actionTitles, boolean useDelayedOnClickListener) {
|
||||||
mView.resetSmartSuggestions(mContainer);
|
mView.resetSmartSuggestions(mContainer);
|
||||||
List<Button> actions = mView.inflateSmartActions(
|
List<Button> actions = mView.inflateSmartActions(
|
||||||
new SmartReplyView.SmartActions(createActions(actionTitles), false),
|
new SmartReplyView.SmartActions(createActions(actionTitles), false),
|
||||||
mLogger,
|
mLogger,
|
||||||
mEntry,
|
mEntry,
|
||||||
mHeadsUpManager);
|
mHeadsUpManager,
|
||||||
|
useDelayedOnClickListener);
|
||||||
mView.addPreInflatedButtons(actions);
|
mView.addPreInflatedButtons(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
|
private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
|
||||||
setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */);
|
setSmartRepliesAndActions(choices, actionTitles, false /* fromAssistant */,
|
||||||
|
true /* useDelayedOnClickListener */);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSmartRepliesAndActions(
|
private void setSmartRepliesAndActions(
|
||||||
CharSequence[] choices, String[] actionTitles, boolean fromAssistant) {
|
CharSequence[] choices, String[] actionTitles, boolean fromAssistant,
|
||||||
|
boolean useDelayedOnClickListener) {
|
||||||
mView.resetSmartSuggestions(mContainer);
|
mView.resetSmartSuggestions(mContainer);
|
||||||
List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant);
|
List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant,
|
||||||
|
useDelayedOnClickListener);
|
||||||
smartSuggestions.addAll(mView.inflateSmartActions(
|
smartSuggestions.addAll(mView.inflateSmartActions(
|
||||||
new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
|
new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
|
||||||
mLogger,
|
mLogger,
|
||||||
mEntry,
|
mEntry,
|
||||||
mHeadsUpManager));
|
mHeadsUpManager,
|
||||||
|
useDelayedOnClickListener));
|
||||||
mView.addPreInflatedButtons(smartSuggestions);
|
mView.addPreInflatedButtons(smartSuggestions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,7 +552,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
new SmartReplyView.SmartReplies(choices, null, null, false);
|
new SmartReplyView.SmartReplies(choices, null, null, false);
|
||||||
for (int i = 0; i < choices.length; ++i) {
|
for (int i = 0; i < choices.length; ++i) {
|
||||||
Button current = SmartReplyView.inflateReplyButton(mView, mContext, i, smartReplies,
|
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.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
|
||||||
current.getPaddingBottom());
|
current.getPaddingBottom());
|
||||||
if (previous != null) {
|
if (previous != null) {
|
||||||
@@ -575,6 +637,40 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any());
|
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
|
@Test
|
||||||
public void testMeasure_shortSmartActions() {
|
public void testMeasure_shortSmartActions() {
|
||||||
String[] actions = new String[] {"Hi", "Hello", "Bye"};
|
String[] actions = new String[] {"Hi", "Hello", "Bye"};
|
||||||
@@ -759,7 +855,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
private Button inflateActionButton(Notification.Action action) {
|
private Button inflateActionButton(Notification.Action action) {
|
||||||
return SmartReplyView.inflateActionButton(mView, getContext(), 0,
|
return SmartReplyView.inflateActionButton(mView, getContext(), 0,
|
||||||
new SmartReplyView.SmartActions(Collections.singletonList(action), false),
|
new SmartReplyView.SmartActions(Collections.singletonList(action), false),
|
||||||
mLogger, mEntry, mHeadsUpManager);
|
mLogger, mEntry, mHeadsUpManager, true /* useDelayedOnClickListener */);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -965,7 +1061,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
createActions(new String[] {"action1"}));
|
createActions(new String[] {"action1"}));
|
||||||
expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
||||||
|
|
||||||
setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
|
setSmartRepliesAndActions(
|
||||||
|
choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
|
||||||
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
||||||
|
|
||||||
assertEqualMeasures(expectedView, mView);
|
assertEqualMeasures(expectedView, mView);
|
||||||
@@ -988,7 +1085,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
createActions(new String[] {"action1"}));
|
createActions(new String[] {"action1"}));
|
||||||
expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
||||||
|
|
||||||
setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
|
setSmartRepliesAndActions(
|
||||||
|
choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
|
||||||
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
||||||
|
|
||||||
assertEqualMeasures(expectedView, mView);
|
assertEqualMeasures(expectedView, mView);
|
||||||
@@ -1017,7 +1115,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
|
|||||||
createActions(new String[] {"Short action"}));
|
createActions(new String[] {"Short action"}));
|
||||||
expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
expectedView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
||||||
|
|
||||||
setSmartRepliesAndActions(choices, actions, true /* fromAssistant */);
|
setSmartRepliesAndActions(
|
||||||
|
choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
|
||||||
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
|
||||||
|
|
||||||
assertEqualMeasures(expectedView, mView);
|
assertEqualMeasures(expectedView, mView);
|
||||||
|
|||||||
Reference in New Issue
Block a user