From b944ce5c1395b6e42fbc60fce04f119045f1c234 Mon Sep 17 00:00:00 2001 From: Gustav Sennton Date: Mon, 25 Feb 2019 18:52:43 +0000 Subject: [PATCH] Refactor smart reply inflation functionality into its own class. To prepare to move smart reply/action inflation off the UI thread we here move some functionality related to view inflation into a separate class. This refactoring should NOT change functionality in any way. Bug: 119801785 Test: none Change-Id: Idece2bab61dfa02796482d6b590124651774d655 --- .../row/NotificationContentView.java | 123 +------- .../policy/InflatedSmartReplies.java | 166 +++++++++++ .../row/NotificationContentViewTest.java | 240 --------------- .../policy/InflatedSmartRepliesTest.java | 279 ++++++++++++++++++ 4 files changed, 451 insertions(+), 357 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java 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 1dc48d4b18b9c..dae14debd6839 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 @@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.row; import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; -import android.app.RemoteInput; import android.content.Context; import android.graphics.Rect; import android.os.Build; @@ -28,7 +27,6 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.AttributeSet; import android.util.Log; -import android.util.Pair; import android.view.MotionEvent; import android.view.NotificationHeaderView; import android.view.View; @@ -39,7 +37,6 @@ import android.widget.ImageView; import android.widget.LinearLayout; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.ArrayUtils; import com.android.internal.util.ContrastColorUtil; import com.android.systemui.Dependency; import com.android.systemui.R; @@ -52,13 +49,14 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.wrapper.NotificationCustomViewWrapper; import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper; import com.android.systemui.statusbar.phone.NotificationGroupManager; +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.SmartReplyConstants; import com.android.systemui.statusbar.policy.SmartReplyView; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.util.List; /** * A frame layout containing the actual payload of the notification, including the contracted, @@ -1319,7 +1317,7 @@ public class NotificationContentView extends FrameLayout { } SmartRepliesAndActions smartRepliesAndActions = - chooseSmartRepliesAndActions(mSmartReplyConstants, entry); + InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, entry); if (DEBUG) { Log.d(TAG, String.format("Adding suggestions for %s, %d actions, and %d replies.", entry.notification.getKey(), @@ -1328,86 +1326,10 @@ public class NotificationContentView extends FrameLayout { smartRepliesAndActions.smartReplies == null ? 0 : smartRepliesAndActions.smartReplies.choices.length)); } - - applyRemoteInput(entry, smartRepliesAndActions.hasFreeformRemoteInput); + applyRemoteInput(entry, InflatedSmartReplies.hasFreeformRemoteInput(entry)); applySmartReplyView(smartRepliesAndActions, entry); } - /** - * 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. - */ - @VisibleForTesting - static SmartRepliesAndActions chooseSmartRepliesAndActions( - SmartReplyConstants smartReplyConstants, - final NotificationEntry entry) { - Notification notification = entry.notification.getNotification(); - Pair remoteInputActionPair = - notification.findRemoteInputActionPair(false /* freeform */); - Pair freeformRemoteInputActionPair = - notification.findRemoteInputActionPair(true /* freeform */); - - if (!smartReplyConstants.isEnabled()) { - if (DEBUG) { - Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for " - + entry.notification.getKey()); - } - return new SmartRepliesAndActions(null, null, freeformRemoteInputActionPair != 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 appGeneratedSmartActions = notification.getContextualActions(); - boolean appGeneratedSmartActionsExist = !appGeneratedSmartActions.isEmpty(); - - SmartReplyView.SmartReplies smartReplies = null; - SmartReplyView.SmartActions smartActions = null; - if (appGeneratedSmartRepliesExist) { - smartReplies = new SmartReplyView.SmartReplies( - 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.systemGeneratedSmartReplies) - && freeformRemoteInputActionPair != null - && freeformRemoteInputActionPair.second.getAllowGeneratedReplies() - && freeformRemoteInputActionPair.second.actionIntent != null; - if (useGeneratedReplies) { - smartReplies = new SmartReplyView.SmartReplies( - entry.systemGeneratedSmartReplies, - freeformRemoteInputActionPair.first, - freeformRemoteInputActionPair.second.actionIntent, - true /* fromAssistant */); - } - boolean useSmartActions = !ArrayUtils.isEmpty(entry.systemGeneratedSmartActions) - && notification.getAllowSystemGeneratedContextualActions(); - if (useSmartActions) { - smartActions = new SmartReplyView.SmartActions( - entry.systemGeneratedSmartActions, true /* fromAssistant */); - } - } - return new SmartRepliesAndActions( - smartReplies, smartActions, freeformRemoteInputActionPair != null); - } - private void applyRemoteInput(NotificationEntry entry, boolean hasFreeformRemoteInput) { View bigContentView = mExpandedChild; if (bigContentView != null) { @@ -1546,26 +1468,11 @@ public class NotificationContentView extends FrameLayout { return null; } LinearLayout smartReplyContainer = (LinearLayout) smartReplyContainerCandidate; - // If there are no smart replies and no smart actions - early out. - if (smartRepliesAndActions.smartReplies == null - && smartRepliesAndActions.smartActions == null) { - smartReplyContainer.setVisibility(View.GONE); - return null; - } - // If we are showing the spinner we don't want to add the buttons. - boolean showingSpinner = entry.notification.getNotification() - .extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false); - if (showingSpinner) { - smartReplyContainer.setVisibility(View.GONE); - return null; - } - // If we are keeping the notification around while sending we don't want to add the buttons. - boolean hideSmartReplies = entry.notification.getNotification() - .extras.getBoolean(Notification.EXTRA_HIDE_SMART_REPLIES, false); - if (hideSmartReplies) { + if (!InflatedSmartReplies.shouldShowSmartReplyView(entry, smartRepliesAndActions)) { smartReplyContainer.setVisibility(View.GONE); return null; } + SmartReplyView smartReplyView = null; if (smartReplyContainer.getChildCount() == 0) { smartReplyView = SmartReplyView.inflate(mContext, smartReplyContainer); @@ -2005,22 +1912,4 @@ public class NotificationContentView extends FrameLayout { } pw.println(); } - - @VisibleForTesting - static class SmartRepliesAndActions { - @Nullable - public final SmartReplyView.SmartReplies smartReplies; - @Nullable - public final SmartReplyView.SmartActions smartActions; - public final boolean hasFreeformRemoteInput; - - SmartRepliesAndActions( - @Nullable SmartReplyView.SmartReplies smartReplies, - @Nullable SmartReplyView.SmartActions smartActions, - boolean hasFreeformRemoteInput) { - this.smartReplies = smartReplies; - this.smartActions = smartActions; - this.hasFreeformRemoteInput = hasFreeformRemoteInput; - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java new file mode 100644 index 0000000000000..754bfb2b30aad --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplies.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Notification; +import android.app.RemoteInput; +import android.os.Build; +import android.util.Log; +import android.util.Pair; + +import com.android.internal.util.ArrayUtils; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; + +import java.util.List; + +/** + * Holder for inflated smart replies and actions. These objects should be inflated on a background + * thread, to later be accessed and modified on the (performance critical) UI thread. + */ +public class InflatedSmartReplies { + private static final String TAG = "InflatedSmartReplies"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + private InflatedSmartReplies() { } + + /** + * 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.notification.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.notification.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.notification.getNotification(); + Pair remoteInputActionPair = + notification.findRemoteInputActionPair(false /* freeform */); + Pair freeformRemoteInputActionPair = + notification.findRemoteInputActionPair(true /* freeform */); + + if (!smartReplyConstants.isEnabled()) { + if (DEBUG) { + Log.d(TAG, "Smart suggestions not enabled, not adding suggestions for " + + entry.notification.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 appGeneratedSmartActions = notification.getContextualActions(); + boolean appGeneratedSmartActionsExist = !appGeneratedSmartActions.isEmpty(); + + SmartReplyView.SmartReplies smartReplies = null; + SmartReplyView.SmartActions smartActions = null; + if (appGeneratedSmartRepliesExist) { + smartReplies = new SmartReplyView.SmartReplies( + 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.systemGeneratedSmartReplies) + && freeformRemoteInputActionPair != null + && freeformRemoteInputActionPair.second.getAllowGeneratedReplies() + && freeformRemoteInputActionPair.second.actionIntent != null; + if (useGeneratedReplies) { + smartReplies = new SmartReplyView.SmartReplies( + entry.systemGeneratedSmartReplies, + freeformRemoteInputActionPair.first, + freeformRemoteInputActionPair.second.actionIntent, + true /* fromAssistant */); + } + boolean useSmartActions = !ArrayUtils.isEmpty(entry.systemGeneratedSmartActions) + && notification.getAllowSystemGeneratedContextualActions(); + if (useSmartActions) { + smartActions = new SmartReplyView.SmartActions( + entry.systemGeneratedSmartActions, true /* fromAssistant */); + } + } + return new SmartRepliesAndActions(smartReplies, smartActions); + } + + /** + * 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.notification.getNotification(); + return null != notification.findRemoteInputActionPair(true /* freeform */); + } + + /** + * A storage for smart replies and smart action. + */ + public static class SmartRepliesAndActions { + @Nullable public final SmartReplyView.SmartReplies smartReplies; + @Nullable public final SmartReplyView.SmartActions smartActions; + + SmartRepliesAndActions( + @Nullable SmartReplyView.SmartReplies smartReplies, + @Nullable SmartReplyView.SmartActions smartActions) { + this.smartReplies = smartReplies; + this.smartActions = smartActions; + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java index c80396d8292ab..d756b0970346d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java @@ -16,8 +16,6 @@ package com.android.systemui.statusbar.notification.row; -import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; @@ -31,62 +29,31 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.AppOpsManager; -import android.app.Notification; -import android.app.PendingIntent; -import android.app.RemoteInput; -import android.content.Intent; import android.graphics.drawable.Icon; -import android.service.notification.StatusBarNotification; import android.support.test.annotation.UiThreadTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.util.ArraySet; -import android.util.Pair; import android.view.NotificationHeaderView; import android.view.View; -import com.android.systemui.R; import com.android.systemui.SysuiTestCase; -import com.android.systemui.statusbar.notification.collection.NotificationEntry; -import com.android.systemui.statusbar.policy.SmartReplyConstants; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) public class NotificationContentViewTest extends SysuiTestCase { - private static final String TEST_ACTION = "com.android.SMART_REPLY_VIEW_ACTION"; - NotificationContentView mView; - @Mock - SmartReplyConstants mSmartReplyConstants; - @Mock - StatusBarNotification mStatusBarNotification; - @Mock - Notification mNotification; - NotificationEntry mEntry; - @Mock - RemoteInput mRemoteInput; - @Mock - RemoteInput mFreeFormRemoteInput; - private Icon mActionIcon; - @Before @UiThreadTest public void setup() { - MockitoAnnotations.initMocks(this); - mView = new NotificationContentView(mContext, null); ExpandableNotificationRow row = new ExpandableNotificationRow(mContext, null); ExpandableNotificationRow mockRow = spy(row); @@ -103,13 +70,6 @@ public class NotificationContentViewTest extends SysuiTestCase { mView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED); mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); - - // Smart replies and actions - when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(true); - when(mStatusBarNotification.getNotification()).thenReturn(mNotification); - mEntry = new NotificationEntry(mStatusBarNotification); - when(mSmartReplyConstants.isEnabled()).thenReturn(true); - mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person); } private View createViewWithHeight(int height) { @@ -158,204 +118,4 @@ public class NotificationContentViewTest extends SysuiTestCase { verify(mockAmbient, never()).showAppOpsIcons(ops); verify(mockHeadsUp, times(1)).showAppOpsIcons(any()); } - - private void setupAppGeneratedReplies(CharSequence[] smartReplies) { - setupAppGeneratedReplies(smartReplies, true /* allowSystemGeneratedReplies */); - } - - private void setupAppGeneratedReplies( - CharSequence[] smartReplies, boolean allowSystemGeneratedReplies) { - PendingIntent pendingIntent = - PendingIntent.getBroadcast(mContext, 0, new Intent(TEST_ACTION), 0); - Notification.Action action = - new Notification.Action.Builder(null, "Test Action", pendingIntent).build(); - when(mRemoteInput.getChoices()).thenReturn(smartReplies); - Pair remoteInputActionPair = - Pair.create(mRemoteInput, action); - when(mNotification.findRemoteInputActionPair(false)).thenReturn(remoteInputActionPair); - - Notification.Action freeFormRemoteInputAction = - createActionBuilder("Freeform Test Action") - .setAllowGeneratedReplies(allowSystemGeneratedReplies) - .build(); - Pair freeFormRemoteInputActionPair = - Pair.create(mFreeFormRemoteInput, freeFormRemoteInputAction); - when(mNotification.findRemoteInputActionPair(true)).thenReturn( - freeFormRemoteInputActionPair); - - when(mSmartReplyConstants.requiresTargetingP()).thenReturn(false); - } - - private void setupAppGeneratedSuggestions( - CharSequence[] smartReplies, List smartActions) { - setupAppGeneratedReplies(smartReplies); - when(mNotification.getContextualActions()).thenReturn(smartActions); - } - - @Test - public void chooseSmartRepliesAndActions_smartRepliesOff_noAppGeneratedSmartSuggestions() { - CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"}; - List smartActions = - createActions(new String[] {"Test Action 1", "Test Action 2"}); - setupAppGeneratedSuggestions(smartReplies, smartActions); - when(mSmartReplyConstants.isEnabled()).thenReturn(false); - - NotificationContentView.SmartRepliesAndActions repliesAndActions = - NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - - assertThat(repliesAndActions.smartReplies).isNull(); - assertThat(repliesAndActions.smartActions).isNull(); - } - - @Test - public void chooseSmartRepliesAndActions_smartRepliesOff_noSystemGeneratedSmartSuggestions() { - mEntry.systemGeneratedSmartReplies = - new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"}; - mEntry.systemGeneratedSmartActions = - createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"}); - when(mSmartReplyConstants.isEnabled()).thenReturn(false); - - NotificationContentView.SmartRepliesAndActions repliesAndActions = - NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - - assertThat(repliesAndActions.smartReplies).isNull(); - assertThat(repliesAndActions.smartActions).isNull(); - } - - @Test - public void chooseSmartRepliesAndActions_appGeneratedSmartReplies() { - CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"}; - setupAppGeneratedReplies(smartReplies); - - NotificationContentView.SmartRepliesAndActions repliesAndActions = - NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - - assertThat(repliesAndActions.smartReplies.choices).isEqualTo(smartReplies); - assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse(); - assertThat(repliesAndActions.smartActions).isNull(); - } - - @Test - public void chooseSmartRepliesAndActions_appGeneratedSmartRepliesAndActions() { - CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"}; - List smartActions = - createActions(new String[] {"Test Action 1", "Test Action 2"}); - setupAppGeneratedSuggestions(smartReplies, smartActions); - - NotificationContentView.SmartRepliesAndActions repliesAndActions = - NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - - assertThat(repliesAndActions.smartReplies.choices).isEqualTo(smartReplies); - assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse(); - assertThat(repliesAndActions.smartActions.actions).isEqualTo(smartActions); - assertThat(repliesAndActions.smartActions.fromAssistant).isFalse(); - } - - @Test - public void chooseSmartRepliesAndActions_sysGeneratedSmartReplies() { - // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart - // replies. - setupAppGeneratedReplies(null /* smartReplies */); - - mEntry.systemGeneratedSmartReplies = - new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"}; - NotificationContentView.SmartRepliesAndActions repliesAndActions = - NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - - assertThat(repliesAndActions.smartReplies.choices).isEqualTo( - mEntry.systemGeneratedSmartReplies); - assertThat(repliesAndActions.smartReplies.fromAssistant).isTrue(); - assertThat(repliesAndActions.smartActions).isNull(); - } - - @Test - public void chooseSmartRepliesAndActions_noSysGeneratedSmartRepliesIfNotAllowed() { - // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart - // replies. - setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */); - - mEntry.systemGeneratedSmartReplies = - new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"}; - NotificationContentView.SmartRepliesAndActions repliesAndActions = - NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - - assertThat(repliesAndActions.smartReplies).isNull(); - assertThat(repliesAndActions.smartActions).isNull(); - } - - @Test - public void chooseSmartRepliesAndActions_sysGeneratedSmartActions() { - // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart - // actions. - setupAppGeneratedReplies(null /* smartReplies */); - - mEntry.systemGeneratedSmartActions = - createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"}); - NotificationContentView.SmartRepliesAndActions repliesAndActions = - NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - - assertThat(repliesAndActions.smartReplies).isNull(); - assertThat(repliesAndActions.smartActions.actions) - .isEqualTo(mEntry.systemGeneratedSmartActions); - assertThat(repliesAndActions.smartActions.fromAssistant).isTrue(); - } - - @Test - public void chooseSmartRepliesAndActions_appGenPreferredOverSysGen() { - CharSequence[] appGenSmartReplies = new String[] {"Reply1", "Reply2"}; - // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart - // replies. - List appGenSmartActions = - createActions(new String[] {"Test Action 1", "Test Action 2"}); - setupAppGeneratedSuggestions(appGenSmartReplies, appGenSmartActions); - - mEntry.systemGeneratedSmartReplies = - new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"}; - mEntry.systemGeneratedSmartActions = - createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"}); - - NotificationContentView.SmartRepliesAndActions repliesAndActions = - NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - - assertThat(repliesAndActions.smartReplies.choices).isEqualTo(appGenSmartReplies); - assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse(); - assertThat(repliesAndActions.smartActions.actions).isEqualTo(appGenSmartActions); - assertThat(repliesAndActions.smartActions.fromAssistant).isFalse(); - } - - @Test - public void chooseSmartRepliesAndActions_disallowSysGenSmartActions() { - // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart - // actions. - setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */); - when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(false); - mEntry.systemGeneratedSmartReplies = - new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"}; - mEntry.systemGeneratedSmartActions = - createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"}); - - NotificationContentView.SmartRepliesAndActions repliesAndActions = - NotificationContentView.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); - - assertThat(repliesAndActions.smartActions).isNull(); - assertThat(repliesAndActions.smartReplies).isNull(); - } - - private Notification.Action.Builder createActionBuilder(String actionTitle) { - PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, - new Intent(TEST_ACTION), 0); - return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent); - } - - private Notification.Action createAction(String actionTitle) { - return createActionBuilder(actionTitle).build(); - } - - private List createActions(String[] actionTitles) { - List actions = new ArrayList<>(); - for (String title : actionTitles) { - actions.add(createAction(title)); - } - return actions; - } } 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 new file mode 100644 index 0000000000000..3382a906d057c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/InflatedSmartRepliesTest.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.policy; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.RemoteInput; +import android.content.Intent; +import android.graphics.drawable.Icon; +import android.service.notification.StatusBarNotification; +import android.support.test.annotation.UiThreadTest; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.util.Pair; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAndActions; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class InflatedSmartRepliesTest extends SysuiTestCase { + + private static final String TEST_ACTION = "com.android.SMART_REPLY_VIEW_ACTION"; + + @Mock + SmartReplyConstants mSmartReplyConstants; + @Mock + StatusBarNotification mStatusBarNotification; + @Mock + Notification mNotification; + NotificationEntry mEntry; + @Mock + RemoteInput mRemoteInput; + @Mock + RemoteInput mFreeFormRemoteInput; + + private Icon mActionIcon; + + @Before + @UiThreadTest + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(true); + when(mStatusBarNotification.getNotification()).thenReturn(mNotification); + mEntry = new NotificationEntry(mStatusBarNotification); + when(mSmartReplyConstants.isEnabled()).thenReturn(true); + mActionIcon = Icon.createWithResource(mContext, R.drawable.ic_person); + } + + @Test + public void chooseSmartRepliesAndActions_smartRepliesOff_noAppGeneratedSmartSuggestions() { + CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"}; + List smartActions = + createActions(new String[] {"Test Action 1", "Test Action 2"}); + setupAppGeneratedSuggestions(smartReplies, smartActions); + when(mSmartReplyConstants.isEnabled()).thenReturn(false); + + SmartRepliesAndActions repliesAndActions = + InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); + + assertThat(repliesAndActions.smartReplies).isNull(); + assertThat(repliesAndActions.smartActions).isNull(); + } + + @Test + public void chooseSmartRepliesAndActions_smartRepliesOff_noSystemGeneratedSmartSuggestions() { + mEntry.systemGeneratedSmartReplies = + new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"}; + mEntry.systemGeneratedSmartActions = + createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"}); + when(mSmartReplyConstants.isEnabled()).thenReturn(false); + + SmartRepliesAndActions repliesAndActions = + InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); + + assertThat(repliesAndActions.smartReplies).isNull(); + assertThat(repliesAndActions.smartActions).isNull(); + } + + @Test + public void chooseSmartRepliesAndActions_appGeneratedSmartReplies() { + CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"}; + setupAppGeneratedReplies(smartReplies); + + SmartRepliesAndActions repliesAndActions = + InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); + + assertThat(repliesAndActions.smartReplies.choices).isEqualTo(smartReplies); + assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse(); + assertThat(repliesAndActions.smartActions).isNull(); + } + + @Test + public void chooseSmartRepliesAndActions_appGeneratedSmartRepliesAndActions() { + CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"}; + List smartActions = + createActions(new String[] {"Test Action 1", "Test Action 2"}); + setupAppGeneratedSuggestions(smartReplies, smartActions); + + SmartRepliesAndActions repliesAndActions = + InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); + + assertThat(repliesAndActions.smartReplies.choices).isEqualTo(smartReplies); + assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse(); + assertThat(repliesAndActions.smartActions.actions).isEqualTo(smartActions); + assertThat(repliesAndActions.smartActions.fromAssistant).isFalse(); + } + + @Test + public void chooseSmartRepliesAndActions_sysGeneratedSmartReplies() { + // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart + // replies. + setupAppGeneratedReplies(null /* smartReplies */); + + mEntry.systemGeneratedSmartReplies = + new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"}; + SmartRepliesAndActions repliesAndActions = + InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); + + assertThat(repliesAndActions.smartReplies.choices).isEqualTo( + mEntry.systemGeneratedSmartReplies); + assertThat(repliesAndActions.smartReplies.fromAssistant).isTrue(); + assertThat(repliesAndActions.smartActions).isNull(); + } + + @Test + public void chooseSmartRepliesAndActions_noSysGeneratedSmartRepliesIfNotAllowed() { + // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart + // replies. + setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */); + + mEntry.systemGeneratedSmartReplies = + new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"}; + SmartRepliesAndActions repliesAndActions = + InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); + + assertThat(repliesAndActions.smartReplies).isNull(); + assertThat(repliesAndActions.smartActions).isNull(); + } + + @Test + public void chooseSmartRepliesAndActions_sysGeneratedSmartActions() { + // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart + // actions. + setupAppGeneratedReplies(null /* smartReplies */); + + mEntry.systemGeneratedSmartActions = + createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"}); + SmartRepliesAndActions repliesAndActions = + InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); + + assertThat(repliesAndActions.smartReplies).isNull(); + assertThat(repliesAndActions.smartActions.actions) + .isEqualTo(mEntry.systemGeneratedSmartActions); + assertThat(repliesAndActions.smartActions.fromAssistant).isTrue(); + } + + @Test + public void chooseSmartRepliesAndActions_appGenPreferredOverSysGen() { + CharSequence[] appGenSmartReplies = new String[] {"Reply1", "Reply2"}; + // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart + // replies. + List appGenSmartActions = + createActions(new String[] {"Test Action 1", "Test Action 2"}); + setupAppGeneratedSuggestions(appGenSmartReplies, appGenSmartActions); + + mEntry.systemGeneratedSmartReplies = + new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"}; + mEntry.systemGeneratedSmartActions = + createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"}); + + SmartRepliesAndActions repliesAndActions = + InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); + + assertThat(repliesAndActions.smartReplies.choices).isEqualTo(appGenSmartReplies); + assertThat(repliesAndActions.smartReplies.fromAssistant).isFalse(); + assertThat(repliesAndActions.smartActions.actions).isEqualTo(appGenSmartActions); + assertThat(repliesAndActions.smartActions.fromAssistant).isFalse(); + } + + @Test + public void chooseSmartRepliesAndActions_disallowSysGenSmartActions() { + // Pass a null-array as app-generated smart replies, so that we use NAS-generated smart + // actions. + setupAppGeneratedReplies(null /* smartReplies */, false /* allowSystemGeneratedReplies */); + when(mNotification.getAllowSystemGeneratedContextualActions()).thenReturn(false); + mEntry.systemGeneratedSmartReplies = + new String[] {"Sys Smart Reply 1", "Sys Smart Reply 2"}; + mEntry.systemGeneratedSmartActions = + createActions(new String[] {"Sys Smart Action 1", "Sys Smart Action 2"}); + + SmartRepliesAndActions repliesAndActions = + InflatedSmartReplies.chooseSmartRepliesAndActions(mSmartReplyConstants, mEntry); + + assertThat(repliesAndActions.smartActions).isNull(); + assertThat(repliesAndActions.smartReplies).isNull(); + } + + private void setupAppGeneratedReplies(CharSequence[] smartReplies) { + setupAppGeneratedReplies(smartReplies, true /* allowSystemGeneratedReplies */); + } + + private void setupAppGeneratedReplies( + CharSequence[] smartReplies, boolean allowSystemGeneratedReplies) { + PendingIntent pendingIntent = + PendingIntent.getBroadcast(mContext, 0, new Intent(TEST_ACTION), 0); + Notification.Action action = + new Notification.Action.Builder(null, "Test Action", pendingIntent).build(); + when(mRemoteInput.getChoices()).thenReturn(smartReplies); + Pair remoteInputActionPair = + Pair.create(mRemoteInput, action); + when(mNotification.findRemoteInputActionPair(false)).thenReturn(remoteInputActionPair); + + Notification.Action freeFormRemoteInputAction = + createActionBuilder("Freeform Test Action") + .setAllowGeneratedReplies(allowSystemGeneratedReplies) + .build(); + Pair freeFormRemoteInputActionPair = + Pair.create(mFreeFormRemoteInput, freeFormRemoteInputAction); + when(mNotification.findRemoteInputActionPair(true)).thenReturn( + freeFormRemoteInputActionPair); + + when(mSmartReplyConstants.requiresTargetingP()).thenReturn(false); + } + + private void setupAppGeneratedSuggestions( + CharSequence[] smartReplies, List smartActions) { + setupAppGeneratedReplies(smartReplies); + when(mNotification.getContextualActions()).thenReturn(smartActions); + } + + private Notification.Action.Builder createActionBuilder(String actionTitle) { + PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, + new Intent(TEST_ACTION), 0); + return new Notification.Action.Builder(mActionIcon, actionTitle, pendingIntent); + } + + private Notification.Action createAction(String actionTitle) { + return createActionBuilder(actionTitle).build(); + } + + private List createActions(String[] actionTitles) { + List actions = new ArrayList<>(); + for (String title : actionTitles) { + actions.add(createAction(title)); + } + return actions; + } +}