Added test for whitelisting pending intent for Doze Mode.
BUG: 28818704
Change-Id: I927364e78cd73133899d67be23e0b274829686af
diff --git a/tests/cts/hostside/app/AndroidManifest.xml b/tests/cts/hostside/app/AndroidManifest.xml
index c7978f8..0598a3b 100644
--- a/tests/cts/hostside/app/AndroidManifest.xml
+++ b/tests/cts/hostside/app/AndroidManifest.xml
@@ -31,6 +31,14 @@
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
+ <service
+ android:name=".MyNotificationListenerService"
+ android:label="MyNotificationListenerService"
+ android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
+ <intent-filter>
+ <action android:name="android.service.notification.NotificationListenerService" />
+ </intent-filter>
+ </service>
</application>
<instrumentation
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 f3c4935..e0ba76b 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
@@ -16,6 +16,8 @@
package com.android.cts.net.hostside;
+import android.os.SystemClock;
+
/**
* Base class for metered and non-metered Doze Mode tests.
*/
@@ -99,6 +101,24 @@
assertBackgroundNetworkAccess(true);
}
+ public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
+ throws Exception {
+ setPendingIntentWhitelistDuration(NETWORK_TIMEOUT_MS);
+ try {
+ registerNotificationListenerService();
+ setDozeMode(true);
+ assertBackgroundNetworkAccess(false);
+
+ sendNotification(42);
+ assertBackgroundNetworkAccess(true);
+ // Make sure access is disabled after it expires
+ SystemClock.sleep(NETWORK_TIMEOUT_MS);
+ assertBackgroundNetworkAccess(false);
+ } finally {
+ resetDeviceIdleSettings();
+ }
+ }
+
// 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 82ef752..ba383a8 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
@@ -34,6 +34,8 @@
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
import android.net.wifi.WifiManager;
+import android.os.SystemClock;
+import android.service.notification.NotificationListenerService;
import android.test.InstrumentationTestCase;
import android.util.Log;
@@ -60,12 +62,16 @@
"com.android.cts.net.hostside.app2.action.CHECK_NETWORK";
private static final String ACTION_RECEIVER_READY =
"com.android.cts.net.hostside.app2.action.RECEIVER_READY";
+ static final String ACTION_SEND_NOTIFICATION =
+ "com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION";
private static final String EXTRA_ACTION = "com.android.cts.net.hostside.app2.extra.ACTION";
private static final String EXTRA_RECEIVER_NAME =
"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 NETWORK_STATUS_SEPARATOR = "\\|";
private static final int SECOND_IN_MS = 1000;
- private static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
+ static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
private static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
@@ -118,7 +124,7 @@
Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
+ attempts + " attempts; sleeping "
+ SLEEP_TIME_SEC + " seconds before trying again");
- Thread.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
+ SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
} while (attempts <= maxAttempts);
assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
+ maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
@@ -211,7 +217,7 @@
}
Log.d(TAG, "App not on background state on attempt #" + i
+ "; sleeping 1s before trying again");
- Thread.sleep(SECOND_IN_MS);
+ SystemClock.sleep(SECOND_IN_MS);
}
fail("App2 is not on background state after " + maxTries + " attempts: " + state );
}
@@ -228,7 +234,7 @@
}
Log.d(TAG, "App not on foreground state on attempt #" + i
+ "; sleeping 1s before trying again");
- Thread.sleep(SECOND_IN_MS);
+ SystemClock.sleep(SECOND_IN_MS);
}
fail("App2 is not on foreground state after " + maxTries + " attempts: " + state );
}
@@ -245,7 +251,7 @@
}
Log.d(TAG, "App not on foreground service state on attempt #" + i
+ "; sleeping 1s before trying again");
- Thread.sleep(SECOND_IN_MS);
+ SystemClock.sleep(SECOND_IN_MS);
}
fail("App2 is not on foreground service state after " + maxTries + " attempts: " + state );
}
@@ -286,7 +292,7 @@
if (state != State.CONNECTED) {
Log.d(TAG, "State (" + state + ") not set to CONNECTED on attempt #" + i
+ "; sleeping 1s before trying again");
- Thread.sleep(SECOND_IN_MS);
+ SystemClock.sleep(SECOND_IN_MS);
} else {
assertEquals("wrong detailed state for " + networkInfo,
DetailedState.CONNECTED, detailedState);
@@ -299,7 +305,7 @@
if (state != State.DISCONNECTED) {
Log.d(TAG, "State (" + state + ") not set to DISCONNECTED on attempt #" + i
+ "; sleeping 1s before trying again");
- Thread.sleep(SECOND_IN_MS);
+ SystemClock.sleep(SECOND_IN_MS);
} else {
assertEquals("wrong detailed state for " + networkInfo,
DetailedState.BLOCKED, detailedState);
@@ -361,7 +367,7 @@
Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
+ checker.getExpected() + "' on attempt #" + i
+ "; sleeping " + napTimeSeconds + "s before trying again");
- Thread.sleep(napTimeSeconds * SECOND_IN_MS);
+ SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
}
fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
+ maxTries
@@ -526,7 +532,7 @@
}
Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
+ expected + ", got " + actual + "); sleeping 1s before polling again");
- Thread.sleep(SECOND_IN_MS);
+ SystemClock.sleep(SECOND_IN_MS);
}
fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
+ ". Full list: " + uids);
@@ -632,11 +638,42 @@
return;
}
Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
- Thread.sleep(SECOND_IN_MS);
+ SystemClock.sleep(SECOND_IN_MS);
}
fail("app2 receiver is not ready");
}
+ /**
+ * Registers a {@link NotificationListenerService} implementation that will execute the
+ * notification actions right after the notification is sent.
+ */
+ protected void registerNotificationListenerService() throws Exception {
+ final StringBuilder listeners = new StringBuilder(getNotificationListenerServices());
+ if (listeners.length() > 0) {
+ listeners.append(":");
+ }
+ listeners.append(MyNotificationListenerService.getId());
+ executeShellCommand("settings put secure enabled_notification_listeners " + listeners);
+ final String newListeners = getNotificationListenerServices();
+ assertEquals("Failed to set 'enabled_notification_listeners'",
+ listeners.toString(), newListeners);
+ }
+
+ private String getNotificationListenerServices() throws Exception {
+ return executeShellCommand("settings get secure enabled_notification_listeners");
+ }
+
+ protected void setPendingIntentWhitelistDuration(int durationMs) throws Exception {
+ final String command = String.format(
+ "settings put global device_idle_constants %s=%d",
+ "notification_whitelist_duration", durationMs);
+ executeSilentShellCommand(command);
+ }
+
+ protected void resetDeviceIdleSettings() throws Exception {
+ executeShellCommand("settings delete global device_idle_constants");
+ }
+
protected void startForegroundService() throws Exception {
executeShellCommand(
"am startservice -f 1 com.android.cts.net.hostside.app2/.MyForegroundService");
@@ -667,6 +704,13 @@
+ "--receiver-foreground --receiver-registered-only");
}
+ protected void sendNotification(int notificationId) {
+ final Intent intent = new Intent(ACTION_SEND_NOTIFICATION);
+ intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId);
+ Log.d(TAG, "Sending broadcast: " + intent);
+ mContext.sendBroadcast(intent);
+ }
+
private String toString(int status) {
switch (status) {
case RESTRICT_BACKGROUND_STATUS_DISABLED:
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
new file mode 100644
index 0000000..b9c3031
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MyNotificationListenerService.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.net.hostside;
+
+import android.app.Notification;
+import android.app.PendingIntent.CanceledException;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+/**
+ * NotificationListenerService implementation that executes the notification actions once they're
+ * created.
+ */
+public class MyNotificationListenerService extends NotificationListenerService {
+ private static final String TAG = "MyNotificationListenerService";
+
+ @Override
+ public void onListenerConnected() {
+ Log.d(TAG, "onListenerConnected()");
+ }
+
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn) {
+ Log.d(TAG, "onNotificationPosted(): " + sbn);
+ if (!sbn.getPackageName().startsWith(getPackageName())) {
+ Log.v(TAG, "ignoring notification from a different package");
+ return;
+ }
+ final Notification notification = sbn.getNotification();
+ if (notification.actions == null) {
+ Log.w(TAG, "ignoring notification without an action");
+ }
+ 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");
+ }
+ }
+ }
+
+ static String getId() {
+ return String.format("%s/%s", MyNotificationListenerService.class.getPackage().getName(),
+ MyNotificationListenerService.class.getName());
+ }
+}