Tests for delaying notification posting

Test: make cts-verifier
Change-Id: I9efb7fd738fc103843519fb425ebf82eee7e14be
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 48c4ec7..eeeffbf 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -1449,6 +1449,7 @@
     <string name="nls_note_received">Check that notification was received.</string>
     <string name="nls_payload_intact">Check that notification payload was intact.</string>
     <string name="nas_adjustment_payload_intact">Check that the Assistant can adjust notifications.</string>
+    <string name="nas_adjustment_enqueue_payload_intact">Check that the Assistant can adjust notifications on enqueue.</string>
     <string name="nas_create_channel">Check that the Assistant can create a Notification Channel for another app.</string>
     <string name="nas_update_channel">Check that the Assistant can update a Notification Channel for another app.</string>
     <string name="nas_block_channel">Check that the Assistant can block a Notification Channel.</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
index 1b9c914..5486911 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockAssistant.java
@@ -17,6 +17,7 @@
 
 import android.app.Activity;
 import android.app.NotificationChannel;
+import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -34,6 +35,7 @@
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public class MockAssistant extends NotificationAssistantService {
@@ -56,6 +58,7 @@
     public static final String SERVICE_UPDATE_CHANNEL = SERVICE_BASE + "UPDATE_CHANNEL";
     public static final String SERVICE_DELETE_CHANNEL = SERVICE_BASE + "DELETE_CHANNEL";
     public static final String SERVICE_CHECK_CHANNELS = SERVICE_BASE + "CHECK_CHANNELS";
+    public static final String SERVICE_ADJUST_ENQUEUE = SERVICE_BASE + "ADJUST_ENQUEUE";
 
     static final String EXTRA_PAYLOAD = "PAYLOAD";
     static final String EXTRA_INT = "INT";
@@ -91,6 +94,7 @@
     private Set<String> mTestPackages = new HashSet<>();
     private BroadcastReceiver mReceiver;
     private int mDND = -1;
+    private boolean adjustEnqueue = false;
 
     @Override
     public void onCreate() {
@@ -109,35 +113,30 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 String action = intent.getAction();
+                Log.d(TAG, action);
                 if (SERVICE_CHECK.equals(action)) {
-                    Log.d(TAG, "SERVICE_CHECK");
                     setResultCode(Activity.RESULT_OK);
                 } else if (SERVICE_ENQUEUED.equals(action)) {
-                    Log.d(TAG, "SERVICE_ENQUEUED");
                     Bundle bundle = new Bundle();
                     bundle.putStringArrayList(EXTRA_PAYLOAD, mEnqueued);
                     setResultExtras(bundle);
                     setResultCode(Activity.RESULT_OK);
                 } else if (SERVICE_POSTED.equals(action)) {
-                    Log.d(TAG, "SERVICE_POSTED");
                     Bundle bundle = new Bundle();
                     bundle.putStringArrayList(EXTRA_PAYLOAD, mPosted);
                     setResultExtras(bundle);
                     setResultCode(Activity.RESULT_OK);
                 } else if (SERVICE_DND.equals(action)) {
-                    Log.d(TAG, "SERVICE_DND");
                     Bundle bundle = new Bundle();
                     bundle.putInt(EXTRA_INT, mDND);
                     setResultExtras(bundle);
                     setResultCode(Activity.RESULT_OK);
                 } else if (SERVICE_ORDER.equals(action)) {
-                    Log.d(TAG, "SERVICE_ORDER");
                     Bundle bundle = new Bundle();
                     bundle.putStringArrayList(EXTRA_PAYLOAD, mOrder);
                     setResultExtras(bundle);
                     setResultCode(Activity.RESULT_OK);
                 } else if (SERVICE_PAYLOADS.equals(action)) {
-                    Log.d(TAG, "SERVICE_PAYLOADS");
                     Bundle bundle = new Bundle();
                     ArrayList<Bundle> payloadData = new ArrayList<>(mNotifications.size());
                     for (Bundle notiStats : mNotifications.values()) {
@@ -147,13 +146,11 @@
                     setResultExtras(bundle);
                     setResultCode(Activity.RESULT_OK);
                 } else if (SERVICE_REMOVED.equals(action)) {
-                    Log.d(TAG, "SERVICE_REMOVED");
                     Bundle bundle = new Bundle();
                     bundle.putStringArrayList(EXTRA_PAYLOAD, mRemoved);
                     setResultExtras(bundle);
                     setResultCode(Activity.RESULT_OK);
                 } else if (SERVICE_REMOVED_REASON.equals(action)) {
-                    Log.d(TAG, "SERVICE_REMOVED_REASON");
                     Bundle bundle = new Bundle();
                     ArrayList<Bundle> payloadData = new ArrayList<>(mRemovedReason.size());
                     for (Bundle notiStats : mRemovedReason.values()) {
@@ -163,7 +160,6 @@
                     setResultExtras(bundle);
                     setResultCode(Activity.RESULT_OK);
                 } else if (SERVICE_CLEAR_ONE.equals(action)) {
-                    Log.d(TAG, "SERVICE_CLEAR_ONE");
                     String tag = intent.getStringExtra(EXTRA_TAG);
                     String key = mNotificationKeys.get(tag);
                     if (key != null) {
@@ -172,10 +168,8 @@
                         Log.w(TAG, "Notification does not exist: " + tag);
                     }
                 } else if (SERVICE_CLEAR_ALL.equals(action)) {
-                    Log.d(TAG, "SERVICE_CLEAR_ALL");
                     MockAssistant.this.cancelAllNotifications();
                 } else if (SERVICE_RESET.equals(action)) {
-                    Log.d(TAG, "SERVICE_RESET");
                     resetData();
                 } else if (SERVICE_ADJUSTMENT.equals(action)) {
                     String tag = intent.getStringExtra(EXTRA_TAG);
@@ -209,6 +203,8 @@
                     String pkg = intent.getStringExtra(EXTRA_PKG);
                     String id = intent.getStringExtra(EXTRA_TAG);
                     MockAssistant.this.deleteNotificationChannel(pkg, id);
+                } else if (SERVICE_ADJUST_ENQUEUE.equals(action)) {
+                    adjustEnqueue = true;
                 } else {
                     Log.w(TAG, "unknown action");
                     setResultCode(Activity.RESULT_CANCELED);
@@ -232,6 +228,7 @@
         filter.addAction(SERVICE_CREATE_CHANNEL);
         filter.addAction(SERVICE_DELETE_CHANNEL);
         filter.addAction(SERVICE_UPDATE_CHANNEL);
+        filter.addAction(SERVICE_ADJUST_ENQUEUE);
         registerReceiver(mReceiver, filter);
     }
 
@@ -294,11 +291,17 @@
     public Adjustment onNotificationEnqueued(StatusBarNotification sbn, int importance,
             boolean user) {
         if (!mTestPackages.contains(sbn.getPackageName())) { return null; }
-        Log.d(TAG, "posted: " + sbn.getTag());
+        Log.d(TAG, "enqueued: " + sbn.getTag());
         mEnqueued.add(sbn.getTag());
-        mNotifications.put(sbn.getKey(), packNotification(sbn));
-        mNotificationKeys.put(sbn.getTag(), sbn.getKey());
 
+        if (adjustEnqueue) {
+            Map<String, Bundle> adjustments =
+                    NotificationAssistantVerifierActivity.getAdjustments();
+            Bundle signals = adjustments.get(sbn.getNotification().getSortKey());
+            Adjustment adjustment = new Adjustment(sbn.getPackageName(),
+                    sbn.getKey(), signals, "", 0);
+            return adjustment;
+        }
         return null;
     }
 
@@ -391,6 +394,10 @@
         context.sendOrderedBroadcast(broadcast, null, catcher, null, RESULT_NO_SERVER, null, null);
     }
 
+    public static void adjustEnqueue(Context context) {
+        sendCommand(context, SERVICE_ADJUST_ENQUEUE, null, 0);
+    }
+
     public static void clearOne(Context context, String tag, int code) {
         sendCommand(context, SERVICE_CLEAR_ONE, tag, code);
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
index 2c04b9cb..d208fba 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAssistantVerifierActivity.java
@@ -44,6 +44,7 @@
 import android.provider.Settings.Secure;
 import android.service.notification.Adjustment;
 import android.service.notification.SnoozeCriterion;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -53,6 +54,7 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
@@ -106,11 +108,12 @@
         tests.add(new DismissOneTest());
         tests.add(new DismissOneWithReasonTest());
         tests.add(new DismissAllTest());
-        tests.add(new AdjustNotificationTest());
         tests.add(new CreateChannelTest());
         tests.add(new UpdateChannelTest());
         tests.add(new DeleteChannelTest());
         tests.add(new UpdateLiveChannelTest());
+        tests.add(new AdjustNotificationTest());
+        tests.add(new AdjustEnqueuedNotificationTest());
         tests.add(new IsDisabledTest());
         tests.add(new ServiceStoppedTest());
         tests.add(new NotificationNotEnqueuedTest());
@@ -146,7 +149,8 @@
         mPackageString = "com.android.cts.verifier";
 
         Notification n1 = new Notification.Builder(mContext)
-                .setContentTitle("ClearTest 1")
+                .setContentTitle("One")
+                .setSortKey(Adjustment.KEY_CHANNEL_ID)
                 .setContentText(mTag1.toString())
                 .setPriority(Notification.PRIORITY_LOW)
                 .setSmallIcon(mIcon1)
@@ -160,7 +164,8 @@
         mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
 
         Notification n2 = new Notification.Builder(mContext)
-                .setContentTitle("ClearTest 2")
+                .setContentTitle("Two")
+                .setSortKey(Adjustment.KEY_PEOPLE)
                 .setContentText(mTag2.toString())
                 .setPriority(Notification.PRIORITY_HIGH)
                 .setSmallIcon(mIcon2)
@@ -174,7 +179,8 @@
         mFlag2 = Notification.FLAG_AUTO_CANCEL;
 
         Notification n3 = new Notification.Builder(mContext)
-                .setContentTitle("ClearTest 3")
+                .setContentTitle("Three")
+                .setSortKey(Adjustment.KEY_SNOOZE_CRITERIA)
                 .setContentText(mTag3.toString())
                 .setPriority(Notification.PRIORITY_LOW)
                 .setSmallIcon(mIcon3)
@@ -698,19 +704,13 @@
     }
 
     private class AdjustNotificationTest extends InteractiveTestCase {
-        private Bundle bundle1 = new Bundle();
-        private Bundle bundle2 = new Bundle();
-        private Bundle bundle3 = new Bundle();
-        private SnoozeCriterion snooze1 = new SnoozeCriterion("id1", "1", "2");
-        private SnoozeCriterion snooze2 = new SnoozeCriterion("id2", "2", "3");
-        private String people1 = "people1";
-        private String people2 = "people2";
         private ArrayList<String> people;
         private ArrayList<SnoozeCriterion> snooze;
         private NotificationChannel originalChannel = new NotificationChannel("original", "new",
                 NotificationManager.IMPORTANCE_LOW);
         private NotificationChannel newChannel = new NotificationChannel("new", "new",
                 NotificationManager.IMPORTANCE_LOW);
+        private Map<String, Bundle> adjustments;
 
         @Override
         View inflate(ViewGroup parent) {
@@ -729,27 +729,38 @@
             } catch (Exception e) {
                 Log.e(TAG, "failed to create channel", e);
             }
+            adjustments = getAdjustments();
+            snooze = adjustments.get(Adjustment.KEY_SNOOZE_CRITERIA).getParcelableArrayList(
+                    Adjustment.KEY_SNOOZE_CRITERIA);
+            people = adjustments.get(Adjustment.KEY_PEOPLE).getStringArrayList(
+                    Adjustment.KEY_PEOPLE);
             sendNotifications(originalChannel);
             status = READY;
-            bundle1.putString(Adjustment.KEY_CHANNEL_ID, newChannel.getId());
-            people = new ArrayList<>();
-            people.add(people1);
-            people.add(people2);
-            bundle2.putStringArrayList(Adjustment.KEY_PEOPLE, people);
-            snooze = new ArrayList<>();
-            snooze.add(snooze1);
-            snooze.add(snooze2);
-            bundle3.putParcelableArrayList(Adjustment.KEY_SNOOZE_CRITERIA, snooze);
             delay();
         }
 
         @Override
         void test() {
             if (status == READY) {
-                MockAssistant.applyAdjustment(mContext, mTag1, bundle1);
-                MockAssistant.applyAdjustment(mContext, mTag2, bundle2);
-                MockAssistant.applyAdjustment(mContext, mTag3, bundle3);
-                status = RETEST;
+                MockAssistant.probeListenerPosted(mContext,
+                        new StringListResultCatcher() {
+                            @Override
+                            public void accept(List<String> result) {
+                                if (result != null && result.size() > 0 && result.contains(mTag1)) {
+                                    MockAssistant.applyAdjustment(mContext, mTag1,
+                                            adjustments.get(Adjustment.KEY_CHANNEL_ID));
+                                    MockAssistant.applyAdjustment(mContext, mTag2,
+                                            adjustments.get(Adjustment.KEY_PEOPLE));
+                                    MockAssistant.applyAdjustment(mContext, mTag3,
+                                            adjustments.get(Adjustment.KEY_SNOOZE_CRITERIA));
+                                    status = RETEST;
+                                } else {
+                                    logFail();
+                                    status = FAIL;
+                                }
+                                delay(3000);
+                            }
+                        });
             } else {
                 MockAssistant.probeListenerPayloads(mContext,
                         new MockAssistant.BundleListResultCatcher() {
@@ -839,6 +850,177 @@
         }
     }
 
+    private class AdjustEnqueuedNotificationTest extends InteractiveTestCase {
+        private ArrayList<String> people;
+        private ArrayList<SnoozeCriterion> snooze;
+        private NotificationChannel originalChannel = new NotificationChannel("original", "new",
+                NotificationManager.IMPORTANCE_LOW);
+        private NotificationChannel newChannel = new NotificationChannel("new", "new",
+                NotificationManager.IMPORTANCE_LOW);
+        private Map<String, Bundle> adjustments;
+
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nas_adjustment_enqueue_payload_intact);
+        }
+
+        @Override
+        void setUp() {
+            MockAssistant.adjustEnqueue(mContext);
+            try {
+                mNm.createNotificationChannel(newChannel, (createdChannel) -> {}, null);
+            } catch (Exception e) {
+                Log.e(TAG, "failed to create channel", e);
+            }
+            try {
+                mNm.createNotificationChannel(originalChannel, (createdChannel) -> {}, null);
+            } catch (Exception e) {
+                Log.e(TAG, "failed to create channel", e);
+            }
+            adjustments = getAdjustments();
+            snooze = adjustments.get(Adjustment.KEY_SNOOZE_CRITERIA).getParcelableArrayList(
+                    Adjustment.KEY_SNOOZE_CRITERIA);
+            people = adjustments.get(Adjustment.KEY_PEOPLE).getStringArrayList(
+                    Adjustment.KEY_PEOPLE);
+            sendNotifications(originalChannel);
+            status = READY;
+            delay();
+        }
+
+        @Override
+        void test() {
+            if (status == READY) {
+                MockAssistant.probeListenerEnqueued(mContext,
+                        new StringListResultCatcher() {
+                            @Override
+                            public void accept(List<String> result) {
+                                if (result != null && result.size() > 0 && result.contains(mTag1)) {
+                                    status = RETEST;
+                                } else {
+                                    logFail();
+                                    status = FAIL;
+                                }
+                                next();
+                            }
+                        });
+            } else {
+                MockAssistant.probeListenerPayloads(mContext,
+                        new MockAssistant.BundleListResultCatcher() {
+                            @Override
+                            public void accept(ArrayList<Parcelable> result) {
+                                Set<String> found = new HashSet<>();
+                                if (result == null || result.size() == 0) {
+                                    status = FAIL;
+                                    return;
+                                }
+                                boolean pass = true;
+                                for (Parcelable payloadData : result) {
+                                    Bundle payload = (Bundle) payloadData;
+                                    pass &= checkEquals(mPackageString,
+                                            payload.getString(KEY_PACKAGE),
+                                            "data integrity test: notification package (%s, %s)");
+
+                                    String tag = payload.getString(KEY_TAG);
+                                    if (mTag1.equals(tag)) {
+                                        found.add(mTag1);
+                                        pass &= checkEquals(newChannel.getId(),
+                                                ((NotificationChannel) payload.getParcelable(
+                                                        KEY_CHANNEL)).getId(),
+                                                "data integrity test: notification channel ("
+                                                        + "%s, %s)");
+                                        pass &= checkEquals(null,
+                                                payload.getStringArray(KEY_PEOPLE),
+                                                "data integrity test, notification people ("
+                                                        + "%s, %s)");
+                                        pass &= checkEquals(null,
+                                                payload.getParcelableArray(KEY_SNOOZE_CRITERIA),
+                                                "data integrity test, notification snooze ("
+                                                        + "%s, %s)");
+                                    } else if (mTag2.equals(tag)) {
+                                        found.add(mTag2);
+                                        pass &= checkEquals(originalChannel.getId(),
+                                                ((NotificationChannel) payload.getParcelable(
+                                                        KEY_CHANNEL)).getId(),
+                                                "data integrity test: notification channel ("
+                                                        + "%s, %s)");
+                                        pass &= checkEquals(people.toArray(new String[]{}),
+                                                payload.getStringArray(KEY_PEOPLE),
+                                                "data integrity test, notification people ("
+                                                        + "%s, %s)");
+                                        pass &= checkEquals(null,
+                                                payload.getParcelableArray(KEY_SNOOZE_CRITERIA),
+                                                "data integrity test, notification snooze ("
+                                                        + "%s, %s)");
+                                    } else if (mTag3.equals(tag)) {
+                                        found.add(mTag3);
+                                        pass &= checkEquals(originalChannel.getId(),
+                                                ((NotificationChannel) payload.getParcelable(
+                                                        KEY_CHANNEL)).getId(),
+                                                "data integrity test: notification channel ("
+                                                        + "%s, %s)");
+                                        pass &= checkEquals(null,
+                                                payload.getStringArray(KEY_PEOPLE),
+                                                "data integrity test, notification people ("
+                                                        + "%s, %s)");;
+                                        pass &= checkEquals(snooze.toArray(new SnoozeCriterion[]{}),
+                                                payload.getParcelableArray(KEY_SNOOZE_CRITERIA),
+                                                "data integrity test, notification snooze ("
+                                                        + "%s, %s)");
+                                    } else {
+                                        pass = false;
+                                        logFail("unexpected notification tag: " + tag);
+                                    }
+                                }
+
+                                pass &= found.size() == 3;
+                                status = pass ? PASS : FAIL;
+                                next();
+                            }
+                        });
+            }
+            delay(6000);  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            mNm.deleteNotificationChannel(originalChannel.getId());
+            mNm.deleteNotificationChannel(newChannel.getId());
+            sleep(1000);
+            MockAssistant.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    public static Map<String, Bundle> getAdjustments() {
+        Map<String, Bundle> adjustments = new ArrayMap<>();
+        Bundle bundle1 = new Bundle();
+        Bundle bundle2 = new Bundle();
+        Bundle bundle3 = new Bundle();
+        SnoozeCriterion snooze1 = new SnoozeCriterion("id1", "1", "2");
+        SnoozeCriterion snooze2 = new SnoozeCriterion("id2", "2", "3");
+        String people1 = "people1";
+        String people2 = "people2";
+        ArrayList<String> people = new ArrayList<>();
+        ArrayList<SnoozeCriterion> snooze = new ArrayList<>();
+        NotificationChannel newChannel = new NotificationChannel("new", "new",
+                NotificationManager.IMPORTANCE_LOW);
+
+        bundle1.putString(Adjustment.KEY_CHANNEL_ID, newChannel.getId());
+        adjustments.put(Adjustment.KEY_CHANNEL_ID, bundle1);
+
+        people.add(people1);
+        people.add(people2);
+        bundle2.putStringArrayList(Adjustment.KEY_PEOPLE, people);
+        adjustments.put(Adjustment.KEY_PEOPLE, bundle2);
+
+        snooze.add(snooze1);
+        snooze.add(snooze2);
+        bundle3.putParcelableArrayList(Adjustment.KEY_SNOOZE_CRITERIA, snooze);
+        adjustments.put(Adjustment.KEY_SNOOZE_CRITERIA, bundle3);
+        return adjustments;
+    }
+
     private class CreateChannelTest extends InteractiveTestCase {
         private NotificationChannel channel = new NotificationChannel("channelForOtherPkg", "new",
                 NotificationManager.IMPORTANCE_LOW);
@@ -1049,7 +1231,11 @@
 
         @Override
         void setUp() {
-            MockAssistant.createChannel(mContext, THIS_PKG, channel);
+            try {
+                mNm.createNotificationChannel(channel, (createdChannel) -> {}, null);
+            } catch (Exception e) {
+                Log.e(TAG, "failed to create channel", e);
+            }
             sendNotifications(channel);
             status = READY;
             delay();
@@ -1070,7 +1256,7 @@
                                     logFail();
                                     status = FAIL;
                                 }
-                                next();
+                                delay(3000);
                             }
                         });
             } else if (status == RETEST) {