Merge "Add a Notifications API for contextual (smart) actions."

This commit is contained in:
Gustav Sennton
2018-11-16 10:22:39 +00:00
committed by Android (Google) Code Review
13 changed files with 93 additions and 73 deletions

View File

@@ -5370,6 +5370,7 @@ package android.app {
field public static final android.os.Parcelable.Creator<android.app.Notification.Action> CREATOR;
field public static final int SEMANTIC_ACTION_ARCHIVE = 5; // 0x5
field public static final int SEMANTIC_ACTION_CALL = 10; // 0xa
field public static final int SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION = 11; // 0xb
field public static final int SEMANTIC_ACTION_DELETE = 4; // 0x4
field public static final int SEMANTIC_ACTION_MARK_AS_READ = 2; // 0x2
field public static final int SEMANTIC_ACTION_MARK_AS_UNREAD = 3; // 0x3

View File

@@ -207,7 +207,8 @@ public class Notification implements Parcelable
private static final int MAX_REPLY_HISTORY = 5;
/**
* Maximum numbers of action buttons in a notification.
* Maximum number of (generic) action buttons in a notification (contextual action buttons are
* handled separately).
* @hide
*/
public static final int MAX_ACTION_BUTTONS = 3;
@@ -1421,6 +1422,12 @@ public class Notification implements Parcelable
*/
public static final int SEMANTIC_ACTION_CALL = 10;
/**
* {@code SemanticAction}: Contextual action - dependent on the current notification. E.g.
* open a Map application with an address shown in the notification.
*/
public static final int SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION = 11;
private final Bundle mExtras;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Icon mIcon;
@@ -2042,7 +2049,8 @@ public class Notification implements Parcelable
SEMANTIC_ACTION_UNMUTE,
SEMANTIC_ACTION_THUMBS_UP,
SEMANTIC_ACTION_THUMBS_DOWN,
SEMANTIC_ACTION_CALL
SEMANTIC_ACTION_CALL,
SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION
})
@Retention(RetentionPolicy.SOURCE)
public @interface SemanticAction {}
@@ -4962,6 +4970,18 @@ public class Notification implements Parcelable
result);
}
private static List<Notification.Action> filterOutContextualActions(
List<Notification.Action> actions) {
List<Notification.Action> nonContextualActions = new ArrayList<>();
for (Notification.Action action : actions) {
if (action.getSemanticAction()
!= Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION) {
nonContextualActions.add(action);
}
}
return nonContextualActions;
}
private RemoteViews applyStandardTemplateWithActions(int layoutId,
StandardTemplateParams p, TemplateBindResult result) {
RemoteViews big = applyStandardTemplate(layoutId, p, result);
@@ -4970,7 +4990,11 @@ public class Notification implements Parcelable
boolean validRemoteInput = false;
int N = mActions.size();
// In the UI contextual actions appear separately from the standard actions, so we
// filter them out here.
List<Notification.Action> nonContextualActions = filterOutContextualActions(mActions);
int N = nonContextualActions.size();
boolean emphazisedMode = mN.fullScreenIntent != null && !p.ambient;
big.setBoolean(R.id.actions, "setEmphasizedMode", emphazisedMode);
if (N > 0) {
@@ -4979,7 +5003,8 @@ public class Notification implements Parcelable
big.setViewLayoutMarginBottomDimen(R.id.notification_action_list_margin_target, 0);
if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS;
for (int i=0; i<N; i++) {
Action action = mActions.get(i);
Action action = nonContextualActions.get(i);
boolean actionHasValidInput = hasValidRemoteInput(action);
validRemoteInput |= actionHasValidInput;

View File

@@ -53,7 +53,8 @@ public class NotificationUiAdjustment {
public static NotificationUiAdjustment extractFromNotificationEntry(
NotificationData.Entry entry) {
return new NotificationUiAdjustment(entry.key, entry.smartActions, entry.smartReplies);
return new NotificationUiAdjustment(
entry.key, entry.systemGeneratedSmartActions, entry.smartReplies);
}
public static boolean needReinflate(

View File

@@ -114,8 +114,9 @@ public class NotificationData {
public CharSequence remoteInputText;
public List<SnoozeCriterion> snoozeCriteria;
public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL;
/** Smart Actions provided by the NotificationAssistantService. */
@NonNull
public List<Notification.Action> smartActions = Collections.emptyList();
public List<Notification.Action> systemGeneratedSmartActions = Collections.emptyList();
public CharSequence[] smartReplies = new CharSequence[0];
private int mCachedContrastColor = COLOR_INVALID;
@@ -171,7 +172,7 @@ public class NotificationData {
importance = ranking.getImportance();
snoozeCriteria = ranking.getSnoozeCriteria();
userSentiment = ranking.getUserSentiment();
smartActions = ranking.getSmartActions() == null
systemGeneratedSmartActions = ranking.getSmartActions() == null
? Collections.emptyList() : ranking.getSmartActions();
smartReplies = ranking.getSmartReplies() == null
? new CharSequence[0]

View File

@@ -702,7 +702,6 @@ public class NotificationEntryManager implements Dumpable, NotificationInflater.
&& !mPresenter.isPresenterFullyCollapsed();
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
row.setSmartActions(entry.smartActions);
row.setEntry(entry);
row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, shouldHeadsUp(entry));

View File

@@ -39,7 +39,6 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -1567,10 +1566,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mNotificationInflater.setUsesIncreasedHeight(use);
}
public void setSmartActions(List<Notification.Action> smartActions) {
mNotificationInflater.setSmartActions(smartActions);
}
public void setUseIncreasedHeadsUpHeight(boolean use) {
mUseIncreasedHeadsUpHeight = use;
mNotificationInflater.setUsesIncreasedHeadsUpHeight(use);

View File

@@ -43,10 +43,7 @@ import com.android.systemui.util.Assert;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
@@ -131,7 +128,6 @@ public class NotificationInflater {
private boolean mIsChildInGroup;
private InflationCallback mCallback;
private boolean mRedactAmbient;
private List<Notification.Action> mSmartActions;
private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
public NotificationInflater(ExpandableNotificationRow row) {
@@ -161,10 +157,6 @@ public class NotificationInflater {
mUsesIncreasedHeight = usesIncreasedHeight;
}
public void setSmartActions(List<Notification.Action> smartActions) {
mSmartActions = smartActions;
}
public void setUsesIncreasedHeadsUpHeight(boolean usesIncreasedHeight) {
mUsesIncreasedHeadsUpHeight = usesIncreasedHeight;
}
@@ -258,8 +250,7 @@ public class NotificationInflater {
StatusBarNotification sbn = mRow.getEntry().notification;
AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mCachedContentViews,
mRow, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, mRedactAmbient, mCallback, mRemoteViewClickHandler,
mSmartActions);
mUsesIncreasedHeadsUpHeight, mRedactAmbient, mCallback, mRemoteViewClickHandler);
if (mCallback != null && mCallback.doInflateSynchronous()) {
task.onPostExecute(task.doInBackground());
} else {
@@ -765,15 +756,13 @@ public class NotificationInflater {
private Exception mError;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private CancellationSignal mCancellationSignal;
private List<Notification.Action> mSmartActions;
private AsyncInflationTask(StatusBarNotification notification,
@InflationFlag int reInflateFlags,
ArrayMap<Integer, RemoteViews> cachedContentViews, ExpandableNotificationRow row,
boolean isLowPriority, boolean isChildInGroup, boolean usesIncreasedHeight,
boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler,
List<Notification.Action> smartActions) {
InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler) {
mRow = row;
mSbn = notification;
mReInflateFlags = reInflateFlags;
@@ -786,9 +775,6 @@ public class NotificationInflater {
mRedactAmbient = redactAmbient;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
mSmartActions = smartActions == null
? Collections.emptyList()
: new ArrayList<>(smartActions);
NotificationData.Entry entry = row.getEntry();
entry.setInflationTask(this);
}
@@ -806,8 +792,6 @@ public class NotificationInflater {
= Notification.Builder.recoverBuilder(mContext,
mSbn.getNotification());
applyChanges(recoveredBuilder);
Context packageContext = mSbn.getPackageContext(mContext);
Notification notification = mSbn.getNotification();
if (notification.isMediaNotification()) {
@@ -834,18 +818,6 @@ public class NotificationInflater {
}
}
/**
* Apply changes to the given notification builder, like adding smart actions suggested by
* a {@link android.service.notification.NotificationAssistantService}.
*/
private void applyChanges(Notification.Builder builder) {
if (mSmartActions != null) {
for (Notification.Action smartAction : mSmartActions) {
builder.addAction(smartAction);
}
}
}
private void handleError(Exception e) {
mRow.getEntry().onInflationTaskFinished();
StatusBarNotification sbn = mRow.getStatusBarNotification();

View File

@@ -72,6 +72,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -390,10 +392,16 @@ public class NotificationDataTest extends SysuiTestCase {
@Test
public void testCreateNotificationDataEntry_RankingUpdate() {
Ranking ranking = mock(Ranking.class);
initStatusBarNotification(false);
ArrayList<Notification.Action> smartActions = new ArrayList<>();
smartActions.add(createAction());
when(ranking.getSmartActions()).thenReturn(smartActions);
List<Notification.Action> appGeneratedSmartActions =
Collections.singletonList(createContextualAction("appGeneratedAction"));
mMockStatusBarNotification.getNotification().actions =
appGeneratedSmartActions.toArray(new Notification.Action[0]);
List<Notification.Action> systemGeneratedSmartActions =
Collections.singletonList(createAction("systemGeneratedAction"));
when(ranking.getSmartActions()).thenReturn(systemGeneratedSmartActions);
when(ranking.getChannel()).thenReturn(NOTIFICATION_CHANNEL);
@@ -407,7 +415,7 @@ public class NotificationDataTest extends SysuiTestCase {
NotificationData.Entry entry =
new NotificationData.Entry(mMockStatusBarNotification, ranking);
assertEquals(smartActions, entry.smartActions);
assertEquals(systemGeneratedSmartActions, entry.systemGeneratedSmartActions);
assertEquals(NOTIFICATION_CHANNEL, entry.channel);
assertEquals(Ranking.USER_SENTIMENT_NEGATIVE, entry.userSentiment);
assertEquals(snoozeCriterions, entry.snoozeCriteria);
@@ -459,10 +467,20 @@ public class NotificationDataTest extends SysuiTestCase {
}
}
private Notification.Action createAction() {
private Notification.Action createContextualAction(String title) {
return new Notification.Action.Builder(
Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
"action",
title,
PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0))
.setSemanticAction(
Notification.Action.SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION)
.build();
}
private Notification.Action createAction(String title) {
return new Notification.Action.Builder(
Icon.createWithResource(getContext(), android.R.drawable.sym_def_app_icon),
title,
PendingIntent.getBroadcast(getContext(), 0, new Intent("Action"), 0)).build();
}
}

View File

@@ -438,8 +438,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow).setEntry(eq(mEntry));
assertEquals(1, mEntry.smartActions.size());
assertEquals("action", mEntry.smartActions.get(0).title);
assertEquals(1, mEntry.systemGeneratedSmartActions.size());
assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
}
@Test
@@ -453,7 +453,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow, never()).setEntry(eq(mEntry));
assertEquals(0, mEntry.smartActions.size());
assertEquals(0, mEntry.systemGeneratedSmartActions.size());
}
@Test
@@ -467,8 +467,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow, never()).setEntry(eq(mEntry));
assertEquals(1, mEntry.smartActions.size());
assertEquals("action", mEntry.smartActions.get(0).title);
assertEquals(1, mEntry.systemGeneratedSmartActions.size());
assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
}
@Test
@@ -482,8 +482,8 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
mEntryManager.updateNotificationRanking(mRankingMap);
verify(mRow, never()).setEntry(eq(mEntry));
assertEquals(1, mEntry.smartActions.size());
assertEquals("action", mEntry.smartActions.get(0).title);
assertEquals(1, mEntry.systemGeneratedSmartActions.size());
assertEquals("action", mEntry.systemGeneratedSmartActions.get(0).title);
}
private Notification.Action createAction() {

View File

@@ -5549,7 +5549,7 @@ public class NotificationManagerService extends SystemService {
ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
ArrayList<Integer> suppressVisuallyBefore = new ArrayList<>(N);
ArrayList<ArrayList<Notification.Action>> smartActionsBefore = new ArrayList<>(N);
ArrayList<ArrayList<Notification.Action>> systemSmartActionsBefore = new ArrayList<>(N);
ArrayList<ArrayList<CharSequence>> smartRepliesBefore = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
final NotificationRecord r = mNotificationList.get(i);
@@ -5562,7 +5562,7 @@ public class NotificationManagerService extends SystemService {
snoozeCriteriaBefore.add(r.getSnoozeCriteria());
userSentimentBefore.add(r.getUserSentiment());
suppressVisuallyBefore.add(r.getSuppressedVisualEffects());
smartActionsBefore.add(r.getSmartActions());
systemSmartActionsBefore.add(r.getSystemGeneratedSmartActions());
smartRepliesBefore.add(r.getSmartReplies());
mRankingHelper.extractSignals(r);
}
@@ -5579,7 +5579,8 @@ public class NotificationManagerService extends SystemService {
|| !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())
|| !Objects.equals(suppressVisuallyBefore.get(i),
r.getSuppressedVisualEffects())
|| !Objects.equals(smartActionsBefore.get(i), r.getSmartActions())
|| !Objects.equals(systemSmartActionsBefore.get(i),
r.getSystemGeneratedSmartActions())
|| !Objects.equals(smartRepliesBefore.get(i), r.getSmartReplies())) {
mHandler.scheduleSendRankingUpdate();
return;
@@ -6581,7 +6582,7 @@ public class NotificationManagerService extends SystemService {
Bundle showBadge = new Bundle();
Bundle userSentiment = new Bundle();
Bundle hidden = new Bundle();
Bundle smartActions = new Bundle();
Bundle systemGeneratedSmartActions = new Bundle();
Bundle smartReplies = new Bundle();
Bundle audiblyAlerted = new Bundle();
Bundle noisy = new Bundle();
@@ -6612,7 +6613,8 @@ public class NotificationManagerService extends SystemService {
showBadge.putBoolean(key, record.canShowBadge());
userSentiment.putInt(key, record.getUserSentiment());
hidden.putBoolean(key, record.isHidden());
smartActions.putParcelableArrayList(key, record.getSmartActions());
systemGeneratedSmartActions.putParcelableArrayList(key,
record.getSystemGeneratedSmartActions());
smartReplies.putCharSequenceArrayList(key, record.getSmartReplies());
audiblyAlerted.putBoolean(key, record.getAudiblyAlerted());
noisy.putBoolean(key, record.getSound() != null || record.getVibration() != null);
@@ -6627,7 +6629,7 @@ public class NotificationManagerService extends SystemService {
return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden,
smartActions, smartReplies, audiblyAlerted, noisy);
systemGeneratedSmartActions, smartReplies, audiblyAlerted, noisy);
}
boolean hasCompanionDevice(ManagedServiceInfo info) {

View File

@@ -163,7 +163,11 @@ public final class NotificationRecord {
private Light mLight;
private String mGroupLogTag;
private String mChannelIdLogTag;
private ArrayList<Notification.Action> mSmartActions;
/**
* This list contains system generated smart actions from NAS, app-generated smart actions are
* stored in Notification.actions marked as SEMANTIC_ACTION_CONTEXTUAL_SUGGESTION.
*/
private ArrayList<Notification.Action> mSystemGeneratedSmartActions;
private ArrayList<CharSequence> mSmartReplies;
private final List<Adjustment> mAdjustments;
@@ -653,10 +657,11 @@ public final class NotificationRecord {
}
}
if (signals.containsKey(Adjustment.KEY_SMART_ACTIONS)) {
setSmartActions(signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
setSystemGeneratedSmartActions(
signals.getParcelableArrayList(Adjustment.KEY_SMART_ACTIONS));
MetricsLogger.action(getAdjustmentLogMaker()
.addTaggedData(MetricsEvent.ADJUSTMENT_KEY_SMART_ACTIONS,
getSmartActions().size()));
getSystemGeneratedSmartActions().size()));
}
if (signals.containsKey(Adjustment.KEY_SMART_REPLIES)) {
setSmartReplies(signals.getCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES));
@@ -1132,12 +1137,13 @@ public final class NotificationRecord {
mHasSeenSmartReplies = hasSeenSmartReplies;
}
public void setSmartActions(ArrayList<Notification.Action> smartActions) {
mSmartActions = smartActions;
public void setSystemGeneratedSmartActions(
ArrayList<Notification.Action> systemGeneratedSmartActions) {
mSystemGeneratedSmartActions = systemGeneratedSmartActions;
}
public ArrayList<Notification.Action> getSmartActions() {
return mSmartActions;
public ArrayList<Notification.Action> getSystemGeneratedSmartActions() {
return mSystemGeneratedSmartActions;
}
public void setSmartReplies(ArrayList<CharSequence> smartReplies) {

View File

@@ -72,7 +72,7 @@ public class NotificationAdjustmentExtractorTest extends UiServiceTestCase {
assertTrue(r.getGroupKey().contains(GroupHelper.AUTOGROUP_KEY));
assertEquals(people, r.getPeopleOverride());
assertEquals(snoozeCriteria, r.getSnoozeCriteria());
assertEquals(smartActions, r.getSmartActions());
assertEquals(smartActions, r.getSystemGeneratedSmartActions());
}
@Test

View File

@@ -708,14 +708,14 @@ public class NotificationRecordTest extends UiServiceTestCase {
true /* defaultSound */, false /* buzzy */, false /* defaultBuzz */,
false /* lights */, false /* defaultLights */, groupId /* group */);
NotificationRecord record = new NotificationRecord(mMockContext, sbn, channel);
assertNull(record.getSmartActions());
assertNull(record.getSystemGeneratedSmartActions());
ArrayList<Notification.Action> smartActions = new ArrayList<>();
smartActions.add(new Notification.Action.Builder(
Icon.createWithResource(getContext(), R.drawable.btn_default),
"text", null).build());
record.setSmartActions(smartActions);
assertEquals(smartActions, record.getSmartActions());
record.setSystemGeneratedSmartActions(smartActions);
assertEquals(smartActions, record.getSystemGeneratedSmartActions());
}
@Test