Added test for whitelisting pending intent for Doze Mode.
BUG: 28818704 Change-Id: I927364e78cd73133899d67be23e0b274829686af
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 @@ abstract class AbstractDozeModeTestCase extends AbstractRestrictBackgroundNetwor
|
||||
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
|
||||
|
||||
@@ -34,6 +34,8 @@ import android.net.NetworkInfo;
|
||||
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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
"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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
}
|
||||
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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
}
|
||||
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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
}
|
||||
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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
}
|
||||
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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
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 @@ abstract class AbstractRestrictBackgroundNetworkTestCase extends Instrumentation
|
||||
+ "--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:
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,8 @@
|
||||
<action android:name="com.android.cts.net.hostside.app2.action.GET_COUNTERS" />
|
||||
<action android:name="com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS" />
|
||||
<action android:name="com.android.cts.net.hostside.app2.action.CHECK_NETWORK" />
|
||||
</intent-filter>
|
||||
<action android:name="com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
|
||||
|
||||
BIN
tests/cts/hostside/app2/res/drawable/ic_notification.png
Normal file
BIN
tests/cts/hostside/app2/res/drawable/ic_notification.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.7 KiB |
@@ -36,9 +36,13 @@ public final class Common {
|
||||
"com.android.cts.net.hostside.app2.action.RECEIVER_READY";
|
||||
static final String ACTION_FINISH_ACTIVITY =
|
||||
"com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY";
|
||||
static final String ACTION_SEND_NOTIFICATION =
|
||||
"com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION";
|
||||
static final String EXTRA_ACTION = "com.android.cts.net.hostside.app2.extra.ACTION";
|
||||
static final String EXTRA_RECEIVER_NAME =
|
||||
"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 int getUid(Context context) {
|
||||
final String packageName = context.getPackageName();
|
||||
|
||||
@@ -22,17 +22,17 @@ import static com.android.cts.net.hostside.app2.Common.ACTION_CHECK_NETWORK;
|
||||
import static com.android.cts.net.hostside.app2.Common.ACTION_GET_COUNTERS;
|
||||
import static com.android.cts.net.hostside.app2.Common.ACTION_GET_RESTRICT_BACKGROUND_STATUS;
|
||||
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_RECEIVER_NAME;
|
||||
import static com.android.cts.net.hostside.app2.Common.MANIFEST_RECEIVER;
|
||||
import static com.android.cts.net.hostside.app2.Common.TAG;
|
||||
import static com.android.cts.net.hostside.app2.Common.getUid;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -41,6 +41,11 @@ import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Receiver used to:
|
||||
* <ol>
|
||||
@@ -85,6 +90,9 @@ public class MyBroadcastReceiver extends BroadcastReceiver {
|
||||
Log.d(TAG, message);
|
||||
setResultData(message);
|
||||
break;
|
||||
case ACTION_SEND_NOTIFICATION:
|
||||
sendNotification(context, intent);
|
||||
break;
|
||||
default:
|
||||
Log.e(TAG, "received unexpected action: " + action);
|
||||
}
|
||||
@@ -213,4 +221,23 @@ public class MyBroadcastReceiver extends BroadcastReceiver {
|
||||
final int counter = getCounter(context, action, receiverName);
|
||||
setResultData(String.valueOf(counter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a system notification containing actions with pending intents to launch the app's
|
||||
* main activitiy or service.
|
||||
*/
|
||||
private void sendNotification(Context context, Intent intent) {
|
||||
final int notificationId = intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1);
|
||||
final Intent serviceIntent = new Intent(context, MyService.class);
|
||||
final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent, 0);
|
||||
|
||||
final Notification notification = new Notification.Builder(context)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setContentTitle("Light, Cameras...")
|
||||
.setContentIntent(pendingIntent)
|
||||
.addAction(R.drawable.ic_notification, "ACTION", pendingIntent)
|
||||
.build();
|
||||
((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
|
||||
.notify(notificationId, notification);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +184,11 @@ public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkTestC
|
||||
"testBackgroundNetworkAccess_enabled");
|
||||
}
|
||||
|
||||
public void testDozeModeMetered_enabledButWhitelistedOnNotificationAction() throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
|
||||
"testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
|
||||
}
|
||||
|
||||
// TODO: currently power-save mode and idle uses the same whitelist, so this test would be
|
||||
// redundant (as it would be testing the same as testBatterySaverMode_reinstall())
|
||||
// public void testDozeMode_reinstall() throws Exception {
|
||||
@@ -204,6 +209,12 @@ public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkTestC
|
||||
"testBackgroundNetworkAccess_enabled");
|
||||
}
|
||||
|
||||
public void testDozeModeNonMetered_enabledButWhitelistedOnNotificationAction()
|
||||
throws Exception {
|
||||
runDeviceTests(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
|
||||
"testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
|
||||
}
|
||||
|
||||
/**********************
|
||||
* Mixed modes tests. *
|
||||
**********************/
|
||||
|
||||
Reference in New Issue
Block a user