Merge "Refactor smart reply inflation functionality into its own class."

This commit is contained in:
Gustav Sennton
2019-02-28 09:52:32 +00:00
committed by Android (Google) Code Review
4 changed files with 451 additions and 357 deletions

View File

@@ -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<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.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<Notification.Action> 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;
}
}
}

View File

@@ -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<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.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<Notification.Action> 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;
}
}
}

View File

@@ -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<RemoteInput, Notification.Action> remoteInputActionPair =
Pair.create(mRemoteInput, action);
when(mNotification.findRemoteInputActionPair(false)).thenReturn(remoteInputActionPair);
Notification.Action freeFormRemoteInputAction =
createActionBuilder("Freeform Test Action")
.setAllowGeneratedReplies(allowSystemGeneratedReplies)
.build();
Pair<RemoteInput, Notification.Action> freeFormRemoteInputActionPair =
Pair.create(mFreeFormRemoteInput, freeFormRemoteInputAction);
when(mNotification.findRemoteInputActionPair(true)).thenReturn(
freeFormRemoteInputActionPair);
when(mSmartReplyConstants.requiresTargetingP()).thenReturn(false);
}
private void setupAppGeneratedSuggestions(
CharSequence[] smartReplies, List<Notification.Action> smartActions) {
setupAppGeneratedReplies(smartReplies);
when(mNotification.getContextualActions()).thenReturn(smartActions);
}
@Test
public void chooseSmartRepliesAndActions_smartRepliesOff_noAppGeneratedSmartSuggestions() {
CharSequence[] smartReplies = new String[] {"Reply1", "Reply2"};
List<Notification.Action> 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<Notification.Action> 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<Notification.Action> 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<Notification.Action> createActions(String[] actionTitles) {
List<Notification.Action> actions = new ArrayList<>();
for (String title : actionTitles) {
actions.add(createAction(title));
}
return actions;
}
}

View File

@@ -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<Notification.Action> 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<Notification.Action> 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<Notification.Action> 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<RemoteInput, Notification.Action> remoteInputActionPair =
Pair.create(mRemoteInput, action);
when(mNotification.findRemoteInputActionPair(false)).thenReturn(remoteInputActionPair);
Notification.Action freeFormRemoteInputAction =
createActionBuilder("Freeform Test Action")
.setAllowGeneratedReplies(allowSystemGeneratedReplies)
.build();
Pair<RemoteInput, Notification.Action> freeFormRemoteInputActionPair =
Pair.create(mFreeFormRemoteInput, freeFormRemoteInputAction);
when(mNotification.findRemoteInputActionPair(true)).thenReturn(
freeFormRemoteInputActionPair);
when(mSmartReplyConstants.requiresTargetingP()).thenReturn(false);
}
private void setupAppGeneratedSuggestions(
CharSequence[] smartReplies, List<Notification.Action> 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<Notification.Action> createActions(String[] actionTitles) {
List<Notification.Action> actions = new ArrayList<>();
for (String title : actionTitles) {
actions.add(createAction(title));
}
return actions;
}
}