diff --git a/api/current.txt b/api/current.txt index 99ea60f719265..e2d31d90ff3a3 100755 --- a/api/current.txt +++ b/api/current.txt @@ -5370,6 +5370,7 @@ package android.app { field public static final android.os.Parcelable.Creator 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 diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index df37a02047b57..450efdf166569 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -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 filterOutContextualActions( + List actions) { + List 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 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 snoozeCriteria; public int userSentiment = Ranking.USER_SENTIMENT_NEUTRAL; + /** Smart Actions provided by the NotificationAssistantService. */ @NonNull - public List smartActions = Collections.emptyList(); + public List 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] diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java index 3bea7db14313f..274d4b07b0179 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java @@ -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)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 5166e061c3ef2..c7876cf430266 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -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 smartActions) { - mNotificationInflater.setSmartActions(smartActions); - } - public void setUseIncreasedHeadsUpHeight(boolean use) { mUseIncreasedHeadsUpHeight = use; mNotificationInflater.setUsesIncreasedHeadsUpHeight(use); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java index 38d6b3593be24..e1c2f7359ce3a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInflater.java @@ -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 mSmartActions; private final ArrayMap mCachedContentViews = new ArrayMap<>(); public NotificationInflater(ExpandableNotificationRow row) { @@ -161,10 +157,6 @@ public class NotificationInflater { mUsesIncreasedHeight = usesIncreasedHeight; } - public void setSmartActions(List 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 mSmartActions; private AsyncInflationTask(StatusBarNotification notification, @InflationFlag int reInflateFlags, ArrayMap cachedContentViews, ExpandableNotificationRow row, boolean isLowPriority, boolean isChildInGroup, boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient, - InflationCallback callback, RemoteViews.OnClickHandler remoteViewClickHandler, - List 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(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java index 8e6bfe3a5f916..f59bfaebb31d5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationDataTest.java @@ -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 smartActions = new ArrayList<>(); - smartActions.add(createAction()); - when(ranking.getSmartActions()).thenReturn(smartActions); + List appGeneratedSmartActions = + Collections.singletonList(createContextualAction("appGeneratedAction")); + mMockStatusBarNotification.getNotification().actions = + appGeneratedSmartActions.toArray(new Notification.Action[0]); + + List 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(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java index 9f8a5cc0afdfa..d1fe5af406d37 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java @@ -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() { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 73ca8aeb7aec5..6195ed9e0d795 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5549,7 +5549,7 @@ public class NotificationManagerService extends SystemService { ArrayList> snoozeCriteriaBefore = new ArrayList<>(N); ArrayList userSentimentBefore = new ArrayList<>(N); ArrayList suppressVisuallyBefore = new ArrayList<>(N); - ArrayList> smartActionsBefore = new ArrayList<>(N); + ArrayList> systemSmartActionsBefore = new ArrayList<>(N); ArrayList> 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) { diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index a11b03f686670..1a9257cf17fc3 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -163,7 +163,11 @@ public final class NotificationRecord { private Light mLight; private String mGroupLogTag; private String mChannelIdLogTag; - private ArrayList 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 mSystemGeneratedSmartActions; private ArrayList mSmartReplies; private final List 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 smartActions) { - mSmartActions = smartActions; + public void setSystemGeneratedSmartActions( + ArrayList systemGeneratedSmartActions) { + mSystemGeneratedSmartActions = systemGeneratedSmartActions; } - public ArrayList getSmartActions() { - return mSmartActions; + public ArrayList getSystemGeneratedSmartActions() { + return mSystemGeneratedSmartActions; } public void setSmartReplies(ArrayList smartReplies) { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java index f17a30ddb1b65..410ab8732a08f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAdjustmentExtractorTest.java @@ -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 diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 9b41fdd9fd68b..8690110ca66a3 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -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 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