Cover more PendingIntent whitelist scenarios.

BUG: 29480440
Change-Id: I961b765f40135efc06fbb3e5a4a94e8e333453da
This commit is contained in:
Felipe Leme
2016-06-27 16:13:18 -07:00
parent 79fad1a209
commit e928006e67
5 changed files with 175 additions and 28 deletions

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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();

View File

@@ -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);
}