summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java223
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt462
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java316
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java202
13 files changed, 841 insertions, 645 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 90dc213ce6e6..63d9a831b33f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfC
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.dagger.SmartRepliesInflationModule;
import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
import com.android.systemui.tuner.dagger.TunerModule;
import com.android.systemui.user.UserModule;
@@ -74,6 +75,7 @@ import dagger.Provides;
SensorModule.class,
SettingsModule.class,
SettingsUtilModule.class,
+ SmartRepliesInflationModule.class,
StatusBarPolicyModule.class,
SysUIConcurrencyModule.class,
TunerModule.class,
@@ -81,13 +83,15 @@ import dagger.Provides;
UtilModule.class,
VolumeModule.class
},
- subcomponents = {StatusBarComponent.class,
- NotificationRowComponent.class,
- DozeComponent.class,
- ExpandableNotificationRowComponent.class,
- KeyguardBouncerComponent.class,
- NotificationShelfComponent.class,
- FragmentService.FragmentCreator.class})
+ subcomponents = {
+ StatusBarComponent.class,
+ NotificationRowComponent.class,
+ DozeComponent.class,
+ ExpandableNotificationRowComponent.class,
+ KeyguardBouncerComponent.class,
+ NotificationShelfComponent.class,
+ FragmentService.FragmentCreator.class
+ })
public abstract class SystemUIModule {
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 1d72557c6a89..c995e324ecfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -81,21 +81,27 @@ public class ExpandableNotificationRowController implements NodeController {
private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
@Inject
- public ExpandableNotificationRowController(ExpandableNotificationRow view,
+ public ExpandableNotificationRowController(
+ ExpandableNotificationRow view,
NotificationListContainer listContainer,
ActivatableNotificationViewController activatableNotificationViewController,
- NotificationMediaManager mediaManager, PluginManager pluginManager,
- SystemClock clock, @AppName String appName, @NotificationKey String notificationKey,
+ NotificationMediaManager mediaManager,
+ PluginManager pluginManager,
+ SystemClock clock,
+ @AppName String appName,
+ @NotificationKey String notificationKey,
KeyguardBypassController keyguardBypassController,
GroupMembershipManager groupMembershipManager,
GroupExpansionManager groupExpansionManager,
RowContentBindStage rowContentBindStage,
- NotificationLogger notificationLogger, HeadsUpManager headsUpManager,
+ NotificationLogger notificationLogger,
+ HeadsUpManager headsUpManager,
ExpandableNotificationRow.OnExpandClickListener onExpandClickListener,
StatusBarStateController statusBarStateController,
NotificationGutsManager notificationGutsManager,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
- OnUserInteractionCallback onUserInteractionCallback, FalsingManager falsingManager,
+ OnUserInteractionCallback onUserInteractionCallback,
+ FalsingManager falsingManager,
PeopleNotificationIdentifier peopleNotificationIdentifier) {
mView = view;
mListContainer = listContainer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 9bcac1163acc..c2c4590fa6cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -42,17 +42,15 @@ import com.android.systemui.media.MediaDataManagerKt;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.InflationException;
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.HeadsUpManager;
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
-import com.android.systemui.statusbar.policy.SmartReplyConstants;
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
import com.android.systemui.util.Assert;
import java.util.HashMap;
@@ -60,8 +58,6 @@ import java.util.concurrent.Executor;
import javax.inject.Inject;
-import dagger.Lazy;
-
/**
* {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
* asynchronously building the content's {@link RemoteViews} and applying it to the row.
@@ -76,27 +72,24 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final boolean mIsMediaInQS;
private final NotificationRemoteInputManager mRemoteInputManager;
private final NotifRemoteViewCache mRemoteViewCache;
- private final Lazy<SmartReplyConstants> mSmartReplyConstants;
- private final Lazy<SmartReplyController> mSmartReplyController;
private final ConversationNotificationProcessor mConversationProcessor;
private final Executor mBgExecutor;
+ private final SmartRepliesAndActionsInflater mSmartRepliesAndActionsInflater;
@Inject
NotificationContentInflater(
NotifRemoteViewCache remoteViewCache,
NotificationRemoteInputManager remoteInputManager,
- Lazy<SmartReplyConstants> smartReplyConstants,
- Lazy<SmartReplyController> smartReplyController,
ConversationNotificationProcessor conversationProcessor,
MediaFeatureFlag mediaFeatureFlag,
- @Background Executor bgExecutor) {
+ @Background Executor bgExecutor,
+ SmartRepliesAndActionsInflater smartRepliesInflater) {
mRemoteViewCache = remoteViewCache;
mRemoteInputManager = remoteInputManager;
- mSmartReplyConstants = smartReplyConstants;
- mSmartReplyController = smartReplyController;
mConversationProcessor = conversationProcessor;
mIsMediaInQS = mediaFeatureFlag.getEnabled();
mBgExecutor = bgExecutor;
+ mSmartRepliesAndActionsInflater = smartRepliesInflater;
}
@Override
@@ -132,8 +125,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
contentToBind,
mRemoteViewCache,
entry,
- mSmartReplyConstants.get(),
- mSmartReplyController.get(),
mConversationProcessor,
row,
bindParams.isLowPriority,
@@ -141,7 +132,8 @@ public class NotificationContentInflater implements NotificationRowContentBinder
bindParams.usesIncreasedHeadsUpHeight,
callback,
mRemoteInputManager.getRemoteViewsOnClickHandler(),
- mIsMediaInQS);
+ mIsMediaInQS,
+ mSmartRepliesAndActionsInflater);
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
@@ -157,17 +149,19 @@ public class NotificationContentInflater implements NotificationRowContentBinder
boolean inflateSynchronously,
@InflationFlag int reInflateFlags,
Notification.Builder builder,
- Context packageContext) {
+ Context packageContext,
+ SmartRepliesAndActionsInflater smartRepliesInflater) {
InflationProgress result = createRemoteViews(reInflateFlags,
builder,
bindParams.isLowPriority,
bindParams.usesIncreasedHeight,
bindParams.usesIncreasedHeadsUpHeight,
packageContext);
+
result = inflateSmartReplyViews(result, reInflateFlags, entry,
- row.getContext(), packageContext, row.getHeadsUpManager(),
- mSmartReplyConstants.get(), mSmartReplyController.get(),
- row.getExistingSmartRepliesAndActions());
+ row.getContext(), packageContext,
+ row.getExistingSmartRepliesAndActions(),
+ smartRepliesInflater);
apply(
mBgExecutor,
@@ -268,22 +262,21 @@ public class NotificationContentInflater implements NotificationRowContentBinder
}
}
- private static InflationProgress inflateSmartReplyViews(InflationProgress result,
- @InflationFlag int reInflateFlags, NotificationEntry entry, Context context,
- Context packageContext, HeadsUpManager headsUpManager,
- SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController,
- SmartRepliesAndActions previousSmartRepliesAndActions) {
+ private static InflationProgress inflateSmartReplyViews(
+ InflationProgress result,
+ @InflationFlag int reInflateFlags,
+ NotificationEntry entry,
+ Context context,
+ Context packageContext,
+ SmartRepliesAndActions previousSmartRepliesAndActions,
+ SmartRepliesAndActionsInflater inflater) {
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0 && result.newExpandedView != null) {
- result.expandedInflatedSmartReplies =
- InflatedSmartReplies.inflate(
- context, packageContext, entry, smartReplyConstants,
- smartReplyController, headsUpManager, previousSmartRepliesAndActions);
+ result.expandedInflatedSmartReplies = inflater.inflateSmartReplies(
+ context, packageContext, entry, previousSmartRepliesAndActions);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0 && result.newHeadsUpView != null) {
- result.headsUpInflatedSmartReplies =
- InflatedSmartReplies.inflate(
- context, packageContext, entry, smartReplyConstants,
- smartReplyController, headsUpManager, previousSmartRepliesAndActions);
+ result.headsUpInflatedSmartReplies = inflater.inflateSmartReplies(
+ context, packageContext, entry, previousSmartRepliesAndActions);
}
return result;
}
@@ -709,8 +702,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private final boolean mUsesIncreasedHeadsUpHeight;
private final @InflationFlag int mReInflateFlags;
private final NotifRemoteViewCache mRemoteViewCache;
- private final SmartReplyConstants mSmartReplyConstants;
- private final SmartReplyController mSmartReplyController;
private final Executor mBgExecutor;
private ExpandableNotificationRow mRow;
private Exception mError;
@@ -718,6 +709,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private CancellationSignal mCancellationSignal;
private final ConversationNotificationProcessor mConversationProcessor;
private final boolean mIsMediaInQS;
+ private final SmartRepliesAndActionsInflater mSmartRepliesInflater;
private AsyncInflationTask(
Executor bgExecutor,
@@ -725,8 +717,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
@InflationFlag int reInflateFlags,
NotifRemoteViewCache cache,
NotificationEntry entry,
- SmartReplyConstants smartReplyConstants,
- SmartReplyController smartReplyController,
ConversationNotificationProcessor conversationProcessor,
ExpandableNotificationRow row,
boolean isLowPriority,
@@ -734,15 +724,15 @@ public class NotificationContentInflater implements NotificationRowContentBinder
boolean usesIncreasedHeadsUpHeight,
InflationCallback callback,
RemoteViews.OnClickHandler remoteViewClickHandler,
- boolean isMediaFlagEnabled) {
+ boolean isMediaFlagEnabled,
+ SmartRepliesAndActionsInflater smartRepliesInflater) {
mEntry = entry;
mRow = row;
- mSmartReplyConstants = smartReplyConstants;
- mSmartReplyController = smartReplyController;
mBgExecutor = bgExecutor;
mInflateSynchronously = inflateSynchronously;
mReInflateFlags = reInflateFlags;
mRemoteViewCache = cache;
+ mSmartRepliesInflater = smartRepliesInflater;
mContext = mRow.getContext();
mIsLowPriority = isLowPriority;
mUsesIncreasedHeight = usesIncreasedHeight;
@@ -786,10 +776,16 @@ public class NotificationContentInflater implements NotificationRowContentBinder
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext);
- return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
- mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
- mSmartReplyConstants, mSmartReplyController,
- mRow.getExistingSmartRepliesAndActions());
+ SmartRepliesAndActions repliesAndActions =
+ mRow.getExistingSmartRepliesAndActions();
+ return inflateSmartReplyViews(
+ inflationProgress,
+ mReInflateFlags,
+ mEntry,
+ mContext,
+ packageContext,
+ repliesAndActions,
+ mSmartRepliesInflater);
} catch (Exception e) {
mError = e;
return null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 1de9308a40b1..8a644ed4d3ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -57,6 +57,7 @@ import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewW
import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions;
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.SmartReplyView;
@@ -1191,23 +1192,31 @@ public class NotificationContentView extends FrameLayout {
View bigContentView = mExpandedChild;
if (bigContentView != null && (bigContentView instanceof ViewGroup)) {
- mMediaTransferManager.applyMediaTransferView((ViewGroup) bigContentView,
- entry);
+ mMediaTransferManager.applyMediaTransferView((ViewGroup) bigContentView, entry);
}
View smallContentView = mContractedChild;
if (smallContentView != null && (smallContentView instanceof ViewGroup)) {
- mMediaTransferManager.applyMediaTransferView((ViewGroup) smallContentView,
- entry);
+ mMediaTransferManager.applyMediaTransferView((ViewGroup) smallContentView, entry);
}
}
+ /**
+ * Returns whether the {@link Notification} represented by entry has a free-form remote input.
+ * Such an input can be used e.g. to implement smart reply buttons - by passing the replies
+ * through the remote input.
+ */
+ public static boolean hasFreeformRemoteInput(NotificationEntry entry) {
+ Notification notification = entry.getSbn().getNotification();
+ return null != notification.findRemoteInputActionPair(true /* freeform */);
+ }
+
private void applyRemoteInputAndSmartReply(final NotificationEntry entry) {
if (mRemoteInputController == null) {
return;
}
- applyRemoteInput(entry, InflatedSmartReplies.hasFreeformRemoteInput(entry));
+ applyRemoteInput(entry, hasFreeformRemoteInput(entry));
if (mExpandedInflatedSmartReplies == null && mHeadsUpInflatedSmartReplies == null) {
if (DEBUG) {
@@ -1438,7 +1447,8 @@ public class NotificationContentView extends FrameLayout {
}
LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate;
- if (!InflatedSmartReplies.shouldShowSmartReplyView(entry, smartRepliesAndActions)) {
+ if (!SmartRepliesAndActionsInflaterKt
+ .shouldShowSmartReplyView(entry, smartRepliesAndActions)) {
smartReplyContainer.setVisibility(View.GONE);
return null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
index c6ae669d5d08..cbc8405cc057 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java
@@ -19,27 +19,8 @@ package com.android.systemui.statusbar.policy;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
-import android.app.RemoteInput;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.os.Build;
-import android.util.Log;
-import android.util.Pair;
import android.widget.Button;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.ArrayUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
-import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.statusbar.NotificationUiAdjustment;
-import com.android.systemui.statusbar.SmartReplyController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -48,13 +29,11 @@ import java.util.List;
* thread, to later be accessed and modified on the (performance critical) UI thread.
*/
public class InflatedSmartReplies {
- private static final String TAG = "InflatedSmartReplies";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@Nullable private final SmartReplyView mSmartReplyView;
@Nullable private final List<Button> mSmartSuggestionButtons;
@NonNull private final SmartRepliesAndActions mSmartRepliesAndActions;
- private InflatedSmartReplies(
+ public InflatedSmartReplies(
@Nullable SmartReplyView smartReplyView,
@Nullable List<Button> smartSuggestionButtons,
@NonNull SmartRepliesAndActions smartRepliesAndActions) {
@@ -76,206 +55,6 @@ public class InflatedSmartReplies {
}
/**
- * Inflate a SmartReplyView and its smart suggestions.
- */
- public static InflatedSmartReplies inflate(
- Context context,
- Context packageContext,
- NotificationEntry entry,
- SmartReplyConstants smartReplyConstants,
- SmartReplyController smartReplyController,
- HeadsUpManager headsUpManager,
- SmartRepliesAndActions existingSmartRepliesAndActions) {
- SmartRepliesAndActions newSmartRepliesAndActions =
- chooseSmartRepliesAndActions(smartReplyConstants, entry);
- if (!shouldShowSmartReplyView(entry, newSmartRepliesAndActions)) {
- return new InflatedSmartReplies(null /* smartReplyView */,
- null /* smartSuggestionButtons */, newSmartRepliesAndActions);
- }
-
- // Only block clicks if the smart buttons are different from the previous set - to avoid
- // scenarios where a user incorrectly cannot click smart buttons because the notification is
- // updated.
- boolean delayOnClickListener =
- !areSuggestionsSimilar(existingSmartRepliesAndActions, newSmartRepliesAndActions);
-
- SmartReplyView smartReplyView = SmartReplyView.inflate(context);
-
- List<Button> suggestionButtons = new ArrayList<>();
- if (newSmartRepliesAndActions.smartReplies != null) {
- suggestionButtons.addAll(smartReplyView.inflateRepliesFromRemoteInput(
- newSmartRepliesAndActions.smartReplies, smartReplyController, entry,
- delayOnClickListener));
- }
- if (newSmartRepliesAndActions.smartActions != null) {
- suggestionButtons.addAll(
- smartReplyView.inflateSmartActions(packageContext,
- newSmartRepliesAndActions.smartActions, smartReplyController, entry,
- headsUpManager, delayOnClickListener));
- }
-
- return new InflatedSmartReplies(smartReplyView, suggestionButtons,
- newSmartRepliesAndActions);
- }
-
- @VisibleForTesting
- static boolean areSuggestionsSimilar(
- SmartRepliesAndActions left, SmartRepliesAndActions right) {
- if (left == right) return true;
- if (left == null || right == null) return false;
-
- if (!left.getSmartReplies().equals(right.getSmartReplies())) {
- return false;
- }
-
- return !NotificationUiAdjustment.areDifferent(
- left.getSmartActions(), right.getSmartActions());
- }
-
- /**
- * Returns whether we should show the smart reply view and its smart suggestions.
- */
- public static boolean shouldShowSmartReplyView(
- NotificationEntry entry,
- SmartRepliesAndActions smartRepliesAndActions) {
- if (smartRepliesAndActions.smartReplies == null
- && smartRepliesAndActions.smartActions == null) {
- // There are no smart replies and no smart actions.
- return false;
- }
- // If we are showing the spinner we don't want to add the buttons.
- boolean showingSpinner = entry.getSbn().getNotification()
- .extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
- if (showingSpinner) {
- return false;
- }
- // If we are keeping the notification around while sending we don't want to add the buttons.
- boolean hideSmartReplies = entry.getSbn().getNotification()
- .extras.getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false);
- if (hideSmartReplies) {
- return false;
- }
- return true;
- }
-
- /**
- * Chose what smart replies and smart actions to display. App generated suggestions take
- * precedence. So if the app provides any smart replies, we don't show any
- * 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.
- */
- @NonNull
- public static SmartRepliesAndActions chooseSmartRepliesAndActions(
- SmartReplyConstants smartReplyConstants,
- final NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- Pair<RemoteInput, Notification.Action> remoteInputActionPair =
- notification.findRemoteInputActionPair(false /* freeform */);
- Pair<RemoteInput, Notification.Action> freeformRemoteInputActionPair =
- notification.findRemoteInputActionPair(true /* freeform */);
-
- if (!smartReplyConstants.isEnabled()) {
- if (DEBUG) {
- Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for "
- + entry.getSbn().getKey());
- }
- return new SmartRepliesAndActions(null, null);
- }
- // 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.
- boolean enableAppGeneratedSmartReplies = (!smartReplyConstants.requiresTargetingP()
- || entry.targetSdk >= Build.VERSION_CODES.P);
-
- boolean appGeneratedSmartRepliesExist =
- enableAppGeneratedSmartReplies
- && remoteInputActionPair != null
- && !ArrayUtils.isEmpty(remoteInputActionPair.first.getChoices())
- && remoteInputActionPair.second.actionIntent != null;
-
- List<Notification.Action> appGeneratedSmartActions = notification.getContextualActions();
- boolean appGeneratedSmartActionsExist = !appGeneratedSmartActions.isEmpty();
-
- SmartReplyView.SmartReplies smartReplies = null;
- SmartReplyView.SmartActions smartActions = null;
- if (appGeneratedSmartRepliesExist) {
- smartReplies = new SmartReplyView.SmartReplies(
- Arrays.asList(remoteInputActionPair.first.getChoices()),
- remoteInputActionPair.first,
- remoteInputActionPair.second.actionIntent,
- false /* fromAssistant */);
- }
- if (appGeneratedSmartActionsExist) {
- smartActions = new SmartReplyView.SmartActions(appGeneratedSmartActions,
- false /* fromAssistant */);
- }
- // Apps didn't provide any smart replies / actions, use those from NAS (if any).
- if (!appGeneratedSmartRepliesExist && !appGeneratedSmartActionsExist) {
- boolean useGeneratedReplies = !ArrayUtils.isEmpty(entry.getSmartReplies())
- && freeformRemoteInputActionPair != null
- && freeformRemoteInputActionPair.second.getAllowGeneratedReplies()
- && freeformRemoteInputActionPair.second.actionIntent != null;
- if (useGeneratedReplies) {
- smartReplies = new SmartReplyView.SmartReplies(
- entry.getSmartReplies(),
- freeformRemoteInputActionPair.first,
- freeformRemoteInputActionPair.second.actionIntent,
- true /* fromAssistant */);
- }
- boolean useSmartActions = !ArrayUtils.isEmpty(entry.getSmartActions())
- && notification.getAllowSystemGeneratedContextualActions();
- if (useSmartActions) {
- List<Notification.Action> systemGeneratedActions =
- entry.getSmartActions();
- // Filter actions if we're in kiosk-mode - we don't care about screen pinning mode,
- // since notifications aren't shown there anyway.
- ActivityManagerWrapper activityManagerWrapper =
- Dependency.get(ActivityManagerWrapper.class);
- if (activityManagerWrapper.isLockTaskKioskModeActive()) {
- systemGeneratedActions = filterWhiteListedLockTaskApps(systemGeneratedActions);
- }
- smartActions = new SmartReplyView.SmartActions(
- systemGeneratedActions, true /* fromAssistant */);
- }
- }
- return new SmartRepliesAndActions(smartReplies, smartActions);
- }
-
- /**
- * Filter actions so that only actions pointing to whitelisted apps are allowed.
- * This filtering is only meaningful when in lock-task mode.
- */
- private static List<Notification.Action> filterWhiteListedLockTaskApps(
- List<Notification.Action> actions) {
- PackageManagerWrapper packageManagerWrapper = Dependency.get(PackageManagerWrapper.class);
- DevicePolicyManagerWrapper devicePolicyManagerWrapper =
- Dependency.get(DevicePolicyManagerWrapper.class);
- List<Notification.Action> filteredActions = new ArrayList<>();
- for (Notification.Action action : actions) {
- if (action.actionIntent == null) continue;
- Intent intent = action.actionIntent.getIntent();
- // Only allow actions that are explicit (implicit intents are not handled in lock-task
- // mode), and link to whitelisted apps.
- ResolveInfo resolveInfo = packageManagerWrapper.resolveActivity(intent, 0 /* flags */);
- if (resolveInfo != null && devicePolicyManagerWrapper.isLockTaskPermitted(
- resolveInfo.activityInfo.packageName)) {
- filteredActions.add(action);
- }
- }
- return filteredActions;
- }
-
- /**
- * Returns whether the {@link Notification} represented by entry has a free-form remote input.
- * Such an input can be used e.g. to implement smart reply buttons - by passing the replies
- * through the remote input.
- */
- public static boolean hasFreeformRemoteInput(NotificationEntry entry) {
- Notification notification = entry.getSbn().getNotification();
- return null != notification.findRemoteInputActionPair(true /* freeform */);
- }
-
- /**
* A storage for smart replies and smart action.
*/
public static class SmartRepliesAndActions {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt
new file mode 100644
index 000000000000..b2c1f4840068
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartRepliesAndActionsInflater.kt
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2020 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 android.app.PendingIntent
+import android.app.RemoteInput
+import android.content.Context
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.os.SystemClock
+import android.util.Log
+import android.view.ContextThemeWrapper
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.accessibility.AccessibilityNodeInfo
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
+import android.widget.Button
+import com.android.systemui.R
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import com.android.systemui.shared.system.DevicePolicyManagerWrapper
+import com.android.systemui.shared.system.PackageManagerWrapper
+import com.android.systemui.statusbar.NotificationRemoteInputManager
+import com.android.systemui.statusbar.NotificationUiAdjustment
+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.SmartReplyView.SmartActions
+import com.android.systemui.statusbar.policy.SmartReplyView.SmartButtonType
+import com.android.systemui.statusbar.policy.SmartReplyView.SmartReplies
+import javax.inject.Inject
+
+/** Returns whether we should show the smart reply view and its smart suggestions. */
+fun shouldShowSmartReplyView(
+ entry: NotificationEntry,
+ smartRepliesAndActions: SmartRepliesAndActions
+): Boolean {
+ if (smartRepliesAndActions.smartReplies == null
+ && smartRepliesAndActions.smartActions == null) {
+ // There are no smart replies and no smart actions.
+ return false
+ }
+ // If we are showing the spinner we don't want to add the buttons.
+ val showingSpinner = entry.sbn.notification.extras
+ .getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false)
+ if (showingSpinner) {
+ return false
+ }
+ // If we are keeping the notification around while sending we don't want to add the buttons.
+ return !entry.sbn.notification.extras
+ .getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false)
+}
+
+/** Determines if two [SmartRepliesAndActions] are visually similar. */
+fun areSuggestionsSimilar(
+ left: SmartRepliesAndActions?,
+ right: SmartRepliesAndActions?
+): Boolean = when {
+ left === right -> true
+ left == null || right == null -> false
+ left.getSmartReplies() != right.getSmartReplies() -> false
+ else -> !NotificationUiAdjustment.areDifferent(left.getSmartActions(), right.getSmartActions())
+}
+
+interface SmartRepliesAndActionsInflater {
+ fun inflateSmartReplies(
+ sysuiContext: Context,
+ notifPackageContext: Context,
+ entry: NotificationEntry,
+ existingRepliesAndAction: SmartRepliesAndActions
+ ): InflatedSmartReplies
+}
+
+/*internal*/ class SmartRepliesAndActionsInflaterImpl @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 {
+
+ override fun inflateSmartReplies(
+ sysuiContext: Context,
+ notifPackageContext: Context,
+ entry: NotificationEntry,
+ existingRepliesAndAction: SmartRepliesAndActions
+ ): InflatedSmartReplies {
+ val newRepliesAndActions = chooseSmartRepliesAndActions(entry)
+ if (!shouldShowSmartReplyView(entry, newRepliesAndActions)) {
+ return InflatedSmartReplies(
+ null /* smartReplyView */,
+ null /* smartSuggestionButtons */,
+ newRepliesAndActions)
+ }
+
+ // 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)
+
+ val smartReplyView = SmartReplyView.inflate(sysuiContext, constants)
+
+ val smartReplies = newRepliesAndActions.smartReplies
+ smartReplyView.setSmartRepliesGeneratedByAssistant(smartReplies?.fromAssistant ?: false)
+ val smartReplyButtons = smartReplies?.let {
+ smartReplies.choices.asSequence().mapIndexed { index, choice ->
+ smartRepliesInflater.inflateReplyButton(
+ smartReplyView,
+ entry,
+ smartReplies,
+ index,
+ choice,
+ delayOnClickListener)
+ }
+ } ?: emptySequence()
+
+ val smartActionButtons = newRepliesAndActions.smartActions?.let { smartActions ->
+ val themedPackageContext =
+ ContextThemeWrapper(notifPackageContext, sysuiContext.theme)
+ smartActions.actions.asSequence()
+ .filter { it.actionIntent != null }
+ .mapIndexed { index, action ->
+ smartActionsInflater.inflateActionButton(
+ smartReplyView,
+ entry,
+ smartActions,
+ index,
+ action,
+ delayOnClickListener,
+ themedPackageContext)
+ }
+ } ?: emptySequence()
+
+ return InflatedSmartReplies(
+ smartReplyView,
+ (smartReplyButtons + smartActionButtons).toList(),
+ newRepliesAndActions)
+ }
+
+ /**
+ * Chose what smart replies and smart actions to display. App generated suggestions take
+ * precedence. So if the app provides any smart replies, we don't show any
+ * 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 {
+ 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)
+ }
+ return SmartRepliesAndActions(null, null)
+ }
+ // 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 appGeneratedSmartActions = notification.contextualActions
+
+ var smartReplies: SmartReplies? = when {
+ enableAppGeneratedSmartReplies -> remoteInputActionPair?.let { pair ->
+ pair.second.actionIntent?.let { actionIntent ->
+ if (pair.first.choices?.isNotEmpty() == true)
+ SmartReplies(
+ pair.first.choices.asList(),
+ pair.first,
+ actionIntent,
+ false /* fromAssistant */)
+ else null
+ }
+ }
+ else -> null
+ }
+ var smartActions: SmartActions? = when {
+ appGeneratedSmartActions.isNotEmpty() ->
+ SmartActions(appGeneratedSmartActions, false /* fromAssistant */)
+ else -> null
+ }
+ // Apps didn't provide any smart replies / actions, use those from NAS (if any).
+ if (smartReplies == null && smartActions == null) {
+ if (entry.smartReplies.isNotEmpty()
+ && freeformRemoteInputActionPair != null
+ && freeformRemoteInputActionPair.second.allowGeneratedReplies
+ && freeformRemoteInputActionPair.second.actionIntent != null) {
+ smartReplies = SmartReplies(
+ entry.smartReplies,
+ freeformRemoteInputActionPair.first,
+ freeformRemoteInputActionPair.second.actionIntent,
+ true /* fromAssistant */)
+ }
+ if (entry.smartActions.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
+ // pinning mode, since notifications aren't shown there anyway.
+ filterAllowlistedLockTaskApps(entry.smartActions)
+ else -> entry.smartActions
+ }
+ smartActions = SmartActions(systemGeneratedActions, true /* fromAssistant */)
+ }
+ }
+ return SmartRepliesAndActions(smartReplies, smartActions)
+ }
+
+ /**
+ * Filter actions so that only actions pointing to allowlisted apps are permitted.
+ * This filtering is only meaningful when in lock-task mode.
+ */
+ private fun filterAllowlistedLockTaskApps(
+ actions: List<Notification.Action>
+ ): List<Notification.Action> = actions.filter { action ->
+ // Only allow actions that are explicit (implicit intents are not handled in lock-task
+ // mode), and link to allowlisted apps.
+ action.actionIntent?.intent?.let { intent ->
+ packageManagerWrapper.resolveActivity(intent, 0 /* flags */)
+ }?.let { resolveInfo ->
+ devicePolicyManagerWrapper.isLockTaskPermitted(resolveInfo.activityInfo.packageName)
+ } ?: false
+ }
+}
+
+interface SmartActionInflater {
+ fun inflateActionButton(
+ parent: ViewGroup,
+ entry: NotificationEntry,
+ smartActions: SmartActions,
+ actionIndex: Int,
+ action: Notification.Action,
+ delayOnClickListener: Boolean,
+ packageContext: Context
+ ): Button
+}
+
+/* internal */ class SmartActionInflaterImpl @Inject constructor(
+ private val constants: SmartReplyConstants,
+ private val activityStarter: ActivityStarter,
+ private val smartReplyController: SmartReplyController,
+ private val headsUpManager: HeadsUpManager
+) : SmartActionInflater {
+
+ override fun inflateActionButton(
+ parent: ViewGroup,
+ entry: NotificationEntry,
+ smartActions: SmartActions,
+ actionIndex: Int,
+ action: Notification.Action,
+ delayOnClickListener: Boolean,
+ packageContext: Context
+ ): Button =
+ (LayoutInflater.from(parent.context)
+ .inflate(R.layout.smart_action_button, parent, false) as Button
+ ).apply {
+ text = action.title
+
+ // We received the Icon from the application - so use the Context of the application to
+ // reference icon resources.
+ val iconDrawable = action.getIcon().loadDrawable(packageContext)
+ .apply {
+ val newIconSize: Int = context.resources.getDimensionPixelSize(
+ R.dimen.smart_action_button_icon_size)
+ setBounds(0, 0, newIconSize, newIconSize)
+ }
+ // Add the action icon to the Smart Action button.
+ setCompoundDrawables(iconDrawable, null, null, null)
+
+ val onClickListener = View.OnClickListener {
+ onSmartActionClick(entry, smartActions, actionIndex, action)
+ }
+ setOnClickListener(
+ if (delayOnClickListener)
+ DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
+ else onClickListener)
+
+ // Mark this as an Action button
+ (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.ACTION
+ }
+
+ private fun onSmartActionClick(
+ entry: NotificationEntry,
+ smartActions: SmartActions,
+ actionIndex: Int,
+ action: Notification.Action
+ ) =
+ activityStarter.startPendingIntentDismissingKeyguard(action.actionIntent, entry.row) {
+ smartReplyController
+ .smartActionClicked(entry, actionIndex, action, smartActions.fromAssistant)
+ headsUpManager.removeNotification(entry.key, true /* releaseImmediately */)
+ }
+}
+
+interface SmartReplyInflater {
+ fun inflateReplyButton(
+ parent: SmartReplyView,
+ entry: NotificationEntry,
+ smartReplies: SmartReplies,
+ replyIndex: Int,
+ choice: CharSequence,
+ delayOnClickListener: Boolean
+ ): Button
+}
+
+class SmartReplyInflaterImpl @Inject constructor(
+ private val constants: SmartReplyConstants,
+ private val keyguardDismissUtil: KeyguardDismissUtil,
+ private val remoteInputManager: NotificationRemoteInputManager,
+ private val smartReplyController: SmartReplyController,
+ private val context: Context
+) : SmartReplyInflater {
+
+ override fun inflateReplyButton(
+ parent: SmartReplyView,
+ entry: NotificationEntry,
+ smartReplies: SmartReplies,
+ replyIndex: Int,
+ choice: CharSequence,
+ delayOnClickListener: Boolean
+ ): Button =
+ (LayoutInflater.from(parent.context)
+ .inflate(R.layout.smart_reply_button, parent, false) as Button
+ ).apply {
+ text = choice
+ val onClickListener = View.OnClickListener {
+ onSmartReplyClick(
+ entry,
+ smartReplies,
+ replyIndex,
+ parent,
+ this,
+ choice)
+ }
+ setOnClickListener(
+ if (delayOnClickListener)
+ DelayedOnClickListener(onClickListener, constants.onClickInitDelay)
+ else onClickListener)
+ accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun onInitializeAccessibilityNodeInfo(
+ host: View,
+ info: AccessibilityNodeInfo
+ ) {
+ super.onInitializeAccessibilityNodeInfo(host, info)
+ val label = parent.resources
+ .getString(R.string.accessibility_send_smart_reply)
+ val action = AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label)
+ info.addAction(action)
+ }
+ }
+ // TODO: probably shouldn't do this here, bad API
+ // Mark this as a Reply button
+ (layoutParams as SmartReplyView.LayoutParams).mButtonType = SmartButtonType.REPLY
+ }
+
+ private fun onSmartReplyClick(
+ entry: NotificationEntry,
+ smartReplies: SmartReplies,
+ replyIndex: Int,
+ smartReplyView: SmartReplyView,
+ button: Button,
+ choice: CharSequence
+ ) = keyguardDismissUtil.executeWhenUnlocked(!entry.isRowPinned) {
+ val canEditBeforeSend = constants.getEffectiveEditChoicesBeforeSending(
+ smartReplies.remoteInput.editChoicesBeforeSending)
+ if (canEditBeforeSend) {
+ remoteInputManager.activateRemoteInput(
+ button,
+ arrayOf(smartReplies.remoteInput),
+ smartReplies.remoteInput,
+ smartReplies.pendingIntent,
+ NotificationEntry.EditedSuggestionInfo(choice, replyIndex))
+ } else {
+ smartReplyController.smartReplySent(
+ entry,
+ replyIndex,
+ button.text,
+ NotificationLogger.getNotificationLocation(entry).toMetricsEventEnum(),
+ false /* modifiedBeforeSending */)
+ entry.setHasSentReply()
+ try {
+ val intent = createRemoteInputIntent(smartReplies, choice)
+ smartReplies.pendingIntent.send(context, 0, intent)
+ } catch (e: PendingIntent.CanceledException) {
+ Log.w(TAG, "Unable to send smart reply", e)
+ }
+ smartReplyView.hideSmartSuggestions()
+ }
+ false // do not defer
+ }
+
+ private fun createRemoteInputIntent(smartReplies: SmartReplies, choice: CharSequence): Intent {
+ val results = Bundle()
+ results.putString(smartReplies.remoteInput.resultKey, choice.toString())
+ val intent = Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+ RemoteInput.addResultsToIntent(arrayOf(smartReplies.remoteInput), intent, results)
+ RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE)
+ return intent
+ }
+}
+
+/**
+ * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of
+ * time.
+ */
+private class DelayedOnClickListener(
+ private val mActualListener: View.OnClickListener,
+ private val mInitDelayMs: Long
+) : View.OnClickListener {
+
+ private val mInitTimeMs = SystemClock.elapsedRealtime()
+
+ override fun onClick(v: View) {
+ if (hasFinishedInitialization()) {
+ mActualListener.onClick(v)
+ } else {
+ Log.i(TAG, "Accidental Smart Suggestion click registered, delay: $mInitDelayMs")
+ }
+ }
+
+ private fun hasFinishedInitialization(): Boolean =
+ SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs
+}
+
+private const val TAG = "SmartReplyViewInflater"
+private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
+
+// convenience function that swaps parameter order so that lambda can be placed at the end
+private fun KeyguardDismissUtil.executeWhenUnlocked(
+ requiresShadeOpen: Boolean,
+ onDismissAction: () -> Boolean
+) = executeWhenUnlocked(onDismissAction, requiresShadeOpen)
+
+// convenience function that swaps parameter order so that lambda can be placed at the end
+private fun ActivityStarter.startPendingIntentDismissingKeyguard(
+ intent: PendingIntent,
+ associatedView: View?,
+ runnable: () -> Unit
+) = startPendingIntentDismissingKeyguard(intent, runnable::invoke, associatedView) \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
index 949ac4df88ba..e7f84a55eb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyView.java
@@ -6,7 +6,6 @@ import android.app.Notification;
import android.app.PendingIntent;
import android.app.RemoteInput;
import android.content.Context;
-import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
@@ -15,34 +14,20 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.InsetDrawable;
import android.graphics.drawable.RippleDrawable;
-import android.os.Bundle;
-import android.os.SystemClock;
import android.text.Layout;
import android.text.TextPaint;
import android.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.util.Log;
-import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.Button;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ContrastColorUtil;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry.EditedSuggestionInfo;
-import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
import java.text.BreakIterator;
import java.util.ArrayList;
@@ -64,10 +49,6 @@ public class SmartReplyView extends ViewGroup {
private static final int SQUEEZE_FAILED = -1;
- private final SmartReplyConstants mConstants;
- private final KeyguardDismissUtil mKeyguardDismissUtil;
- private final NotificationRemoteInputManager mRemoteInputManager;
-
/**
* The upper bound for the height of this view in pixels. Notifications are automatically
* recreated on density or font size changes so caching this should be fine.
@@ -98,30 +79,25 @@ public class SmartReplyView extends ViewGroup {
*/
private boolean mSmartRepliesGeneratedByAssistant = false;
- @ColorInt
- private int mCurrentBackgroundColor;
- @ColorInt
- private final int mDefaultBackgroundColor;
- @ColorInt
- private final int mDefaultStrokeColor;
- @ColorInt
- private final int mDefaultTextColor;
- @ColorInt
- private final int mDefaultTextColorDarkBg;
- @ColorInt
- private final int mRippleColorDarkBg;
- @ColorInt
- private final int mRippleColor;
+ @ColorInt private int mCurrentBackgroundColor;
+ @ColorInt private final int mDefaultBackgroundColor;
+ @ColorInt private final int mDefaultStrokeColor;
+ @ColorInt private final int mDefaultTextColor;
+ @ColorInt private final int mDefaultTextColorDarkBg;
+ @ColorInt private final int mRippleColorDarkBg;
+ @ColorInt private final int mRippleColor;
private final int mStrokeWidth;
private final double mMinStrokeContrast;
- private ActivityStarter mActivityStarter;
+ @ColorInt private int mCurrentStrokeColor;
+ @ColorInt private int mCurrentTextColor;
+ @ColorInt private int mCurrentRippleColor;
+ private int mMaxSqueezeRemeasureAttempts;
+ private int mMaxNumActions;
+ private int mMinNumSystemGeneratedReplies;
public SmartReplyView(Context context, AttributeSet attrs) {
super(context, attrs);
- mConstants = Dependency.get(SmartReplyConstants.class);
- mKeyguardDismissUtil = Dependency.get(KeyguardDismissUtil.class);
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
mHeightUpperLimit = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.smart_reply_button_max_height);
@@ -172,6 +148,18 @@ public class SmartReplyView extends ViewGroup {
}
/**
+ * Inflate an instance of this class.
+ */
+ public static SmartReplyView inflate(Context context, SmartReplyConstants constants) {
+ SmartReplyView view = (SmartReplyView) LayoutInflater.from(context).inflate(
+ R.layout.smart_reply_view, null /* root */);
+ view.setMaxNumActions(constants.getMaxNumActions());
+ view.setMaxSqueezeRemeasureAttempts(constants.getMaxSqueezeRemeasureAttempts());
+ view.setMinNumSystemGeneratedReplies(constants.getMinNumSystemGeneratedReplies());
+ return view;
+ }
+
+ /**
* Returns an upper bound for the height of this view in pixels. This method is intended to be
* invoked before onMeasure, so it doesn't do any analysis on the contents of the buttons.
*/
@@ -197,174 +185,25 @@ public class SmartReplyView extends ViewGroup {
mCurrentBackgroundColor = mDefaultBackgroundColor;
}
- /**
- * Add buttons to the {@link SmartReplyView} - these buttons must have been preinflated using
- * one of the methods in this class.
- */
+ /** Add buttons to the {@link SmartReplyView} */
public void addPreInflatedButtons(List<Button> smartSuggestionButtons) {
for (Button button : smartSuggestionButtons) {
addView(button);
+ setButtonColors(button);
}
reallocateCandidateButtonQueueForSqueezing();
}
- /**
- * Add smart replies to this view, using the provided {@link RemoteInput} and
- * {@link PendingIntent} to respond when the user taps a smart reply. Only the replies that fit
- * into the notification are shown.
- */
- public List<Button> inflateRepliesFromRemoteInput(
- @NonNull SmartReplies smartReplies,
- SmartReplyController smartReplyController, NotificationEntry entry,
- boolean delayOnClickListener) {
- List<Button> buttons = new ArrayList<>();
-
- if (smartReplies.remoteInput != null && smartReplies.pendingIntent != null) {
- if (smartReplies.choices != null) {
- for (int i = 0; i < smartReplies.choices.size(); ++i) {
- buttons.add(inflateReplyButton(
- this, getContext(), i, smartReplies, smartReplyController, entry,
- delayOnClickListener));
- }
- this.mSmartRepliesGeneratedByAssistant = smartReplies.fromAssistant;
- }
- }
- return buttons;
- }
-
- /**
- * Add smart actions to be shown next to smart replies. Only the actions that fit into the
- * notification are shown.
- */
- public List<Button> inflateSmartActions(Context packageContext,
- @NonNull SmartActions smartActions, SmartReplyController smartReplyController,
- NotificationEntry entry, HeadsUpManager headsUpManager, boolean delayOnClickListener) {
- Context themedPackageContext = new ContextThemeWrapper(packageContext, mContext.getTheme());
- List<Button> buttons = new ArrayList<>();
- int numSmartActions = smartActions.actions.size();
- for (int n = 0; n < numSmartActions; n++) {
- Notification.Action action = smartActions.actions.get(n);
- if (action.actionIntent != null) {
- buttons.add(inflateActionButton(
- this, getContext(), themedPackageContext, n, smartActions,
- smartReplyController,
- entry, headsUpManager, delayOnClickListener));
- }
- }
- return buttons;
- }
-
- /**
- * Inflate an instance of this class.
- */
- public static SmartReplyView inflate(Context context) {
- return (SmartReplyView) LayoutInflater.from(context).inflate(
- R.layout.smart_reply_view, null /* root */);
+ public void setMaxNumActions(int maxNumActions) {
+ mMaxNumActions = maxNumActions;
}
- @VisibleForTesting
- static Button inflateReplyButton(SmartReplyView smartReplyView, Context context,
- int replyIndex, SmartReplies smartReplies, SmartReplyController smartReplyController,
- NotificationEntry entry, boolean useDelayedOnClickListener) {
- Button b = (Button) LayoutInflater.from(context).inflate(
- R.layout.smart_reply_button, smartReplyView, false);
- CharSequence choice = smartReplies.choices.get(replyIndex);
- b.setText(choice);
-
- OnDismissAction action = () -> {
- if (smartReplyView.mConstants.getEffectiveEditChoicesBeforeSending(
- smartReplies.remoteInput.getEditChoicesBeforeSending())) {
- EditedSuggestionInfo editedSuggestionInfo =
- new EditedSuggestionInfo(choice, replyIndex);
- smartReplyView.mRemoteInputManager.activateRemoteInput(b,
- new RemoteInput[] { smartReplies.remoteInput }, smartReplies.remoteInput,
- smartReplies.pendingIntent, editedSuggestionInfo);
- return false;
- }
-
- smartReplyController.smartReplySent(entry, replyIndex, b.getText(),
- NotificationLogger.getNotificationLocation(entry).toMetricsEventEnum(),
- false /* modifiedBeforeSending */);
- Bundle results = new Bundle();
- results.putString(smartReplies.remoteInput.getResultKey(), choice.toString());
- Intent intent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- RemoteInput.addResultsToIntent(new RemoteInput[] { smartReplies.remoteInput }, intent,
- results);
- RemoteInput.setResultsSource(intent, RemoteInput.SOURCE_CHOICE);
- entry.setHasSentReply();
- try {
- smartReplies.pendingIntent.send(context, 0, intent);
- } catch (PendingIntent.CanceledException e) {
- Log.w(TAG, "Unable to send smart reply", e);
- }
- // Note that as inflateReplyButton is called mSmartReplyContainer is null, but when the
- // reply Button is added to the SmartReplyView mSmartReplyContainer will be set. So, it
- // will not be possible for a user to trigger this on-click-listener without
- // mSmartReplyContainer being set.
- smartReplyView.mSmartReplyContainer.setVisibility(View.GONE);
- return false; // do not defer
- };
-
- OnClickListener onClickListener = view ->
- smartReplyView.mKeyguardDismissUtil.executeWhenUnlocked(action, !entry.isRowPinned());
- if (useDelayedOnClickListener) {
- onClickListener = new DelayedOnClickListener(onClickListener,
- smartReplyView.mConstants.getOnClickInitDelay());
- }
- b.setOnClickListener(onClickListener);
-
- b.setAccessibilityDelegate(new AccessibilityDelegate() {
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- String label = smartReplyView.getResources().getString(
- R.string.accessibility_send_smart_reply);
- info.addAction(new AccessibilityAction(AccessibilityNodeInfo.ACTION_CLICK, label));
- }
- });
-
- SmartReplyView.setButtonColors(b, smartReplyView.mCurrentBackgroundColor,
- smartReplyView.mDefaultStrokeColor, smartReplyView.mDefaultTextColor,
- smartReplyView.mRippleColor, smartReplyView.mStrokeWidth);
- return b;
+ public void setMinNumSystemGeneratedReplies(int minNumSystemGeneratedReplies) {
+ mMinNumSystemGeneratedReplies = minNumSystemGeneratedReplies;
}
- @VisibleForTesting
- static Button inflateActionButton(SmartReplyView smartReplyView, Context context,
- Context packageContext, int actionIndex, SmartActions smartActions,
- SmartReplyController smartReplyController, NotificationEntry entry,
- HeadsUpManager headsUpManager, boolean useDelayedOnClickListener) {
- Notification.Action action = smartActions.actions.get(actionIndex);
- Button button = (Button) LayoutInflater.from(context).inflate(
- R.layout.smart_action_button, smartReplyView, false);
- button.setText(action.title);
-
- // We received the Icon from the application - so use the Context of the application to
- // reference icon resources.
- Drawable iconDrawable = action.getIcon().loadDrawable(packageContext);
- // Add the action icon to the Smart Action button.
- int newIconSize = context.getResources().getDimensionPixelSize(
- R.dimen.smart_action_button_icon_size);
- iconDrawable.setBounds(0, 0, newIconSize, newIconSize);
- button.setCompoundDrawables(iconDrawable, null, null, null);
-
- OnClickListener onClickListener = view ->
- smartReplyView.getActivityStarter().startPendingIntentDismissingKeyguard(
- action.actionIntent,
- () -> {
- smartReplyController.smartActionClicked(
- entry, actionIndex, action, smartActions.fromAssistant);
- headsUpManager.removeNotification(entry.getKey(), true);
- }, entry.getRow());
- if (useDelayedOnClickListener) {
- onClickListener = new DelayedOnClickListener(onClickListener,
- smartReplyView.mConstants.getOnClickInitDelay());
- }
- button.setOnClickListener(onClickListener);
-
- // Mark this as an Action button
- final LayoutParams lp = (LayoutParams) button.getLayoutParams();
- lp.buttonType = SmartButtonType.ACTION;
- return button;
+ public void setMaxSqueezeRemeasureAttempts(int maxSqueezeRemeasureAttempts) {
+ mMaxSqueezeRemeasureAttempts = maxSqueezeRemeasureAttempts;
}
@Override
@@ -416,13 +255,13 @@ public class SmartReplyView extends ViewGroup {
// reply button is added.
SmartSuggestionMeasures actionsMeasures = null;
- final int maxNumActions = mConstants.getMaxNumActions();
+ final int maxNumActions = mMaxNumActions;
int numShownActions = 0;
for (View child : smartSuggestions) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (maxNumActions != -1 // -1 means 'no limit'
- && lp.buttonType == SmartButtonType.ACTION
+ && lp.mButtonType == SmartButtonType.ACTION
&& numShownActions >= maxNumActions) {
// We've reached the maximum number of actions, don't add another one!
continue;
@@ -446,7 +285,7 @@ public class SmartReplyView extends ViewGroup {
// Remember the current measurements in case the current button doesn't fit in.
SmartSuggestionMeasures originalMeasures = accumulatedMeasures.clone();
- if (actionsMeasures == null && lp.buttonType == SmartButtonType.REPLY) {
+ if (actionsMeasures == null && lp.mButtonType == SmartButtonType.REPLY) {
// We've added all actions (we go through actions first), now add their
// measurements.
actionsMeasures = accumulatedMeasures.clone();
@@ -510,7 +349,7 @@ public class SmartReplyView extends ViewGroup {
lp.show = true;
displayedChildCount++;
- if (lp.buttonType == SmartButtonType.ACTION) {
+ if (lp.mButtonType == SmartButtonType.ACTION) {
numShownActions++;
}
}
@@ -551,6 +390,19 @@ public class SmartReplyView extends ViewGroup {
resolveSize(buttonHeight, heightMeasureSpec));
}
+ // TODO: this should be replaced, and instead, setMinSystemGenerated... should be invoked
+ // with MAX_VALUE if mSmartRepliesGeneratedByAssistant would be false (essentially, this is a
+ // ViewModel decision, as opposed to a View decision)
+ void setSmartRepliesGeneratedByAssistant(boolean fromAssistant) {
+ mSmartRepliesGeneratedByAssistant = fromAssistant;
+ }
+
+ void hideSmartSuggestions() {
+ if (mSmartReplyContainer != null) {
+ mSmartReplyContainer.setVisibility(View.GONE);
+ }
+ }
+
/**
* Fields we keep track of inside onMeasure() to correctly measure the SmartReplyView depending
* on which suggestions are added.
@@ -577,6 +429,7 @@ public class SmartReplyView extends ViewGroup {
* Returns whether our notification contains at least N smart replies (or 0) where N is
* determined by {@link SmartReplyConstants}.
*/
+ // TODO: we probably sholdn't make this deliberation in the View
private boolean gotEnoughSmartReplies(List<View> smartReplies) {
int numShownReplies = 0;
for (View smartReplyButton : smartReplies) {
@@ -585,8 +438,7 @@ public class SmartReplyView extends ViewGroup {
numShownReplies++;
}
}
- if (numShownReplies == 0
- || numShownReplies >= mConstants.getMinNumSystemGeneratedReplies()) {
+ if (numShownReplies == 0 || numShownReplies >= mMinNumSystemGeneratedReplies) {
// We have enough replies, yay!
return true;
}
@@ -602,7 +454,7 @@ public class SmartReplyView extends ViewGroup {
if (child.getVisibility() != View.VISIBLE || !(child instanceof Button)) {
continue;
}
- if (lp.buttonType == buttonType) {
+ if (lp.mButtonType == buttonType) {
actions.add(child);
}
}
@@ -656,7 +508,7 @@ public class SmartReplyView extends ViewGroup {
// See if there's a better line-break point (leading to a more narrow button) in
// either left or right direction.
final boolean moveLeft = initialLeftTextWidth > initialRightTextWidth;
- final int maxSqueezeRemeasureAttempts = mConstants.getMaxSqueezeRemeasureAttempts();
+ final int maxSqueezeRemeasureAttempts = mMaxSqueezeRemeasureAttempts;
for (int i = 0; i < maxSqueezeRemeasureAttempts; i++) {
final int newPosition =
moveLeft ? mBreakIterator.previous() : mBreakIterator.next();
@@ -833,41 +685,38 @@ public class SmartReplyView extends ViewGroup {
final boolean dark = !ContrastColorUtil.isColorLight(backgroundColor);
- int textColor = ContrastColorUtil.ensureTextContrast(
+ mCurrentTextColor = ContrastColorUtil.ensureTextContrast(
dark ? mDefaultTextColorDarkBg : mDefaultTextColor,
backgroundColor | 0xff000000, dark);
- int strokeColor = ContrastColorUtil.ensureContrast(
+ mCurrentStrokeColor = ContrastColorUtil.ensureContrast(
mDefaultStrokeColor, backgroundColor | 0xff000000, dark, mMinStrokeContrast);
- int rippleColor = dark ? mRippleColorDarkBg : mRippleColor;
+ mCurrentRippleColor = dark ? mRippleColorDarkBg : mRippleColor;
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
- final Button child = (Button) getChildAt(i);
- setButtonColors(child, backgroundColor, strokeColor, textColor, rippleColor,
- mStrokeWidth);
+ setButtonColors((Button) getChildAt(i));
}
}
- private static void setButtonColors(Button button, int backgroundColor, int strokeColor,
- int textColor, int rippleColor, int strokeWidth) {
+ private void setButtonColors(Button button) {
Drawable drawable = button.getBackground();
if (drawable instanceof RippleDrawable) {
// Mutate in case other notifications are using this drawable.
drawable = drawable.mutate();
RippleDrawable ripple = (RippleDrawable) drawable;
- ripple.setColor(ColorStateList.valueOf(rippleColor));
+ ripple.setColor(ColorStateList.valueOf(mCurrentRippleColor));
Drawable inset = ripple.getDrawable(0);
if (inset instanceof InsetDrawable) {
Drawable background = ((InsetDrawable) inset).getDrawable();
if (background instanceof GradientDrawable) {
GradientDrawable gradientDrawable = (GradientDrawable) background;
- gradientDrawable.setColor(backgroundColor);
- gradientDrawable.setStroke(strokeWidth, strokeColor);
+ gradientDrawable.setColor(mCurrentBackgroundColor);
+ gradientDrawable.setStroke(mStrokeWidth, mCurrentStrokeColor);
}
}
button.setBackground(drawable);
}
- button.setTextColor(textColor);
+ button.setTextColor(mCurrentTextColor);
}
private void setCornerRadius(Button button, float radius) {
@@ -887,14 +736,7 @@ public class SmartReplyView extends ViewGroup {
}
}
- private ActivityStarter getActivityStarter() {
- if (mActivityStarter == null) {
- mActivityStarter = Dependency.get(ActivityStarter.class);
- }
- return mActivityStarter;
- }
-
- private enum SmartButtonType {
+ enum SmartButtonType {
REPLY,
ACTION
}
@@ -924,7 +766,7 @@ public class SmartReplyView extends ViewGroup {
private boolean show = false;
private int squeezeStatus = SQUEEZE_STATUS_NONE;
- private SmartButtonType buttonType = SmartButtonType.REPLY;
+ SmartButtonType mButtonType = SmartButtonType.REPLY;
private LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
@@ -975,32 +817,4 @@ public class SmartReplyView extends ViewGroup {
this.fromAssistant = fromAssistant;
}
}
-
- /**
- * An OnClickListener wrapper that blocks the underlying OnClickListener for a given amount of
- * time.
- */
- private static class DelayedOnClickListener implements OnClickListener {
- private final OnClickListener mActualListener;
- private final long mInitDelayMs;
- private final long mInitTimeMs;
-
- DelayedOnClickListener(OnClickListener actualOnClickListener, long initDelayMs) {
- mActualListener = actualOnClickListener;
- mInitDelayMs = initDelayMs;
- mInitTimeMs = SystemClock.elapsedRealtime();
- }
-
- public void onClick(View v) {
- if (hasFinishedInitialization()) {
- mActualListener.onClick(v);
- } else {
- Log.i(TAG, "Accidental Smart Suggestion click registered, delay: " + mInitDelayMs);
- }
- }
-
- private boolean hasFinishedInitialization() {
- return SystemClock.elapsedRealtime() >= mInitTimeMs + mInitDelayMs;
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt
new file mode 100644
index 000000000000..803d26ec3286
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2020 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.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 dagger.Binds
+import dagger.Module
+
+@Module
+interface SmartRepliesInflationModule {
+ @Binds fun bindSmartActionsInflater(impl: SmartActionInflaterImpl): SmartActionInflater
+ @Binds fun bindSmartReplyInflater(impl: SmartReplyInflaterImpl): SmartReplyInflater
+ @Binds fun bindsInflatedSmartRepliesProvider(
+ impl: SmartRepliesAndActionsInflaterImpl
+ ): SmartRepliesAndActionsInflater
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index a2f8c1cb0ad3..6b0a23f2b4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -54,13 +54,13 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.media.MediaFeatureFlag;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
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.SmartReplyConstants;
+import com.android.systemui.statusbar.policy.InflatedSmartReplies;
+import com.android.systemui.statusbar.policy.SmartRepliesAndActionsInflater;
import org.junit.Assert;
import org.junit.Before;
@@ -87,6 +87,11 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
@Mock private NotifRemoteViewCache mCache;
@Mock private ConversationNotificationProcessor mConversationNotificationProcessor;
+ @Mock private InflatedSmartReplies mInflatedSmartReplies;
+
+ private final SmartRepliesAndActionsInflater mSmartRepliesAndActionsInflater =
+ (sysuiContext, notifPackageContext, entry, existingRepliesAndAction) ->
+ mInflatedSmartReplies;
@Before
public void setUp() throws Exception {
@@ -103,16 +108,13 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
ExpandableNotificationRow row = helper.createRow(mBuilder.build());
mRow = spy(row);
- final SmartReplyConstants smartReplyConstants = mock(SmartReplyConstants.class);
- final SmartReplyController smartReplyController = mock(SmartReplyController.class);
mNotificationInflater = new NotificationContentInflater(
mCache,
mock(NotificationRemoteInputManager.class),
- () -> smartReplyConstants,
- () -> smartReplyController,
mConversationNotificationProcessor,
mock(MediaFeatureFlag.class),
- mock(Executor.class));
+ mock(Executor.class),
+ mSmartRepliesAndActionsInflater);
}
@Test
@@ -120,13 +122,15 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
BindParams params = new BindParams();
params.usesIncreasedHeadsUpHeight = true;
Notification.Builder builder = spy(mBuilder);
- mNotificationInflater.inflateNotificationViews(mRow.getEntry(),
+ mNotificationInflater.inflateNotificationViews(
+ mRow.getEntry(),
mRow,
params,
true /* inflateSynchronously */,
FLAG_CONTENT_VIEW_ALL,
builder,
- mContext);
+ mContext,
+ mSmartRepliesAndActionsInflater);
verify(builder).createHeadsUpContentView(true);
}
@@ -135,13 +139,15 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
BindParams params = new BindParams();
params.usesIncreasedHeight = true;
Notification.Builder builder = spy(mBuilder);
- mNotificationInflater.inflateNotificationViews(mRow.getEntry(),
+ mNotificationInflater.inflateNotificationViews(
+ mRow.getEntry(),
mRow,
params,
true /* inflateSynchronously */,
FLAG_CONTENT_VIEW_ALL,
builder,
- mContext);
+ mContext,
+ mSmartRepliesAndActionsInflater);
verify(builder).createContentView(true);
}
@@ -366,7 +372,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
}
}
- private class AsyncFailRemoteView extends RemoteViews {
+ private static class AsyncFailRemoteView extends RemoteViews {
Handler mHandler = Handler.createAsync(Looper.getMainLooper());
public AsyncFailRemoteView(String packageName, int layoutId) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index aff8ade6f1ae..1255b6de6608 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -79,7 +79,7 @@ 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.SmartReplyConstants;
+import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.time.FakeSystemClock;
@@ -138,6 +138,7 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
@Mock private ActivatableNotificationViewController mActivatableNotificationViewController;
@Mock private NotificationRowComponent.Builder mNotificationRowComponentBuilder;
@Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+ @Mock private InflatedSmartReplies mInflatedSmartReplies;
private StatusBarNotification mSbn;
private NotificationListenerService.RankingMap mRankingMap;
@@ -199,11 +200,11 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
NotificationContentInflater binder = new NotificationContentInflater(
cache,
mRemoteInputManager,
- () -> mock(SmartReplyConstants.class),
- () -> mock(SmartReplyController.class),
mock(ConversationNotificationProcessor.class),
mock(MediaFeatureFlag.class),
- mBgExecutor);
+ mBgExecutor,
+ (sysuiContext, notifPackageContext, entry, existingRepliesAndAction) ->
+ mInflatedSmartReplies);
mRowContentBindStage = new RowContentBindStage(
binder,
mock(NotifInflationErrorManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 2ce8b34b193a..fb37ed57c1da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -53,7 +53,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -69,7 +68,7 @@ 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.SmartReplyConstants;
+import com.android.systemui.statusbar.policy.InflatedSmartReplies;
import org.mockito.ArgumentCaptor;
@@ -134,11 +133,11 @@ public class NotificationTestHelper {
NotificationContentInflater contentBinder = new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
mock(NotificationRemoteInputManager.class),
- () -> mock(SmartReplyConstants.class),
- () -> mock(SmartReplyController.class),
mock(ConversationNotificationProcessor.class),
mock(MediaFeatureFlag.class),
- mock(Executor.class));
+ mock(Executor.class),
+ (sysuiContext, notifPackageContext, entry, existingRepliesAndAction) ->
+ mock(InflatedSmartReplies.class));
contentBinder.setInflateSynchronously(true);
mBindStage = new RowContentBindStage(contentBinder,
mock(NotifInflationErrorManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
index 53d8e5866347..e93c5dbdadc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java
@@ -67,16 +67,20 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
private static final Intent WHITELISTED_TEST_INTENT =
new Intent("com.android.WHITELISTED_TEST_ACTION");
- @Mock SmartReplyConstants mSmartReplyConstants;
- @Mock Notification mNotification;
- NotificationEntry mEntry;
- @Mock RemoteInput mRemoteInput;
- @Mock RemoteInput mFreeFormRemoteInput;
- @Mock ActivityManagerWrapper mActivityManagerWrapper;
- @Mock PackageManagerWrapper mPackageManagerWrapper;
- @Mock DevicePolicyManagerWrapper mDevicePolicyManagerWrapper;
+ @Mock private SmartReplyConstants mSmartReplyConstants;
+ @Mock private Notification mNotification;
+ @Mock private RemoteInput mRemoteInput;
+ @Mock private RemoteInput mFreeFormRemoteInput;
+ @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;
@Before
@UiThreadTest
@@ -96,6 +100,14 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
when(mActivityManagerWrapper.isLockTaskKioskModeActive()).thenReturn(false);
+
+ mSmartRepliesInflater = new SmartRepliesAndActionsInflaterImpl(
+ mSmartReplyConstants,
+ mActivityManagerWrapper,
+ mPackageManagerWrapper,
+ mDevicePolicyManagerWrapper,
+ mSmartReplyInflater,
+ mSmartActionInflater);
}
@Test
@@ -107,7 +119,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
when(mSmartReplyConstants.isEnabled()).thenReturn(false);
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions).isNull();
@@ -123,7 +135,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
when(mSmartReplyConstants.isEnabled()).thenReturn(false);
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions).isNull();
@@ -135,7 +147,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
setupAppGeneratedReplies(smartReplies);
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(Arrays.asList(smartReplies));
assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
@@ -150,7 +162,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
setupAppGeneratedSuggestions(smartReplies, smartActions);
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(Arrays.asList(smartReplies));
assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse();
@@ -169,7 +181,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
mEntry.getSmartReplies());
@@ -187,7 +199,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.setSmartReplies(createReplies("Sys Smart Reply 1", "Sys Smart Reply 2"))
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions).isNull();
@@ -202,8 +214,9 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
modifyRanking(mEntry)
.setSmartActions(createActions("Sys Smart Action 1", "Sys Smart Action 2"))
.build();
+
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies).isNull();
assertThat(repliesAndActions.smartActions.actions)
@@ -226,7 +239,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices)
.isEqualTo(Arrays.asList(appGenSmartReplies));
@@ -248,7 +261,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartActions).isNull();
assertThat(repliesAndActions.smartReplies).isNull();
@@ -270,7 +283,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
mEntry.getSmartReplies());
@@ -306,7 +319,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
// Only the action for the whitelisted package should be allowed.
assertThat(repliesAndActions.smartActions.actions.size()).isEqualTo(1);
@@ -329,7 +342,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
.build();
SmartRepliesAndActions repliesAndActions =
- InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry);
+ mSmartRepliesInflater.chooseSmartRepliesAndActions(mEntry);
// We don't restrict replies or actions in screen pinning mode.
assertThat(repliesAndActions.smartReplies.choices).isEqualTo(
@@ -356,8 +369,10 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */));
- assertThat(InflatedSmartReplies.areSuggestionsSimilar(
- leftRepliesAndActions, rightRepliesAndActions)).isTrue();
+ assertThat(
+ SmartRepliesAndActionsInflaterKt
+ .areSuggestionsSimilar(leftRepliesAndActions, rightRepliesAndActions))
+ .isTrue();
}
@Test
@@ -378,7 +393,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */));
- assertThat(InflatedSmartReplies.areSuggestionsSimilar(
+ assertThat(SmartRepliesAndActionsInflaterKt.areSuggestionsSimilar(
leftRepliesAndActions, rightRepliesAndActions)).isFalse();
}
@@ -400,7 +415,7 @@ public class InflatedSmartRepliesTest extends SysuiTestCase {
new SmartReplies(rightReplies, null, null, false /* fromAssistant */),
new SmartActions(rightActions, false /* fromAssistant */));
- assertThat(InflatedSmartReplies.areSuggestionsSimilar(
+ assertThat(SmartRepliesAndActionsInflaterKt.areSuggestionsSimilar(
leftRepliesAndActions, rightRepliesAndActions)).isFalse();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
index f1a6e67edb43..836a81e42193 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SmartReplyViewTest.java
@@ -70,6 +70,12 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import kotlin.sequences.Sequence;
+import kotlin.sequences.SequencesKt;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@@ -98,31 +104,32 @@ public class SmartReplyViewTest extends SysuiTestCase {
private int mDoubleLinePaddingHorizontal;
private int mSpacing;
- @Mock private SmartReplyController mLogger;
private NotificationEntry mEntry;
private Notification mNotification;
+
+ private SmartReplyInflaterImpl mSmartReplyInflater;
+ private SmartActionInflaterImpl mSmartActionInflater;
+
@Mock private SmartReplyConstants mConstants;
+ @Mock private ActivityStarter mActivityStarter;
+ @Mock private HeadsUpManager mHeadsUpManager;
+ @Mock private NotificationRemoteInputManager mNotificationRemoteInputManager;
+ @Mock private SmartReplyController mSmartReplyController;
- @Mock ActivityStarter mActivityStarter;
- @Mock HeadsUpManager mHeadsUpManager;
+ private final KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil();
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mReceiver = new BlockingQueueIntentReceiver();
mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION));
- mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {
- action.onDismiss();
- });
+ mKeyguardDismissUtil.setDismissHandler((action, unused) -> action.onDismiss());
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
mDependency.injectMockDependency(ShadeController.class);
mDependency.injectMockDependency(NotificationRemoteInputManager.class);
mDependency.injectTestDependency(ActivityStarter.class, mActivityStarter);
mDependency.injectTestDependency(SmartReplyConstants.class, mConstants);
- mContainer = new View(mContext, null);
- mView = SmartReplyView.inflate(mContext);
-
// Any number of replies are fine.
when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(0);
when(mConstants.getMaxSqueezeRemeasureAttempts()).thenReturn(3);
@@ -130,6 +137,9 @@ public class SmartReplyViewTest extends SysuiTestCase {
// Ensure there's no delay before we can click smart suggestions.
when(mConstants.getOnClickInitDelay()).thenReturn(0L);
+ mContainer = new View(mContext, null);
+ mView = SmartReplyView.inflate(mContext, mConstants);
+
final Resources res = mContext.getResources();
mSingleLinePaddingHorizontal = res.getDimensionPixelSize(
R.dimen.smart_reply_button_padding_horizontal_single_line);
@@ -147,6 +157,18 @@ public class SmartReplyViewTest extends SysuiTestCase {
.build();
mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person);
+
+ mSmartReplyInflater = new SmartReplyInflaterImpl(
+ mConstants,
+ mKeyguardDismissUtil,
+ mNotificationRemoteInputManager,
+ mSmartReplyController,
+ mContext);
+ mSmartActionInflater = new SmartActionInflaterImpl(
+ mConstants,
+ mActivityStarter,
+ mSmartReplyController,
+ mHeadsUpManager);
}
@After
@@ -168,7 +190,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
@Test
public void testSendSmartReply_keyguardCancelled() throws InterruptedException {
- mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {});
+ mKeyguardDismissUtil.setDismissHandler((action, unused) -> { });
setSmartReplies(TEST_CHOICES);
mView.getChildAt(2).performClick();
@@ -179,9 +201,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
@Test
public void testSendSmartReply_waitsForKeyguard() throws InterruptedException {
AtomicReference<OnDismissAction> actionRef = new AtomicReference<>();
- mDependency.get(KeyguardDismissUtil.class).setDismissHandler((action, unused) -> {
- actionRef.set(action);
- });
+
+ mKeyguardDismissUtil.setDismissHandler((action, unused) -> actionRef.set(action));
setSmartReplies(TEST_CHOICES);
mView.getChildAt(2).performClick();
@@ -202,7 +223,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
public void testSendSmartReply_controllerCalled() {
setSmartReplies(TEST_CHOICES);
mView.getChildAt(2).performClick();
- verify(mLogger).smartReplySent(mEntry, 2, TEST_CHOICES[2],
+ verify(mSmartReplyController).smartReplySent(mEntry, 2, TEST_CHOICES[2],
MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
}
@@ -461,24 +482,28 @@ public class SmartReplyViewTest extends SysuiTestCase {
private void setSmartReplies(CharSequence[] choices, boolean useDelayedOnClickListener) {
mView.resetSmartSuggestions(mContainer);
- List<Button> replyButtons = inflateSmartReplies(choices, false /* fromAssistant */,
- useDelayedOnClickListener);
+ List<Button> replyButtons =
+ inflateSmartReplies(
+ choices, false /* fromAssistant */, useDelayedOnClickListener)
+ .collect(Collectors.toList());
mView.addPreInflatedButtons(replyButtons);
}
- private List<Button> inflateSmartReplies(CharSequence[] choices, boolean fromAssistant,
- boolean useDelayedOnClickListener) {
- PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
- new Intent(TEST_ACTION), 0);
+ private SmartReplyView.SmartReplies createSmartReplies(CharSequence[] choices,
+ boolean fromAssistant) {
+ PendingIntent pendingIntent =
+ PendingIntent.getBroadcast(mContext, 0, new Intent(TEST_ACTION), 0);
RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).setChoices(choices).build();
- SmartReplyView.SmartReplies smartReplies =
- new SmartReplyView.SmartReplies(
- Arrays.asList(choices),
- input,
- pendingIntent,
- fromAssistant);
- return mView.inflateRepliesFromRemoteInput(smartReplies, mLogger, mEntry,
- useDelayedOnClickListener);
+ return new SmartReplyView.SmartReplies(
+ Arrays.asList(choices), input, pendingIntent, fromAssistant);
+ }
+
+ private Stream<Button> inflateSmartReplies(CharSequence[] choices, boolean fromAssistant,
+ boolean useDelayedOnClickListener) {
+ SmartReplyView.SmartReplies smartReplies = createSmartReplies(choices, fromAssistant);
+ return IntStream.range(0, choices.length).mapToObj(idx ->
+ mSmartReplyInflater.inflateReplyButton(
+ mView, mEntry, smartReplies, idx, choices[idx], useDelayedOnClickListener));
}
private Notification.Action createAction(String actionTitle) {
@@ -501,14 +526,20 @@ public class SmartReplyViewTest extends SysuiTestCase {
private void setSmartActions(String[] actionTitles, boolean useDelayedOnClickListener) {
mView.resetSmartSuggestions(mContainer);
- List<Button> actions = mView.inflateSmartActions(
- getContext(),
- new SmartReplyView.SmartActions(createActions(actionTitles), false),
- mLogger,
- mEntry,
- mHeadsUpManager,
- useDelayedOnClickListener);
- mView.addPreInflatedButtons(actions);
+ SmartReplyView.SmartActions smartActions = new SmartReplyView.SmartActions(
+ createActions(actionTitles), false);
+
+ Stream<Button> buttons = IntStream.range(0, smartActions.actions.size()).mapToObj(idx ->
+ mSmartActionInflater.inflateActionButton(
+ mView,
+ mEntry,
+ smartActions,
+ idx,
+ smartActions.actions.get(idx),
+ useDelayedOnClickListener,
+ getContext()));
+
+ mView.addPreInflatedButtons(buttons.collect(Collectors.toList()));
}
private void setSmartRepliesAndActions(CharSequence[] choices, String[] actionTitles) {
@@ -520,16 +551,25 @@ public class SmartReplyViewTest extends SysuiTestCase {
CharSequence[] choices, String[] actionTitles, boolean fromAssistant,
boolean useDelayedOnClickListener) {
mView.resetSmartSuggestions(mContainer);
- List<Button> smartSuggestions = inflateSmartReplies(choices, fromAssistant,
- useDelayedOnClickListener);
- smartSuggestions.addAll(mView.inflateSmartActions(
- getContext(),
- new SmartReplyView.SmartActions(createActions(actionTitles), fromAssistant),
- mLogger,
- mEntry,
- mHeadsUpManager,
- useDelayedOnClickListener));
- mView.addPreInflatedButtons(smartSuggestions);
+ Sequence<Button> inflatedReplies = SequencesKt.asSequence(
+ inflateSmartReplies(choices, fromAssistant, useDelayedOnClickListener)
+ .iterator());
+ SmartReplyView.SmartActions smartActions = new SmartReplyView.SmartActions(
+ createActions(actionTitles), fromAssistant);
+ Sequence<Button> inflatedSmartActions = SequencesKt.asSequence(
+ IntStream.range(0, smartActions.actions.size())
+ .mapToObj(idx -> mSmartActionInflater.inflateActionButton(
+ mView,
+ mEntry,
+ smartActions,
+ idx,
+ smartActions.actions.get(idx),
+ useDelayedOnClickListener,
+ getContext()))
+ .iterator());
+ mView.addPreInflatedButtons(
+ SequencesKt.toList(SequencesKt.plus(inflatedReplies, inflatedSmartActions)));
+ mView.setSmartRepliesGeneratedByAssistant(fromAssistant);
}
private ViewGroup buildExpectedView(CharSequence[] choices, int lineCount) {
@@ -564,10 +604,18 @@ public class SmartReplyViewTest extends SysuiTestCase {
Button previous = null;
SmartReplyView.SmartReplies smartReplies =
new SmartReplyView.SmartReplies(Arrays.asList(choices), null, null, false);
- for (int i = 0; i < choices.length; ++i) {
- Button current = SmartReplyView.inflateReplyButton(mView, mContext, i, smartReplies,
- null /* SmartReplyController */, null /* NotificationEntry */,
- true /* useDelayedOnClickListener */);
+
+ Iterable<Button> inflatedReplies = SequencesKt.asIterable(SequencesKt.asSequence(
+ IntStream.range(0, smartReplies.choices.size()).mapToObj(
+ idx -> mSmartReplyInflater.inflateReplyButton(
+ mView,
+ mEntry,
+ smartReplies,
+ idx,
+ smartReplies.choices.get(idx),
+ true /* delayOnClickListener */))
+ .iterator()));
+ for (Button current : inflatedReplies) {
current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
current.getPaddingBottom());
if (previous != null) {
@@ -583,9 +631,21 @@ public class SmartReplyViewTest extends SysuiTestCase {
previous = current;
}
+ SmartReplyView.SmartActions smartActions = new SmartReplyView.SmartActions(actions, false);
+ Iterable<Button> inflatedSmartActions = SequencesKt.asIterable(SequencesKt.asSequence(
+ IntStream.range(0, smartActions.actions.size())
+ .mapToObj(idx -> mSmartActionInflater.inflateActionButton(
+ mView,
+ mEntry,
+ smartActions,
+ idx,
+ smartActions.actions.get(idx),
+ true /* delayOnClickListener */,
+ getContext()))
+ .iterator()));
+
// Add smart actions
- for (int i = 0; i < actions.size(); ++i) {
- Button current = inflateActionButton(actions.get(i));
+ for (Button current : inflatedSmartActions) {
current.setPadding(paddingHorizontal, current.getPaddingTop(), paddingHorizontal,
current.getPaddingBottom());
if (previous != null) {
@@ -672,8 +732,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
Thread.sleep(delayMs);
mView.getChildAt(2).performClick();
- verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
- any());
+ verify(mActivityStarter, times(1))
+ .startPendingIntentDismissingKeyguard(any(), any(), any());
}
@Test
@@ -684,8 +744,8 @@ public class SmartReplyViewTest extends SysuiTestCase {
mView.getChildAt(2).performClick();
- verify(mActivityStarter, times(1)).startPendingIntentDismissingKeyguard(any(), any(),
- any());
+ verify(mActivityStarter, times(1))
+ .startPendingIntentDismissingKeyguard(any(), any(), any());
}
@Test
@@ -869,18 +929,26 @@ public class SmartReplyViewTest extends SysuiTestCase {
assertReplyButtonHidden(mView.getChildAt(2));
}
- private Button inflateActionButton(Notification.Action action) {
- return SmartReplyView.inflateActionButton(mView, getContext(), getContext(), 0,
- new SmartReplyView.SmartActions(Collections.singletonList(action), false),
- mLogger, mEntry, mHeadsUpManager, true /* useDelayedOnClickListener */);
- }
-
@Test
public void testInflateActionButton_smartActionIconSingleLineSizeForTwoLineButton() {
// Ensure smart action icons are the same size regardless of the number of text rows in the
// button.
- Button singleLineButton = inflateActionButton(createAction("One line"));
- Button doubleLineButton = inflateActionButton(createAction("Two\nlines"));
+ List<Notification.Action> actions = Stream.of("One line", "Two\nlines")
+ .map(this::createAction)
+ .collect(Collectors.toList());
+ SmartReplyView.SmartActions smartActions = new SmartReplyView.SmartActions(actions, false);
+ List<Button> buttons = IntStream.range(0, smartActions.actions.size())
+ .mapToObj(idx -> mSmartActionInflater.inflateActionButton(
+ mView,
+ mEntry,
+ smartActions,
+ idx,
+ smartActions.actions.get(idx),
+ true /* delayOnClickListener */,
+ getContext()))
+ .collect(Collectors.toList());
+ Button singleLineButton = buttons.get(0);
+ Button doubleLineButton = buttons.get(1);
Drawable singleLineDrawable = singleLineButton.getCompoundDrawables()[0]; // left drawable
Drawable doubleLineDrawable = doubleLineButton.getCompoundDrawables()[0]; // left drawable
assertEquals(singleLineDrawable.getBounds().width(),
@@ -1068,7 +1136,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
@Test
public void testMeasure_minNumSystemGeneratedSmartReplies_notEnoughReplies() {
- when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(3);
+ mView.setMinNumSystemGeneratedReplies(3);
// Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
String[] choices = new String[] {"reply1", "reply2"};
@@ -1082,7 +1150,9 @@ public class SmartReplyViewTest extends SysuiTestCase {
choices, actions, true /* fromAssistant */, true /* useDelayedOnClickListener */);
mView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
+ // 395, 168
assertEqualMeasures(expectedView, mView);
+
// smart replies
assertReplyButtonHidden(mView.getChildAt(0));
assertReplyButtonHidden(mView.getChildAt(1));
@@ -1121,7 +1191,7 @@ public class SmartReplyViewTest extends SysuiTestCase {
*/
@Test
public void testMeasure_minNumSystemGeneratedSmartReplies_unSqueezeActions() {
- when(mConstants.getMinNumSystemGeneratedReplies()).thenReturn(2);
+ mView.setMinNumSystemGeneratedReplies(2);
// Add 2 replies when the minimum is 3 -> we should end up with 0 replies.
String[] choices = new String[] {"This is a very long two-line reply."};