Merge "Cover more PendingIntent whitelist scenarios." into nyc-mr1-dev
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
index b89cf93..6669af5 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
@@ -132,16 +132,29 @@
             setDozeMode(true);
             assertBackgroundNetworkAccess(false);
 
-            sendNotification(42);
-            assertBackgroundNetworkAccess(true);
-            // Make sure access is disabled after it expires
-            SystemClock.sleep(NETWORK_TIMEOUT_MS);
-            assertBackgroundNetworkAccess(false);
+            testNotification(4, NOTIFICATION_TYPE_CONTENT);
+            testNotification(8, NOTIFICATION_TYPE_DELETE);
+            testNotification(15, NOTIFICATION_TYPE_FULL_SCREEN);
+            testNotification(16, NOTIFICATION_TYPE_BUNDLE);
+            testNotification(23, NOTIFICATION_TYPE_ACTION);
+            testNotification(42, NOTIFICATION_TYPE_ACTION_BUNDLE);
+            testNotification(108, NOTIFICATION_TYPE_ACTION_REMOTE_INPUT);
         } finally {
             resetDeviceIdleSettings();
         }
     }
 
+    private void testNotification(int id, String type) throws Exception {
+        sendNotification(id, type);
+        assertBackgroundNetworkAccess(true);
+        if (type.equals(NOTIFICATION_TYPE_ACTION)) {
+            // Make sure access is disabled after it expires. Since this check considerably slows
+            // downs the CTS tests, do it just once.
+            SystemClock.sleep(NETWORK_TIMEOUT_MS);
+            assertBackgroundNetworkAccess(false);
+        }
+    }
+
     // Must override so it only tests foreground service - once an app goes to foreground, device
     // leaves Doze Mode.
     @Override
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 439fbbe..d04aa0f 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -69,6 +69,18 @@
             "com.android.cts.net.hostside.app2.extra.RECEIVER_NAME";
     private static final String EXTRA_NOTIFICATION_ID =
             "com.android.cts.net.hostside.app2.extra.NOTIFICATION_ID";
+    private static final String EXTRA_NOTIFICATION_TYPE =
+            "com.android.cts.net.hostside.app2.extra.NOTIFICATION_TYPE";
+
+    protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
+    protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
+    protected static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
+    protected static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
+    protected static final String NOTIFICATION_TYPE_ACTION = "ACTION";
+    protected static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
+    protected static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
+
+
     private static final String NETWORK_STATUS_SEPARATOR = "\\|";
     private static final int SECOND_IN_MS = 1000;
     static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
@@ -735,10 +747,12 @@
                 + "--receiver-foreground --receiver-registered-only");
     }
 
-    protected void sendNotification(int notificationId) {
+    protected void sendNotification(int notificationId, String notificationType) {
         final Intent intent = new Intent(ACTION_SEND_NOTIFICATION);
         intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId);
-        Log.d(TAG, "Sending broadcast: " + intent);
+        intent.putExtra(EXTRA_NOTIFICATION_TYPE, notificationType);
+        Log.d(TAG, "Sending notification broadcast (id=" + notificationId + ", type="
+                + notificationType + ": " + intent);
         mContext.sendBroadcast(intent);
     }
 
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
index b9c3031..0893511 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
@@ -16,7 +16,10 @@
 package com.android.cts.net.hostside;
 
 import android.app.Notification;
+import android.app.PendingIntent;
 import android.app.PendingIntent.CanceledException;
+import android.app.RemoteInput;
+import android.os.Bundle;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
@@ -40,22 +43,75 @@
             Log.v(TAG, "ignoring notification from a different package");
             return;
         }
+        final PendingIntentSender sender = new PendingIntentSender();
         final Notification notification = sbn.getNotification();
-        if (notification.actions == null) {
-            Log.w(TAG, "ignoring notification without an action");
+        if (notification.contentIntent != null) {
+            sender.send("content", notification.contentIntent);
         }
-        for (Notification.Action action : notification.actions) {
-            Log.i(TAG, "Sending pending intent " + action.actionIntent);
-            try {
-                action.actionIntent.send();
-            } catch (CanceledException e) {
-                Log.w(TAG, "Pending Intent canceled");
+        if (notification.deleteIntent != null) {
+            sender.send("delete", notification.deleteIntent);
+        }
+        if (notification.fullScreenIntent != null) {
+            sender.send("full screen", notification.fullScreenIntent);
+        }
+        if (notification.actions != null) {
+            for (Notification.Action action : notification.actions) {
+                sender.send("action", action.actionIntent);
+                sender.send("action extras", action.getExtras());
+                final RemoteInput[] remoteInputs = action.getRemoteInputs();
+                if (remoteInputs != null && remoteInputs.length > 0) {
+                    for (RemoteInput remoteInput : remoteInputs) {
+                        sender.send("remote input extras", remoteInput.getExtras());
+                    }
+                }
             }
         }
+        sender.send("notification extras", notification.extras);
     }
 
     static String getId() {
         return String.format("%s/%s", MyNotificationListenerService.class.getPackage().getName(),
                 MyNotificationListenerService.class.getName());
     }
+
+    private static final class PendingIntentSender {
+        private PendingIntent mSentIntent = null;
+        private String mReason = null;
+
+        private void send(String reason, PendingIntent pendingIntent) {
+            if (pendingIntent == null) {
+                // Could happen on action that only has extras
+                Log.v(TAG, "Not sending null pending intent for " + reason);
+                return;
+            }
+            if (mSentIntent != null || mReason != null) {
+                // Sanity check: make sure test case set up just one pending intent in the
+                // notification, otherwise it could pass because another pending intent caused the
+                // whitelisting.
+                throw new IllegalStateException("Already sent a PendingIntent (" + mSentIntent
+                        + ") for reason '" + mReason + "' when requested another for '" + reason
+                        + "' (" + pendingIntent + ")");
+            }
+            Log.i(TAG, "Sending pending intent for " + reason + ":" + pendingIntent);
+            try {
+                pendingIntent.send();
+                mSentIntent = pendingIntent;
+                mReason = reason;
+            } catch (CanceledException e) {
+                Log.w(TAG, "Pending intent " + pendingIntent + " canceled");
+            }
+        }
+
+        private void send(String reason, Bundle extras) {
+            if (extras != null) {
+                for (String key : extras.keySet()) {
+                    Object value = extras.get(key);
+                    if (value instanceof PendingIntent) {
+                        send(reason + " with key '" + key + "'", (PendingIntent) value);
+                    }
+                }
+            }
+        }
+
+    }
 }
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java
index f02f651..8806e3b 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/Common.java
@@ -43,6 +43,16 @@
             "com.android.cts.net.hostside.app2.extra.RECEIVER_NAME";
     static final String EXTRA_NOTIFICATION_ID =
             "com.android.cts.net.hostside.app2.extra.NOTIFICATION_ID";
+    static final String EXTRA_NOTIFICATION_TYPE =
+            "com.android.cts.net.hostside.app2.extra.NOTIFICATION_TYPE";
+
+    static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
+    static final String NOTIFICATION_TYPE_DELETE = "DELETE";
+    static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
+    static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
+    static final String NOTIFICATION_TYPE_ACTION = "ACTION";
+    static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
+    static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
 
     static int getUid(Context context) {
         final String packageName = context.getPackageName();
diff --git a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
index 60e5de1..3b82f42 100644
--- a/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
+++ b/hostsidetests/net/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java
@@ -25,8 +25,16 @@
 import static com.android.cts.net.hostside.app2.Common.ACTION_SEND_NOTIFICATION;
 import static com.android.cts.net.hostside.app2.Common.EXTRA_ACTION;
 import static com.android.cts.net.hostside.app2.Common.EXTRA_NOTIFICATION_ID;
+import static com.android.cts.net.hostside.app2.Common.EXTRA_NOTIFICATION_TYPE;
 import static com.android.cts.net.hostside.app2.Common.EXTRA_RECEIVER_NAME;
 import static com.android.cts.net.hostside.app2.Common.MANIFEST_RECEIVER;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
+import static com.android.cts.net.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
 import static com.android.cts.net.hostside.app2.Common.TAG;
 import static com.android.cts.net.hostside.app2.Common.getUid;
 
@@ -34,6 +42,7 @@
 import android.app.Notification.Action;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.RemoteInput;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -230,21 +239,66 @@
      */
     private void sendNotification(Context context, Intent intent) {
         final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
+        final String notificationType = intent.getStringExtra(EXTRA_NOTIFICATION_TYPE);
+        Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType
+                + ", intent=" + intent);
         final Intent serviceIntent = new Intent(context, MyService.class);
-        final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent, 0);
-        final Bundle badBundle = new Bundle();
-        badBundle.putCharSequence("parcelable", "I am not");
-        final Action action = new Action.Builder(
-                R.drawable.ic_notification, "ACTION", pendingIntent)
-                .addExtras(badBundle)
-                .build();
+        final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
+                notificationId);
+        final Bundle bundle = new Bundle();
+        bundle.putCharSequence("parcelable", "I am not");
 
-        final Notification notification = new Notification.Builder(context)
-                .setSmallIcon(R.drawable.ic_notification)
-                .setContentTitle("Light, Cameras...")
-                .setContentIntent(pendingIntent)
-                .addAction(action)
-                .build();
+        final Notification.Builder builder = new Notification.Builder(context)
+                .setSmallIcon(R.drawable.ic_notification);
+
+        Action action = null;
+        switch (notificationType) {
+            case NOTIFICATION_TYPE_CONTENT:
+                builder
+                    .setContentTitle("Light, Cameras...")
+                    .setContentIntent(pendingIntent);
+                break;
+            case NOTIFICATION_TYPE_DELETE:
+                builder.setDeleteIntent(pendingIntent);
+                break;
+            case NOTIFICATION_TYPE_FULL_SCREEN:
+                builder.setFullScreenIntent(pendingIntent, true);
+                break;
+            case NOTIFICATION_TYPE_BUNDLE:
+                bundle.putParcelable("Magnum P.I. (Pending Intent)", pendingIntent);
+                builder.setExtras(bundle);
+                break;
+            case NOTIFICATION_TYPE_ACTION:
+                action = new Action.Builder(
+                        R.drawable.ic_notification, "ACTION", pendingIntent)
+                        .build();
+                builder.addAction(action);
+                break;
+            case NOTIFICATION_TYPE_ACTION_BUNDLE:
+                bundle.putParcelable("Magnum A.P.I. (Action Pending Intent)", pendingIntent);
+                action = new Action.Builder(
+                        R.drawable.ic_notification, "ACTION WITH BUNDLE", null)
+                        .addExtras(bundle)
+                        .build();
+                builder.addAction(action);
+                break;
+            case NOTIFICATION_TYPE_ACTION_REMOTE_INPUT:
+                bundle.putParcelable("Magnum R.I. (Remote Input)", null);
+                final RemoteInput remoteInput = new RemoteInput.Builder("RI")
+                    .addExtras(bundle)
+                    .build();
+                action = new Action.Builder(
+                        R.drawable.ic_notification, "ACTION WITH REMOTE INPUT", pendingIntent)
+                        .addRemoteInput(remoteInput)
+                        .build();
+                builder.addAction(action);
+                break;
+            default:
+                Log.e(TAG, "Unknown notification type: " + notificationType);
+                return;
+        }
+
+        final Notification notification = builder.build();
         ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
             .notify(notificationId, notification);
     }