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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>)
|
||||
}
|
||||
@@ -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>?
|
||||
)
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user