From e928006e6785b91d8b1e92125536ca979071bb09 Mon Sep 17 00:00:00 2001 From: Felipe Leme Date: Mon, 27 Jun 2016 16:13:18 -0700 Subject: [PATCH] Cover more PendingIntent whitelist scenarios. BUG: 29480440 Change-Id: I961b765f40135efc06fbb3e5a4a94e8e333453da --- .../hostside/AbstractDozeModeTestCase.java | 23 ++++-- ...ractRestrictBackgroundNetworkTestCase.java | 18 ++++- .../MyNotificationListenerService.java | 72 +++++++++++++++-- .../android/cts/net/hostside/app2/Common.java | 10 +++ .../hostside/app2/MyBroadcastReceiver.java | 80 ++++++++++++++++--- 5 files changed, 175 insertions(+), 28 deletions(-) diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java index b89cf93b99..6669af5edd 100644 --- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java @@ -132,16 +132,29 @@ abstract class AbstractDozeModeTestCase extends AbstractRestrictBackgroundNetwor 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/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java index 439fbbe0c9..d04aa0f0db 100644 --- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java +++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java @@ -69,6 +69,18 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation "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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation + "--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/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java index b9c303107c..0893511071 100644 --- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java +++ b/tests/cts/hostside/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 @@ public class MyNotificationListenerService extends NotificationListenerService { 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/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java index f02f651f06..8806e3b970 100644 --- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java +++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/Common.java @@ -43,6 +43,16 @@ public final class Common { "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/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java index 60e5de1b9a..3b82f42a67 100644 --- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java +++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyBroadcastReceiver.java @@ -25,8 +25,16 @@ import static com.android.cts.net.hostside.app2.Common.ACTION_RECEIVER_READY; 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; 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 @@ public class MyBroadcastReceiver extends BroadcastReceiver { */ 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); }