Merge changes from topic "phishing_templates" into sc-dev

* changes:
  Refactor the SmartReply classes
  Filter out RemoteInput actions when Phishing action is present
This commit is contained in:
Jeff DeCew
2021-02-19 18:06:33 +00:00
committed by Android (Google) Code Review
19 changed files with 628 additions and 320 deletions

View File

@@ -3624,7 +3624,7 @@ public class Notification implements Parcelable
private Bundle mUserExtras = new Bundle();
private Style mStyle;
@UnsupportedAppUsage
private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS);
private ArrayList<Action> mActions = new ArrayList<>(MAX_ACTION_BUTTONS);
private ArrayList<Person> mPersonList = new ArrayList<>();
private ContrastColorUtil mColorUtil;
private boolean mIsLegacy;
@@ -4878,6 +4878,16 @@ public class Notification implements Parcelable
return this;
}
private void bindPhishingAlertIcon(RemoteViews contentView, StandardTemplateParams p) {
// TODO(b/180334837): Get buy-in on this color, or make sure to give this the
// accent color, while still accommodating the colorized state.
contentView.setDrawableTint(
R.id.phishing_alert,
false /* targetBackground */,
getPrimaryTextColor(p),
PorterDuff.Mode.SRC_ATOP);
}
private Drawable getProfileBadgeDrawable() {
if (mContext.getUserId() == UserHandle.USER_SYSTEM) {
// This user can never be a badged profile,
@@ -5279,6 +5289,7 @@ public class Notification implements Parcelable
hasTextToLeft |= bindHeaderAppName(contentView, p, true /* force */);
}
bindHeaderChronometerAndTime(contentView, p, hasTextToLeft);
bindPhishingAlertIcon(contentView, p);
bindProfileBadge(contentView, p);
bindAlertedIcon(contentView, p);
bindExpandButton(contentView, p);
@@ -5474,15 +5485,18 @@ public class Notification implements Parcelable
RemoteViews.MARGIN_BOTTOM, bottomMarginDimen);
}
private static List<Notification.Action> filterOutContextualActions(
List<Notification.Action> actions) {
List<Notification.Action> nonContextualActions = new ArrayList<>();
for (Notification.Action action : actions) {
/**
* Returns the actions that are not contextual.
*/
private @NonNull List<Notification.Action> getNonContextualActions() {
if (mActions == null) return Collections.emptyList();
List<Notification.Action> contextualActions = new ArrayList<>();
for (Notification.Action action : mActions) {
if (!action.isContextual()) {
nonContextualActions.add(action);
contextualActions.add(action);
}
}
return nonContextualActions;
return contextualActions;
}
private RemoteViews applyStandardTemplateWithActions(int layoutId,
@@ -5493,9 +5507,9 @@ public class Notification implements Parcelable
boolean validRemoteInput = false;
// In the UI contextual actions appear separately from the standard actions, so we
// In the UI, contextual actions appear separately from the standard actions, so we
// filter them out here.
List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions);
List<Notification.Action> nonContextualActions = getNonContextualActions();
int N = nonContextualActions.size();
boolean emphazisedMode = mN.fullScreenIntent != null;
@@ -9241,7 +9255,7 @@ public class Notification implements Parcelable
lastAction = answerAction;
}
// For consistency with the standard actions bar, contextual actions are ignored.
for (Action action : Builder.filterOutContextualActions(mBuilder.mActions)) {
for (Action action : mBuilder.getNonContextualActions()) {
if (actions.size() >= MAX_ACTION_BUTTONS - 1) {
break;
}

View File

@@ -101,8 +101,8 @@
<ImageView
android:id="@+id/verification_icon"
android:layout_width="@dimen/notification_badge_size"
android:layout_height="@dimen/notification_badge_size"
android:layout_width="@dimen/notification_verification_icon_size"
android:layout_height="@dimen/notification_verification_icon_size"
android:layout_gravity="center"
android:layout_marginStart="4dp"
android:contentDescription="@string/notification_alerted_content_description"
@@ -139,6 +139,19 @@
android:visibility="gone"
/>
<ImageView
android:id="@+id/phishing_alert"
android:layout_width="@dimen/notification_phishing_alert_size"
android:layout_height="@dimen/notification_phishing_alert_size"
android:layout_marginStart="4dp"
android:paddingTop="2dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_dialog_alert_material"
android:visibility="gone"
android:contentDescription="@string/notification_phishing_alert_content_description"
/>
<ImageView
android:id="@+id/profile_badge"
android:layout_width="@dimen/notification_badge_size"

View File

@@ -122,6 +122,18 @@
android:contentDescription="@string/notification_feedback_indicator"
/>
<ImageView
android:id="@+id/phishing_alert"
android:layout_width="@dimen/notification_phishing_alert_size"
android:layout_height="@dimen/notification_phishing_alert_size"
android:layout_marginStart="4dp"
android:baseline="10dp"
android:scaleType="fitCenter"
android:src="@drawable/ic_dialog_alert_material"
android:visibility="gone"
android:contentDescription="@string/notification_phishing_alert_content_description"
/>
<ImageView
android:id="@+id/profile_badge"
android:layout_width="@dimen/notification_badge_size"

View File

@@ -496,14 +496,20 @@
<!-- The padding on top of inbox style elements -->
<dimen name="notification_inbox_item_top_padding">5dp</dimen>
<!-- Size of the verification icon for call notifications -->
<dimen name="notification_verification_icon_size">@dimen/notification_badge_size</dimen>
<!-- Size of the feedback indicator for notifications -->
<dimen name="notification_feedback_size">20dp</dimen>
<!-- Size of the phishing alert for notifications -->
<dimen name="notification_phishing_alert_size">@dimen/notification_badge_size</dimen>
<!-- Size of the profile badge for notifications -->
<dimen name="notification_badge_size">12dp</dimen>
<!-- Size of the alerted icon for notifications -->
<dimen name="notification_alerted_size">12dp</dimen>
<dimen name="notification_alerted_size">@dimen/notification_badge_size</dimen>
<!-- Keyguard dimensions -->
<!-- TEMP -->

View File

@@ -5002,6 +5002,9 @@
<string name="stk_cc_ss_to_ussd">SS request changed to USSD request</string>
<string name="stk_cc_ss_to_ss">Changed to new SS request</string>
<!-- Content description of the phishing alert icon in the notification. [CHAR_LIMIT=NONE] -->
<string name="notification_phishing_alert_content_description">Phishing alert</string>
<!-- Content description of the work profile icon in the notification. -->
<string name="notification_work_profile_content_description">Work profile</string>

View File

@@ -233,6 +233,7 @@
<java-symbol type="id" name="pin_confirm_text" />
<java-symbol type="id" name="pin_error_message" />
<java-symbol type="id" name="timePickerLayout" />
<java-symbol type="id" name="phishing_alert" />
<java-symbol type="id" name="profile_badge" />
<java-symbol type="id" name="alerted_icon" />
<java-symbol type="id" name="transitionPosition" />

View File

@@ -104,7 +104,7 @@ import com.android.systemui.statusbar.notification.stack.SwipeableView;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.wmshell.BubblesManager;
import java.io.FileDescriptor;
@@ -3196,8 +3196,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
/**
* Returns the Smart Suggestions backing the smart suggestion buttons in the notification.
*/
public SmartRepliesAndActions getExistingSmartRepliesAndActions() {
return mPrivateLayout.getCurrentSmartRepliesAndActions();
public InflatedSmartReplyState getExistingSmartReplyState() {
return mPrivateLayout.getCurrentSmartReplyState();
}
@VisibleForTesting

View File

@@ -48,9 +48,9 @@ import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
import com.android.systemui.util.Assert;
import java.util.HashMap;
@@ -74,7 +74,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final NotifRemoteViewCache mRemoteViewCache;
private final ConversationNotificationProcessor mConversationProcessor;
private final Executor mBgExecutor;
private final SmartRepliesAndActionsInflater mSmartRepliesAndActionsInflater;
private final SmartReplyStateInflater mSmartReplyStateInflater;
@Inject
NotificationContentInflater(
@@ -83,13 +83,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder
ConversationNotificationProcessor conversationProcessor,
MediaFeatureFlag mediaFeatureFlag,
@Background Executor bgExecutor,
SmartRepliesAndActionsInflater smartRepliesInflater) {
SmartReplyStateInflater smartRepliesInflater) {
mRemoteViewCache = remoteViewCache;
mRemoteInputManager = remoteInputManager;
mConversationProcessor = conversationProcessor;
mIsMediaInQS = mediaFeatureFlag.getEnabled();
mBgExecutor = bgExecutor;
mSmartRepliesAndActionsInflater = smartRepliesInflater;
mSmartReplyStateInflater = smartRepliesInflater;
}
@Override
@@ -133,7 +133,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
callback,
mRemoteInputManager.getRemoteViewsOnClickHandler(),
mIsMediaInQS,
mSmartRepliesAndActionsInflater);
mSmartReplyStateInflater);
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
@@ -150,7 +150,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
@InflationFlag int reInflateFlags,
Notification.Builder builder,
Context packageContext,
SmartRepliesAndActionsInflater smartRepliesInflater) {
SmartReplyStateInflater smartRepliesInflater) {
InflationProgress result = createRemoteViews(reInflateFlags,
builder,
bindParams.isLowPriority,
@@ -160,7 +160,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
result = inflateSmartReplyViews(result, reInflateFlags, entry,
row.getContext(), packageContext,
row.getExistingSmartRepliesAndActions(),
row.getExistingSmartReplyState(),
smartRepliesInflater);
apply(
@@ -268,15 +268,26 @@ public class NotificationContentInflater implements NotificationRowContentBinder
NotificationEntry entry,
Context context,
Context packageContext,
SmartRepliesAndActions previousSmartRepliesAndActions,
SmartRepliesAndActionsInflater inflater) {
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
result.expandedInflatedSmartReplies = inflater.inflateSmartReplies(
context, packageContext, entry, previousSmartRepliesAndActions);
InflatedSmartReplyState previousSmartReplyState,
SmartReplyStateInflater inflater) {
boolean inflateContracted = (reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0
&& result.newContentView != null;
boolean inflateExpanded = (reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0
&& result.newExpandedView != null;
boolean inflateHeadsUp = (reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0
&& result.newHeadsUpView != null;
if (inflateContracted || inflateExpanded || inflateHeadsUp) {
result.inflatedSmartReplyState = inflater.inflateSmartReplyState(entry);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 && result.newHeadsUpView != null) {
result.headsUpInflatedSmartReplies = inflater.inflateSmartReplies(
context, packageContext, entry, previousSmartRepliesAndActions);
if (inflateExpanded) {
result.expandedInflatedSmartReplies = inflater.inflateSmartReplyViewHolder(
context, packageContext, entry, previousSmartReplyState,
result.inflatedSmartReplyState);
}
if (inflateHeadsUp) {
result.headsUpInflatedSmartReplies = inflater.inflateSmartReplyViewHolder(
context, packageContext, entry, previousSmartReplyState,
result.inflatedSmartReplyState);
}
return result;
}
@@ -566,6 +577,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
if (runningInflations.isEmpty()) {
boolean setRepliesAndActions = true;
if ((reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0) {
if (result.inflatedContentView != null) {
// New view case
@@ -578,6 +590,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
result.newContentView);
}
setRepliesAndActions = true;
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
@@ -599,6 +612,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
privateLayout.setExpandedInflatedSmartReplies(null);
}
row.setExpandable(result.newExpandedView != null);
setRepliesAndActions = true;
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
@@ -619,6 +633,10 @@ public class NotificationContentInflater implements NotificationRowContentBinder
} else {
privateLayout.setHeadsUpInflatedSmartReplies(null);
}
setRepliesAndActions = true;
}
if (setRepliesAndActions) {
privateLayout.setInflatedSmartReplyState(result.inflatedSmartReplyState);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
@@ -709,7 +727,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private CancellationSignal mCancellationSignal;
private final ConversationNotificationProcessor mConversationProcessor;
private final boolean mIsMediaInQS;
private final SmartRepliesAndActionsInflater mSmartRepliesInflater;
private final SmartReplyStateInflater mSmartRepliesInflater;
private AsyncInflationTask(
Executor bgExecutor,
@@ -725,7 +743,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
InflationCallback callback,
RemoteViews.InteractionHandler remoteViewClickHandler,
boolean isMediaFlagEnabled,
SmartRepliesAndActionsInflater smartRepliesInflater) {
SmartReplyStateInflater smartRepliesInflater) {
mEntry = entry;
mRow = row;
mBgExecutor = bgExecutor;
@@ -776,15 +794,14 @@ public class NotificationContentInflater implements NotificationRowContentBinder
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext);
SmartRepliesAndActions repliesAndActions =
mRow.getExistingSmartRepliesAndActions();
InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
return inflateSmartReplyViews(
inflationProgress,
mReInflateFlags,
mEntry,
mContext,
packageContext,
repliesAndActions,
previousSmartReplyState,
mSmartRepliesInflater);
} catch (Exception e) {
mError = e;
@@ -879,8 +896,9 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private CharSequence headsUpStatusBarText;
private CharSequence headsUpStatusBarTextPublic;
private InflatedSmartReplies expandedInflatedSmartReplies;
private InflatedSmartReplies headsUpInflatedSmartReplies;
private InflatedSmartReplyState inflatedSmartReplyState;
private InflatedSmartReplyViewHolder expandedInflatedSmartReplies;
private InflatedSmartReplyViewHolder headsUpInflatedSmartReplies;
}
@VisibleForTesting

View File

@@ -58,16 +58,18 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflaterKt;
import com.android.systemui.statusbar.policy.SmartReplyConstants;
import com.android.systemui.statusbar.policy.SmartReplyStateInflaterKt;
import com.android.systemui.statusbar.policy.SmartReplyView;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A frame layout containing the actual payload of the notification, including the contracted,
@@ -106,9 +108,9 @@ public class NotificationContentView extends FrameLayout {
private SmartReplyView mExpandedSmartReplyView;
private SmartReplyView mHeadsUpSmartReplyView;
private SmartReplyController mSmartReplyController;
private InflatedSmartReplies mExpandedInflatedSmartReplies;
private InflatedSmartReplies mHeadsUpInflatedSmartReplies;
private SmartRepliesAndActions mCurrentSmartRepliesAndActions;
private InflatedSmartReplyViewHolder mExpandedInflatedSmartReplies;
private InflatedSmartReplyViewHolder mHeadsUpInflatedSmartReplies;
private InflatedSmartReplyState mCurrentSmartReplyState;
private NotificationViewWrapper mContractedWrapper;
private NotificationViewWrapper mExpandedWrapper;
@@ -1189,29 +1191,19 @@ public class NotificationContentView extends FrameLayout {
applyRemoteInput(entry, hasFreeformRemoteInput(entry));
if (mExpandedInflatedSmartReplies == null && mHeadsUpInflatedSmartReplies == null) {
if (mCurrentSmartReplyState == null) {
if (DEBUG) {
Log.d(TAG, "Both expanded, and heads-up InflatedSmartReplies are null, "
+ "don't add smart replies.");
Log.d(TAG, "InflatedSmartReplies are null, don't add smart replies.");
}
return;
}
// The inflated smart-reply objects for the expanded view and the heads-up view both contain
// 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.
mCurrentSmartRepliesAndActions = mExpandedInflatedSmartReplies != null
? mExpandedInflatedSmartReplies.getSmartRepliesAndActions()
: mHeadsUpInflatedSmartReplies.getSmartRepliesAndActions();
if (DEBUG) {
Log.d(TAG, String.format("Adding suggestions for %s, %d actions, and %d replies.",
entry.getSbn().getKey(),
mCurrentSmartRepliesAndActions.smartActions == null ? 0 :
mCurrentSmartRepliesAndActions.smartActions.actions.size(),
mCurrentSmartRepliesAndActions.smartReplies == null ? 0 :
mCurrentSmartRepliesAndActions.smartReplies.choices.size()));
mCurrentSmartReplyState.getSmartActionsList().size(),
mCurrentSmartReplyState.getSmartRepliesList().size()));
}
applySmartReplyView(mCurrentSmartRepliesAndActions, entry);
applySmartReplyView(mCurrentSmartReplyState, entry);
}
private void applyRemoteInput(NotificationEntry entry, boolean hasFreeformRemoteInput) {
@@ -1415,41 +1407,74 @@ public class NotificationContentView extends FrameLayout {
}
private void applySmartReplyView(
SmartRepliesAndActions smartRepliesAndActions,
InflatedSmartReplyState state,
NotificationEntry entry) {
if (mContractedChild != null) {
applyExternalSmartReplyState(mContractedChild, state);
}
if (mExpandedChild != null) {
mExpandedSmartReplyView = applySmartReplyView(mExpandedChild, smartRepliesAndActions,
applyExternalSmartReplyState(mExpandedChild, state);
mExpandedSmartReplyView = applySmartReplyView(mExpandedChild, state,
entry, mExpandedInflatedSmartReplies);
if (mExpandedSmartReplyView != null) {
if (smartRepliesAndActions.smartReplies != null
|| smartRepliesAndActions.smartActions != null) {
int numSmartReplies = smartRepliesAndActions.smartReplies == null
? 0 : smartRepliesAndActions.smartReplies.choices.size();
int numSmartActions = smartRepliesAndActions.smartActions == null
? 0 : smartRepliesAndActions.smartActions.actions.size();
boolean fromAssistant = smartRepliesAndActions.smartReplies == null
? smartRepliesAndActions.smartActions.fromAssistant
: smartRepliesAndActions.smartReplies.fromAssistant;
boolean editBeforeSending = smartRepliesAndActions.smartReplies != null
SmartReplyView.SmartReplies smartReplies = state.getSmartReplies();
SmartReplyView.SmartActions smartActions = state.getSmartActions();
if (smartReplies != null || smartActions != null) {
int numSmartReplies = smartReplies == null ? 0 : smartReplies.choices.size();
int numSmartActions = smartActions == null ? 0 : smartActions.actions.size();
boolean fromAssistant = smartReplies == null
? smartActions.fromAssistant
: smartReplies.fromAssistant;
boolean editBeforeSending = smartReplies != null
&& mSmartReplyConstants.getEffectiveEditChoicesBeforeSending(
smartRepliesAndActions.smartReplies.remoteInput
.getEditChoicesBeforeSending());
smartReplies.remoteInput.getEditChoicesBeforeSending());
mSmartReplyController.smartSuggestionsAdded(entry, numSmartReplies,
numSmartActions, fromAssistant, editBeforeSending);
}
}
}
if (mHeadsUpChild != null && mSmartReplyConstants.getShowInHeadsUp()) {
mHeadsUpSmartReplyView = applySmartReplyView(mHeadsUpChild, smartRepliesAndActions,
entry, mHeadsUpInflatedSmartReplies);
if (mHeadsUpChild != null) {
applyExternalSmartReplyState(mHeadsUpChild, state);
if (mSmartReplyConstants.getShowInHeadsUp()) {
mHeadsUpSmartReplyView = applySmartReplyView(mHeadsUpChild, state,
entry, mHeadsUpInflatedSmartReplies);
}
}
}
private void applyExternalSmartReplyState(View view, InflatedSmartReplyState state) {
boolean hasPhishingAlert = state != null && state.getHasPhishingAction();
View phishingAlertIcon = view.findViewById(com.android.internal.R.id.phishing_alert);
if (phishingAlertIcon != null) {
if (DEBUG) {
Log.d(TAG, "Setting 'phishing_alert' view visible=" + hasPhishingAlert + ".");
}
phishingAlertIcon.setVisibility(hasPhishingAlert ? View.VISIBLE : View.GONE);
}
List<Integer> suppressedActionIndices = state != null
? state.getSuppressedActionIndices()
: Collections.emptyList();
ViewGroup actionsList = view.findViewById(com.android.internal.R.id.actions);
if (actionsList != null) {
if (DEBUG && !suppressedActionIndices.isEmpty()) {
Log.d(TAG, "Suppressing actions with indices: " + suppressedActionIndices);
}
for (int i = 0; i < actionsList.getChildCount(); i++) {
View actionBtn = actionsList.getChildAt(i);
Object actionIndex =
actionBtn.getTag(com.android.internal.R.id.notification_action_index_tag);
boolean suppressAction = actionIndex instanceof Integer
&& suppressedActionIndices.contains(actionIndex);
actionBtn.setVisibility(suppressAction ? View.GONE : View.VISIBLE);
}
}
}
@Nullable
private SmartReplyView applySmartReplyView(View view,
SmartRepliesAndActions smartRepliesAndActions,
NotificationEntry entry, InflatedSmartReplies inflatedSmartReplyView) {
InflatedSmartReplyState smartReplyState,
NotificationEntry entry, InflatedSmartReplyViewHolder inflatedSmartReplyViewHolder) {
View smartReplyContainerCandidate = view.findViewById(
com.android.internal.R.id.smart_reply_container);
if (!(smartReplyContainerCandidate instanceof LinearLayout)) {
@@ -1457,8 +1482,7 @@ public class NotificationContentView extends FrameLayout {
}
LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
if (!SmartRepliesAndActionsInflaterKt
.shouldShowSmartReplyView(entry, smartRepliesAndActions)) {
if (!SmartReplyStateInflaterKt.shouldShowSmartReplyView(entry, smartReplyState)) {
smartReplyContainer.setVisibility(View.GONE);
return null;
}
@@ -1471,15 +1495,15 @@ public class NotificationContentView extends FrameLayout {
smartReplyContainer.removeAllViews();
}
if (smartReplyContainer.getChildCount() == 0
&& inflatedSmartReplyView != null
&& inflatedSmartReplyView.getSmartReplyView() != null) {
smartReplyView = inflatedSmartReplyView.getSmartReplyView();
&& inflatedSmartReplyViewHolder != null
&& inflatedSmartReplyViewHolder.getSmartReplyView() != null) {
smartReplyView = inflatedSmartReplyViewHolder.getSmartReplyView();
smartReplyContainer.addView(smartReplyView);
}
if (smartReplyView != null) {
smartReplyView.resetSmartSuggestions(smartReplyContainer);
smartReplyView.addPreInflatedButtons(
inflatedSmartReplyView.getSmartSuggestionButtons());
inflatedSmartReplyViewHolder.getSmartSuggestionButtons());
// Ensure the colors of the smart suggestion buttons are up-to-date.
smartReplyView.setBackgroundTintColor(entry.getRow().getCurrentBackgroundTint());
smartReplyContainer.setVisibility(View.VISIBLE);
@@ -1495,7 +1519,7 @@ public class NotificationContentView extends FrameLayout {
* {@link SmartReplyView} related to the expanded notification state is cleared.
*/
public void setExpandedInflatedSmartReplies(
@Nullable InflatedSmartReplies inflatedSmartReplies) {
@Nullable InflatedSmartReplyViewHolder inflatedSmartReplies) {
mExpandedInflatedSmartReplies = inflatedSmartReplies;
if (inflatedSmartReplies == null) {
mExpandedSmartReplyView = null;
@@ -1510,18 +1534,29 @@ public class NotificationContentView extends FrameLayout {
* {@link SmartReplyView} related to the heads-up notification state is cleared.
*/
public void setHeadsUpInflatedSmartReplies(
@Nullable InflatedSmartReplies inflatedSmartReplies) {
@Nullable InflatedSmartReplyViewHolder inflatedSmartReplies) {
mHeadsUpInflatedSmartReplies = inflatedSmartReplies;
if (inflatedSmartReplies == null) {
mHeadsUpSmartReplyView = null;
}
}
/**
* Set pre-inflated replies and actions for the notification.
* This can be relevant to any state of the notification, even contracted, because smart actions
* may cause a phishing alert to be made visible.
* @param smartReplyState the pre-inflated list of replies and actions
*/
public void setInflatedSmartReplyState(
@NonNull InflatedSmartReplyState smartReplyState) {
mCurrentSmartReplyState = smartReplyState;
}
/**
* Returns the smart replies and actions currently shown in the notification.
*/
@Nullable public SmartRepliesAndActions getCurrentSmartRepliesAndActions() {
return mCurrentSmartRepliesAndActions;
@Nullable public InflatedSmartReplyState getCurrentSmartReplyState() {
return mCurrentSmartReplyState;
}
public void closeRemoteInput() {

View File

@@ -1,79 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.policy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.widget.Button;
import java.util.Collections;
import java.util.List;
/**
* Holder for inflated smart replies and actions. These objects should be inflated on a background
* thread, to later be accessed and modified on the (performance critical) UI thread.
*/
public class InflatedSmartReplies {
@Nullable private final SmartReplyView mSmartReplyView;
@Nullable private final List<Button> mSmartSuggestionButtons;
@NonNull private final SmartRepliesAndActions mSmartRepliesAndActions;
public InflatedSmartReplies(
@Nullable SmartReplyView smartReplyView,
@Nullable List<Button> smartSuggestionButtons,
@NonNull SmartRepliesAndActions smartRepliesAndActions) {
mSmartReplyView = smartReplyView;
mSmartSuggestionButtons = smartSuggestionButtons;
mSmartRepliesAndActions = smartRepliesAndActions;
}
@Nullable public SmartReplyView getSmartReplyView() {
return mSmartReplyView;
}
@Nullable public List<Button> getSmartSuggestionButtons() {
return mSmartSuggestionButtons;
}
@NonNull public SmartRepliesAndActions getSmartRepliesAndActions() {
return mSmartRepliesAndActions;
}
/**
* A storage for smart replies and smart action.
*/
public static class SmartRepliesAndActions {
@Nullable public final SmartReplyView.SmartReplies smartReplies;
@Nullable public final SmartReplyView.SmartActions smartActions;
SmartRepliesAndActions(
@Nullable SmartReplyView.SmartReplies smartReplies,
@Nullable SmartReplyView.SmartActions smartActions) {
this.smartReplies = smartReplies;
this.smartActions = smartActions;
}
@NonNull public List<CharSequence> getSmartReplies() {
return smartReplies == null ? Collections.emptyList() : smartReplies.choices;
}
@NonNull public List<Notification.Action> getSmartActions() {
return smartActions == null ? Collections.emptyList() : smartActions.actions;
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.policy
import android.app.Notification
import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions
import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies
/**
* A storage for smart replies, smart actions, and related state
*/
class InflatedSmartReplyState internal constructor(
val smartReplies: SmartReplies?,
val smartActions: SmartActions?,
val suppressedActions: SuppressedActions?,
val hasPhishingAction: Boolean
) {
val smartRepliesList: List<CharSequence>
get() = smartReplies?.choices ?: emptyList()
val smartActionsList: List<Notification.Action>
get() = smartActions?.actions ?: emptyList()
val suppressedActionIndices: List<Int>
get() = suppressedActions?.suppressedActionIndices ?: emptyList()
/**
* Data class for standard actions suppressed by the smart actions.
*/
class SuppressedActions(val suppressedActionIndices: List<Int>)
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.policy
import android.widget.Button
/**
* Holder for inflated smart replies and actions. These objects should be inflated on a background
* thread, to later be accessed and modified on the (performance critical) UI thread.
*/
class InflatedSmartReplyViewHolder(
val smartReplyView: SmartReplyView?,
val smartSuggestionButtons: List<Button>?
)

View File

@@ -44,7 +44,7 @@ import com.android.systemui.statusbar.SmartReplyController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logging.NotificationLogger
import com.android.systemui.statusbar.phone.KeyguardDismissUtil
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions
import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedActions
import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions
import com.android.systemui.statusbar.policy.SmartReplyView.SmartButtonType
import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies
@@ -53,10 +53,10 @@ import javax.inject.Inject
/** Returns whether we should show the smart reply view and its smart suggestions. */
fun shouldShowSmartReplyView(
entry: NotificationEntry,
smartRepliesAndActions: SmartRepliesAndActions
smartReplyState: InflatedSmartReplyState
): Boolean {
if (smartRepliesAndActions.smartReplies == null
&& smartRepliesAndActions.smartActions == null) {
if (smartReplyState.smartReplies == null &&
smartReplyState.smartActions == null) {
// There are no smart replies and no smart actions.
return false
}
@@ -71,58 +71,65 @@ fun shouldShowSmartReplyView(
.getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)
}
/** Determines if two [SmartRepliesAndActions] are visually similar. */
/** Determines if two [InflatedSmartReplyState] are visually similar. */
fun areSuggestionsSimilar(
left: SmartRepliesAndActions?,
right: SmartRepliesAndActions?
left: InflatedSmartReplyState?,
right: InflatedSmartReplyState?
): Boolean = when {
left === right -> true
left == null || right == null -> false
left.getSmartReplies() != right.getSmartReplies() -> false
else -> !NotificationUiAdjustment.areDifferent(left.getSmartActions(), right.getSmartActions())
left.hasPhishingAction != right.hasPhishingAction -> false
left.smartRepliesList != right.smartRepliesList -> false
left.suppressedActionIndices != right.suppressedActionIndices -> false
else -> !NotificationUiAdjustment.areDifferent(left.smartActionsList, right.smartActionsList)
}
interface SmartRepliesAndActionsInflater {
fun inflateSmartReplies(
interface SmartReplyStateInflater {
fun inflateSmartReplyState(entry: NotificationEntry): InflatedSmartReplyState
fun inflateSmartReplyViewHolder(
sysuiContext: Context,
notifPackageContext: Context,
entry: NotificationEntry,
existingRepliesAndAction: SmartRepliesAndActions?
): InflatedSmartReplies
existingSmartReplyState: InflatedSmartReplyState?,
newSmartReplyState: InflatedSmartReplyState
): InflatedSmartReplyViewHolder
}
/*internal*/ class SmartRepliesAndActionsInflaterImpl @Inject constructor(
/*internal*/ class SmartReplyStateInflaterImpl @Inject constructor(
private val constants: SmartReplyConstants,
private val activityManagerWrapper: ActivityManagerWrapper,
private val packageManagerWrapper: PackageManagerWrapper,
private val devicePolicyManagerWrapper: DevicePolicyManagerWrapper,
private val smartRepliesInflater: SmartReplyInflater,
private val smartActionsInflater: SmartActionInflater
) : SmartRepliesAndActionsInflater {
) : SmartReplyStateInflater {
override fun inflateSmartReplies(
override fun inflateSmartReplyState(entry: NotificationEntry): InflatedSmartReplyState =
chooseSmartRepliesAndActions(entry)
override fun inflateSmartReplyViewHolder(
sysuiContext: Context,
notifPackageContext: Context,
entry: NotificationEntry,
existingRepliesAndAction: SmartRepliesAndActions?
): InflatedSmartReplies {
val newRepliesAndActions = chooseSmartRepliesAndActions(entry)
if (!shouldShowSmartReplyView(entry, newRepliesAndActions)) {
return InflatedSmartReplies(
existingSmartReplyState: InflatedSmartReplyState?,
newSmartReplyState: InflatedSmartReplyState
): InflatedSmartReplyViewHolder {
if (!shouldShowSmartReplyView(entry, newSmartReplyState)) {
return InflatedSmartReplyViewHolder(
null /* smartReplyView */,
null /* smartSuggestionButtons */,
newRepliesAndActions)
null /* smartSuggestionButtons */)
}
// 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.
val delayOnClickListener =
!areSuggestionsSimilar(existingRepliesAndAction, newRepliesAndActions)
!areSuggestionsSimilar(existingSmartReplyState, newSmartReplyState)
val smartReplyView = SmartReplyView.inflate(sysuiContext, constants)
val smartReplies = newRepliesAndActions.smartReplies
val smartReplies = newSmartReplyState.smartReplies
smartReplyView.setSmartRepliesGeneratedByAssistant(smartReplies?.fromAssistant ?: false)
val smartReplyButtons = smartReplies?.let {
smartReplies.choices.asSequence().mapIndexed { index, choice ->
@@ -136,7 +143,7 @@ interface SmartRepliesAndActionsInflater {
}
} ?: emptySequence()
val smartActionButtons = newRepliesAndActions.smartActions?.let { smartActions ->
val smartActionButtons = newSmartReplyState.smartActions?.let { smartActions ->
val themedPackageContext =
ContextThemeWrapper(notifPackageContext, sysuiContext.theme)
smartActions.actions.asSequence()
@@ -153,10 +160,9 @@ interface SmartRepliesAndActionsInflater {
}
} ?: emptySequence()
return InflatedSmartReplies(
return InflatedSmartReplyViewHolder(
smartReplyView,
(smartReplyButtons + smartActionButtons).toList(),
newRepliesAndActions)
(smartReplyButtons + smartActionButtons).toList())
}
/**
@@ -165,23 +171,23 @@ interface SmartRepliesAndActionsInflater {
* replies or actions generated by the NotificationAssistantService (NAS), and if the app
* provides any smart actions we also don't show any NAS-generated replies or actions.
*/
fun chooseSmartRepliesAndActions(entry: NotificationEntry): SmartRepliesAndActions {
fun chooseSmartRepliesAndActions(entry: NotificationEntry): InflatedSmartReplyState {
val notification = entry.sbn.notification
val remoteInputActionPair = notification.findRemoteInputActionPair(false /* freeform */)
val freeformRemoteInputActionPair =
notification.findRemoteInputActionPair(true /* freeform */)
if (!constants.isEnabled) {
if (DEBUG) {
Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for "
+ entry.sbn.key)
Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for " +
entry.sbn.key)
}
return SmartRepliesAndActions(null, null)
return InflatedSmartReplyState(null, null, null, false)
}
// Only use smart replies from the app if they target P or above. We have this check because
// the smart reply API has been used for other things (Wearables) in the past. The API to
// add smart actions is new in Q so it doesn't require a target-sdk check.
val enableAppGeneratedSmartReplies = (!constants.requiresTargetingP()
|| entry.targetSdk >= Build.VERSION_CODES.P)
val enableAppGeneratedSmartReplies = (!constants.requiresTargetingP() ||
entry.targetSdk >= Build.VERSION_CODES.P)
val appGeneratedSmartActions = notification.contextualActions
var smartReplies: SmartReplies? = when {
@@ -207,18 +213,18 @@ interface SmartRepliesAndActionsInflater {
if (smartReplies == null && smartActions == null) {
val entryReplies = entry.smartReplies
val entryActions = entry.smartActions
if (entryReplies.isNotEmpty()
&& freeformRemoteInputActionPair != null
&& freeformRemoteInputActionPair.second.allowGeneratedReplies
&& freeformRemoteInputActionPair.second.actionIntent != null) {
if (entryReplies.isNotEmpty() &&
freeformRemoteInputActionPair != null &&
freeformRemoteInputActionPair.second.allowGeneratedReplies &&
freeformRemoteInputActionPair.second.actionIntent != null) {
smartReplies = SmartReplies(
entryReplies,
freeformRemoteInputActionPair.first,
freeformRemoteInputActionPair.second.actionIntent,
true /* fromAssistant */)
}
if (entryActions.isNotEmpty()
&& notification.allowSystemGeneratedContextualActions) {
if (entryActions.isNotEmpty() &&
notification.allowSystemGeneratedContextualActions) {
val systemGeneratedActions: List<Notification.Action> = when {
activityManagerWrapper.isLockTaskKioskModeActive ->
// Filter actions if we're in kiosk-mode - we don't care about screen
@@ -229,7 +235,21 @@ interface SmartRepliesAndActionsInflater {
smartActions = SmartActions(systemGeneratedActions, true /* fromAssistant */)
}
}
return SmartRepliesAndActions(smartReplies, smartActions)
val hasPhishingAction = smartActions?.actions?.any {
it.isContextual && it.semanticAction ==
Notification.Action.SEMANTIC_ACTION_CONVERSATION_IS_PHISHING
} ?: false
var suppressedActions: SuppressedActions? = null
if (hasPhishingAction) {
// If there is a phishing action, calculate the indices of the actions with RemoteInput
// as those need to be hidden from the view.
val suppressedActionIndices = notification.actions.mapIndexedNotNull { index, action ->
if (action.remoteInputs?.isNotEmpty() == true) index else null
}
suppressedActions = SuppressedActions(suppressedActionIndices)
}
return InflatedSmartReplyState(smartReplies, smartActions, suppressedActions,
hasPhishingAction)
}
/**
@@ -311,8 +331,8 @@ interface SmartActionInflater {
actionIndex: Int,
action: Notification.Action
) =
if (smartActions.fromAssistant
&& SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == action.semanticAction) {
if (smartActions.fromAssistant &&
SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY == action.semanticAction) {
entry.row.doSmartActionClick(entry.row.x.toInt() / 2,
entry.row.y.toInt() / 2, SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY)
smartReplyController

View File

@@ -794,8 +794,8 @@ public class SmartReplyView extends ViewGroup {
public final List<CharSequence> choices;
public final boolean fromAssistant;
public SmartReplies(List<CharSequence> choices, RemoteInput remoteInput,
PendingIntent pendingIntent, boolean fromAssistant) {
public SmartReplies(@NonNull List<CharSequence> choices, @NonNull RemoteInput remoteInput,
@NonNull PendingIntent pendingIntent, boolean fromAssistant) {
this.choices = choices;
this.remoteInput = remoteInput;
this.pendingIntent = pendingIntent;
@@ -812,7 +812,7 @@ public class SmartReplyView extends ViewGroup {
public final List<Notification.Action> actions;
public final boolean fromAssistant;
public SmartActions(List<Notification.Action> actions, boolean fromAssistant) {
public SmartActions(@NonNull List<Notification.Action> actions, boolean fromAssistant) {
this.actions = actions;
this.fromAssistant = fromAssistant;
}

View File

@@ -17,10 +17,10 @@ package com.android.systemui.statusbar.policy.dagger
import com.android.systemui.statusbar.policy.SmartActionInflater
import com.android.systemui.statusbar.policy.SmartActionInflaterImpl
import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater
import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflaterImpl
import com.android.systemui.statusbar.policy.SmartReplyInflater
import com.android.systemui.statusbar.policy.SmartReplyInflaterImpl
import com.android.systemui.statusbar.policy.SmartReplyStateInflater
import com.android.systemui.statusbar.policy.SmartReplyStateInflaterImpl
import dagger.Binds
import dagger.Module
@@ -29,6 +29,6 @@ interface SmartRepliesInflationModule {
@Binds fun bindSmartActionsInflater(impl: SmartActionInflaterImpl): SmartActionInflater
@Binds fun bindSmartReplyInflater(impl: SmartReplyInflaterImpl): SmartReplyInflater
@Binds fun bindsInflatedSmartRepliesProvider(
impl: SmartRepliesAndActionsInflaterImpl
): SmartRepliesAndActionsInflater
impl: SmartReplyStateInflaterImpl
): SmartReplyStateInflater
}

View File

@@ -58,8 +58,9 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
import com.android.systemui.tests.R;
import org.junit.Assert;
@@ -87,11 +88,24 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
@Mock private NotifRemoteViewCache mCache;
@Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
@Mock private InflatedSmartReplies mInflatedSmartReplies;
@Mock private InflatedSmartReplyState mInflatedSmartReplyState;
@Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies;
private final SmartRepliesAndActionsInflater mSmartRepliesAndActionsInflater =
(sysuiContext, notifPackageContext, entry, existingRepliesAndAction) ->
mInflatedSmartReplies;
private final SmartReplyStateInflater mSmartReplyStateInflater =
new SmartReplyStateInflater() {
@Override
public InflatedSmartReplyViewHolder inflateSmartReplyViewHolder(
Context sysuiContext, Context notifPackageContext, NotificationEntry entry,
InflatedSmartReplyState existingSmartReplyState,
InflatedSmartReplyState newSmartReplyState) {
return mInflatedSmartReplies;
}
@Override
public InflatedSmartReplyState inflateSmartReplyState(NotificationEntry entry) {
return mInflatedSmartReplyState;
}
};
@Before
public void setUp() throws Exception {
@@ -114,7 +128,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
mConversationNotificationProcessor,
mock(MediaFeatureFlag.class),
mock(Executor.class),
mSmartRepliesAndActionsInflater);
mSmartReplyStateInflater);
}
@Test
@@ -130,7 +144,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
FLAG_CONTENT_VIEW_ALL,
builder,
mContext,
mSmartRepliesAndActionsInflater);
mSmartReplyStateInflater);
verify(builder).createHeadsUpContentView(true);
}
@@ -147,7 +161,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
FLAG_CONTENT_VIEW_ALL,
builder,
mContext,
mSmartRepliesAndActionsInflater);
mSmartReplyStateInflater);
verify(builder).createContentView(true);
}

View File

@@ -29,6 +29,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.os.Handler;
import android.service.notification.NotificationListenerService;
@@ -80,7 +81,9 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationRowCom
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.time.FakeSystemClock;
@@ -140,7 +143,8 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
@Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
@Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Mock private InflatedSmartReplies mInflatedSmartReplies;
@Mock private InflatedSmartReplyState mInflatedSmartReplyState;
@Mock private InflatedSmartReplyViewHolder mInflatedSmartReplies;
private StatusBarNotification mSbn;
private NotificationListenerService.RankingMap mRankingMap;
@@ -206,8 +210,21 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
mock(ConversationNotificationProcessor.class),
mock(MediaFeatureFlag.class),
mBgExecutor,
(sysuiContext, notifPackageContext, entry, existingRepliesAndAction) ->
mInflatedSmartReplies);
new SmartReplyStateInflater() {
@Override
public InflatedSmartReplyState inflateSmartReplyState(NotificationEntry entry) {
return mInflatedSmartReplyState;
}
@Override
public InflatedSmartReplyViewHolder inflateSmartReplyViewHolder(
Context sysuiContext, Context notifPackageContext,
NotificationEntry entry,
InflatedSmartReplyState existingSmartReplyState,
InflatedSmartReplyState newSmartReplyState) {
return mInflatedSmartReplies;
}
});
mRowContentBindStage = new RowContentBindStage(
binder,
mock(NotifInflationErrorManager.class),

View File

@@ -66,7 +66,9 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState;
import com.android.systemui.statusbar.policy.InflatedSmartReplyViewHolder;
import com.android.systemui.statusbar.policy.SmartReplyStateInflater;
import com.android.systemui.tests.R;
import com.android.systemui.wmshell.BubblesManager;
import com.android.systemui.wmshell.BubblesTestActivity;
@@ -139,8 +141,7 @@ public class NotificationTestHelper {
mock(ConversationNotificationProcessor.class),
mock(MediaFeatureFlag.class),
mock(Executor.class),
(sysuiContext, notifPackageContext, entry, existingRepliesAndAction) ->
mock(InflatedSmartReplies.class));
new MockSmartReplyInflater());
contentBinder.setInflateSynchronously(true);
mBindStage = new RowContentBindStage(contentBinder,
mock(NotifInflationErrorManager.class),
@@ -464,4 +465,19 @@ public class NotificationTestHelper {
.setDesiredHeight(314)
.build();
}
private static class MockSmartReplyInflater implements SmartReplyStateInflater {
@Override
public InflatedSmartReplyState inflateSmartReplyState(NotificationEntry entry) {
return mock(InflatedSmartReplyState.class);
}
@Override
public InflatedSmartReplyViewHolder inflateSmartReplyViewHolder(Context sysuiContext,
Context notifPackageContext, NotificationEntry entry,
InflatedSmartReplyState existingSmartReplyState,
InflatedSmartReplyState newSmartReplyState) {
return mock(InflatedSmartReplyViewHolder.class);
}
}
}

View File

@@ -45,7 +45,7 @@ import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.statusbar.NotificationEntryHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
import com.android.systemui.statusbar.policy.InflatedSmartReplyState.SuppressedActions;
import com.android.systemui.statusbar.policy.SmartReplyView.SmartActions;
import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies;
@@ -74,13 +74,12 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
@Mock private ActivityManagerWrapper mActivityManagerWrapper;
@Mock private PackageManagerWrapper mPackageManagerWrapper;
@Mock private DevicePolicyManagerWrapper mDevicePolicyManagerWrapper;
@Mock private SmartRepliesAndActions mSmartRepliesAndActions;
@Mock private SmartReplyInflater mSmartReplyInflater;
@Mock private SmartActionInflater mSmartActionInflater;
private Icon mActionIcon;
private NotificationEntry mEntry;
private SmartRepliesAndActionsInflaterImpl mSmartRepliesInflater;
private SmartReplyStateInflaterImpl mSmartReplyStateInflater;
@Before
@UiThreadTest
@@ -101,7 +100,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(false);
mSmartRepliesInflater = new SmartRepliesAndActionsInflaterImpl(
mSmartReplyStateInflater = new SmartReplyStateInflaterImpl(
mSmartReplyConstants,
mActivityManagerWrapper,
mPackageManagerWrapper,
@@ -118,11 +117,13 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
setupAppGeneratedSuggestions(smartReplies, smartActions);
when(mSmartReplyConstants.isEnabled()).thenReturn(false);
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions).isNull();
assertThat(smartReplyState.getSmartReplies()).isNull();
assertThat(smartReplyState.getSmartActions()).isNull();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -134,11 +135,13 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
when(mSmartReplyConstants.isEnabled()).thenReturn(false);
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions).isNull();
assertThat(smartReplyState.getSmartReplies()).isNull();
assertThat(smartReplyState.getSmartActions()).isNull();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -146,12 +149,15 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"};
setupAppGeneratedReplies(smartReplies);
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(Arrays.asList(smartReplies));
assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
assertThat(repliesAndActions.smartActions).isNull();
assertThat(smartReplyState.getSmartReplies().choices)
.containsExactlyElementsIn(smartReplies).inOrder();
assertThat(smartReplyState.getSmartReplies().fromAssistant).isFalse();
assertThat(smartReplyState.getSmartActions()).isNull();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -161,13 +167,17 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
createActions("Test Action 1", "Test Action 2");
setupAppGeneratedSuggestions(smartReplies, smartActions);
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(Arrays.asList(smartReplies));
assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
assertThat(repliesAndActions.smartActions.actions).isEqualTo(smartActions);
assertThat(repliesAndActions.smartActions.fromAssistant).isFalse();
assertThat(smartReplyState.getSmartReplies().choices)
.containsExactlyElementsIn(smartReplies).inOrder();
assertThat(smartReplyState.getSmartReplies().fromAssistant).isFalse();
assertThat(smartReplyState.getSmartActions().actions)
.containsExactlyElementsIn(smartActions).inOrder();
assertThat(smartReplyState.getSmartActions().fromAssistant).isFalse();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -180,12 +190,15 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
.build();
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(mEntry.getSmartReplies());
assertThat(repliesAndActions.smartReplies.fromAssistant).isTrue();
assertThat(repliesAndActions.smartActions).isNull();
assertThat(smartReplyState.getSmartReplies().choices)
.containsExactlyElementsIn(mEntry.getSmartReplies()).inOrder();
assertThat(smartReplyState.getSmartReplies().fromAssistant).isTrue();
assertThat(smartReplyState.getSmartActions()).isNull();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -197,11 +210,13 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
NotificationEntryHelper.modifyRanking(mEntry)
.setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
.build();
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions).isNull();
assertThat(smartReplyState.getSmartReplies()).isNull();
assertThat(smartReplyState.getSmartActions()).isNull();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -214,13 +229,50 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
.build();
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions.actions)
.isEqualTo(mEntry.getSmartActions());
assertThat(repliesAndActions.smartActions.fromAssistant).isTrue();
assertThat(smartReplyState.getSmartReplies()).isNull();
assertThat(smartReplyState.getSmartActions().actions)
.containsExactlyElementsIn(mEntry.getSmartActions()).inOrder();
assertThat(smartReplyState.getSmartActions().fromAssistant).isTrue();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
public void chooseSmartRepliesAndActions_sysGeneratedPhishingSmartAction() {
// Pass a null-array as app-generated smart replies, so that we use NAS-generated smart
// actions.
setupAppGeneratedReplies(null /* smartReplies */);
mNotification.actions = new Notification.Action[]{
createAction("Details"),
createActionBuilder("Reply").addRemoteInput(
new RemoteInput.Builder("key").build()).build()
};
modifyRanking(mEntry)
.setSmartActions(
createAction("Sys Smart Action 1"),
createActionBuilder("Sys Smart Action 2")
.setContextual(true)
.setSemanticAction(Notification.Action
.SEMANTIC_ACTION_CONVERSATION_IS_PHISHING)
.build())
.build();
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(smartReplyState.getSmartReplies()).isNull();
assertThat(smartReplyState.getSmartActions().actions)
.containsExactlyElementsIn(mEntry.getSmartActions()).inOrder();
assertThat(smartReplyState.getSmartActions().fromAssistant).isTrue();
assertThat(smartReplyState.getSuppressedActions()).isNotNull();
assertThat(smartReplyState.getSuppressedActions().getSuppressedActionIndices())
.containsExactly(1);
assertThat(smartReplyState.getHasPhishingAction()).isTrue();
}
@Test
@@ -237,14 +289,17 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
.build();
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices)
.isEqualTo(Arrays.asList(appGenSmartReplies));
assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
assertThat(repliesAndActions.smartActions.actions).isEqualTo(appGenSmartActions);
assertThat(repliesAndActions.smartActions.fromAssistant).isFalse();
assertThat(smartReplyState.getSmartReplies().choices)
.containsExactlyElementsIn(Arrays.asList(appGenSmartReplies)).inOrder();
assertThat(smartReplyState.getSmartReplies().fromAssistant).isFalse();
assertThat(smartReplyState.getSmartActions().actions)
.containsExactlyElementsIn(appGenSmartActions).inOrder();
assertThat(smartReplyState.getSmartActions().fromAssistant).isFalse();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -259,11 +314,13 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
.build();
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartActions).isNull();
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(smartReplyState.getSmartActions()).isNull();
assertThat(smartReplyState.getSmartReplies()).isNull();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -281,13 +338,15 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
.build();
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
mEntry.getSmartReplies());
assertThat(smartReplyState.getSmartReplies().choices)
.containsExactlyElementsIn(mEntry.getSmartReplies()).inOrder();
// Since no apps are whitelisted no actions should be shown.
assertThat(repliesAndActions.smartActions.actions).isEmpty();
assertThat(smartReplyState.getSmartActions().actions).isEmpty();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -317,13 +376,14 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.setSmartActions(actions)
.build();
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
// Only the action for the whitelisted package should be allowed.
assertThat(repliesAndActions.smartActions.actions.size()).isEqualTo(1);
assertThat(repliesAndActions.smartActions.actions.get(0)).isEqualTo(
mEntry.getSmartActions().get(0));
assertThat(smartReplyState.getSmartActions().actions)
.containsExactly(mEntry.getSmartActions().get(0));
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -340,14 +400,16 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
.build();
SmartRepliesAndActions repliesAndActions =
mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
InflatedSmartReplyState smartReplyState =
mSmartReplyStateInflater.chooseSmartRepliesAndActions(mEntry);
// We don't restrict replies or actions in screen pinning mode.
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
mEntry.getSmartReplies());
assertThat(repliesAndActions.smartActions.actions).isEqualTo(
mEntry.getSmartActions());
assertThat(smartReplyState.getSmartReplies().choices)
.containsExactlyElementsIn(mEntry.getSmartReplies()).inOrder();
assertThat(smartReplyState.getSmartActions().actions)
.containsExactlyElementsIn(mEntry.getSmartActions()).inOrder();
assertThat(smartReplyState.getSuppressedActions()).isNull();
assertThat(smartReplyState.getHasPhishingAction()).isFalse();
}
@Test
@@ -360,17 +422,24 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
List<Notification.Action> rightActions = Arrays.asList(
createAction("firstAction"),
createAction("secondAction"));
List<Integer> leftSuppressed = Arrays.asList(1, 2);
List<Integer> rightSuppressed = Arrays.asList(1, 2);
boolean leftPhishing = true;
boolean rightPhishing = true;
SmartRepliesAndActions leftRepliesAndActions = new SmartRepliesAndActions(
InflatedSmartReplyState leftRepliesAndActions = new InflatedSmartReplyState(
new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
new SmartActions(leftActions, false /* fromAssistant */));
SmartRepliesAndActions rightRepliesAndActions = new SmartRepliesAndActions(
new SmartActions(leftActions, false /* fromAssistant */),
new SuppressedActions(leftSuppressed),
leftPhishing);
InflatedSmartReplyState rightRepliesAndActions = new InflatedSmartReplyState(
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */));
new SmartActions(rightActions, false /* fromAssistant */),
new SuppressedActions(rightSuppressed),
rightPhishing);
assertThat(
SmartRepliesAndActionsInflaterKt
.areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
assertThat(SmartReplyStateInflaterKt
.areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
.isTrue();
}
@@ -384,16 +453,25 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
List<Notification.Action> rightActions = Arrays.asList(
createAction("firstAction"),
createAction("secondAction"));
List<Integer> leftSuppressed = Arrays.asList(1, 2);
List<Integer> rightSuppressed = Arrays.asList(1, 2);
boolean leftPhishing = true;
boolean rightPhishing = true;
SmartRepliesAndActions leftRepliesAndActions = new SmartRepliesAndActions(
InflatedSmartReplyState leftRepliesAndActions = new InflatedSmartReplyState(
new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
new SmartActions(leftActions, false /* fromAssistant */));
SmartRepliesAndActions rightRepliesAndActions = new SmartRepliesAndActions(
new SmartActions(leftActions, false /* fromAssistant */),
new SuppressedActions(leftSuppressed),
leftPhishing);
InflatedSmartReplyState rightRepliesAndActions = new InflatedSmartReplyState(
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */));
new SmartActions(rightActions, false /* fromAssistant */),
new SuppressedActions(rightSuppressed),
rightPhishing);
assertThat(SmartRepliesAndActionsInflaterKt.areSuggestionsSimilar(
leftRepliesAndActions, rightRepliesAndActions)).isFalse();
assertThat(SmartReplyStateInflaterKt
.areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
.isFalse();
}
@Test
@@ -406,16 +484,87 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
List<Notification.Action> rightActions = Arrays.asList(
createAction("firstAction"),
createAction("not secondAction"));
List<Integer> leftSuppressed = Arrays.asList(1, 2);
List<Integer> rightSuppressed = Arrays.asList(1, 2);
boolean leftPhishing = true;
boolean rightPhishing = true;
SmartRepliesAndActions leftRepliesAndActions = new SmartRepliesAndActions(
InflatedSmartReplyState leftRepliesAndActions = new InflatedSmartReplyState(
new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
new SmartActions(leftActions, false /* fromAssistant */));
SmartRepliesAndActions rightRepliesAndActions = new SmartRepliesAndActions(
new SmartActions(leftActions, false /* fromAssistant */),
new SuppressedActions(leftSuppressed),
leftPhishing);
InflatedSmartReplyState rightRepliesAndActions = new InflatedSmartReplyState(
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */));
new SmartActions(rightActions, false /* fromAssistant */),
new SuppressedActions(rightSuppressed),
rightPhishing);
assertThat(SmartRepliesAndActionsInflaterKt.areSuggestionsSimilar(
leftRepliesAndActions, rightRepliesAndActions)).isFalse();
assertThat(SmartReplyStateInflaterKt
.areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
.isFalse();
}
@Test
public void areSuggestionsSimilar_falseForDifferentSuppressedActions() {
List<CharSequence> leftReplies = createReplies("first reply", "second reply");
List<CharSequence> rightReplies = createReplies("first reply", "second reply");
List<Notification.Action> leftActions = Arrays.asList(
createAction("firstAction"),
createAction("secondAction"));
List<Notification.Action> rightActions = Arrays.asList(
createAction("firstAction"),
createAction("secondAction"));
List<Integer> leftSuppressed = Arrays.asList(1, 2);
List<Integer> rightSuppressed = Arrays.asList(1, 3);
boolean leftPhishing = true;
boolean rightPhishing = true;
InflatedSmartReplyState leftRepliesAndActions = new InflatedSmartReplyState(
new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
new SmartActions(leftActions, false /* fromAssistant */),
new SuppressedActions(leftSuppressed),
leftPhishing);
InflatedSmartReplyState rightRepliesAndActions = new InflatedSmartReplyState(
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */),
new SuppressedActions(rightSuppressed),
rightPhishing);
assertThat(SmartReplyStateInflaterKt
.areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
.isFalse();
}
@Test
public void areSuggestionsSimilar_falseForDifferentPhishing() {
List<CharSequence> leftReplies = createReplies("first reply", "second reply");
List<CharSequence> rightReplies = createReplies("first reply", "second reply");
List<Notification.Action> leftActions = Arrays.asList(
createAction("firstAction"),
createAction("secondAction"));
List<Notification.Action> rightActions = Arrays.asList(
createAction("firstAction"),
createAction("secondAction"));
List<Integer> leftSuppressed = Arrays.asList(1, 2);
List<Integer> rightSuppressed = Arrays.asList(1, 2);
boolean leftPhishing = true;
boolean rightPhishing = false;
InflatedSmartReplyState leftRepliesAndActions = new InflatedSmartReplyState(
new SmartReplies(leftReplies, null, null, false /* fromAssistant */),
new SmartActions(leftActions, false /* fromAssistant */),
new SuppressedActions(leftSuppressed),
leftPhishing);
InflatedSmartReplyState rightRepliesAndActions = new InflatedSmartReplyState(
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */),
new SuppressedActions(rightSuppressed),
rightPhishing);
assertThat(SmartReplyStateInflaterKt
.areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
.isFalse();
}
private void setupAppGeneratedReplies(CharSequence[] smartReplies) {