blob: 70fc51acfc76891b9d1bf72dc7ae21d4383b330f [file] [log] [blame]
/*
* 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 static android.cts.util.SystemUtil.runShellCommand;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import android.app.Instrumentation;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
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;
/**
* Superclass for tests related to background network restrictions.
*/
abstract class AbstractRestrictBackgroundNetworkTestCase extends InstrumentationTestCase {
protected static final String TAG = "RestrictBackgroundNetworkTests";
protected static final String TEST_PKG = "com.android.cts.net.hostside";
protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
private static final int SLEEP_TIME_SEC = 1;
private static final boolean DEBUG = true;
// Constants below must match values defined on app2's Common.java
private static final String MANIFEST_RECEIVER = "ManifestReceiver";
private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
private static final String ACTION_GET_COUNTERS =
"com.android.cts.net.hostside.app2.action.GET_COUNTERS";
private static final String ACTION_GET_RESTRICT_BACKGROUND_STATUS =
"com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS";
private static final String ACTION_CHECK_NETWORK =
"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;
static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
private static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
private static final int PROCESS_STATE_TOP = 2;
// Must be higher than NETWORK_TIMEOUT_MS
private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4;
protected Context mContext;
protected Instrumentation mInstrumentation;
protected ConnectivityManager mCm;
protected WifiManager mWfm;
protected int mUid;
private String mMeteredWifi;
@Override
protected void setUp() throws Exception {
super.setUp();
mInstrumentation = getInstrumentation();
mContext = mInstrumentation.getContext();
mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
mWfm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
mUid = getUid(TEST_APP2_PKG);
final int myUid = getUid(mContext.getPackageName());
Log.i(TAG, "Apps status on " + getName() + ":\n"
+ "\ttest app: uid=" + myUid + ", state=" + getProcessStateByUid(myUid) + "\n"
+ "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
}
protected int getUid(String packageName) throws Exception {
return mContext.getPackageManager().getPackageUid(packageName, 0);
}
protected void assertRestrictBackgroundChangedReceived(int expectedCount) throws Exception {
assertRestrictBackgroundChangedReceived(DYNAMIC_RECEIVER, expectedCount);
assertRestrictBackgroundChangedReceived(MANIFEST_RECEIVER, 0);
}
protected void assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)
throws Exception {
int attempts = 0;
int count = 0;
final int maxAttempts = 5;
do {
attempts++;
count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
if (count == expectedCount) {
break;
}
Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
+ attempts + " attempts; sleeping "
+ SLEEP_TIME_SEC + " seconds before trying again");
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);
}
protected String sendOrderedBroadcast(Intent intent) throws Exception {
return sendOrderedBroadcast(intent, ORDERED_BROADCAST_TIMEOUT_MS);
}
protected String sendOrderedBroadcast(Intent intent, int timeoutMs) throws Exception {
final LinkedBlockingQueue<String> result = new LinkedBlockingQueue<>(1);
Log.d(TAG, "Sending ordered broadcast: " + intent);
mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String resultData = getResultData();
if (resultData == null) {
Log.e(TAG, "Received null data from ordered intent");
return;
}
result.offer(resultData);
}
}, null, 0, null, null);
final String resultData = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
Log.d(TAG, "Ordered broadcast response after " + timeoutMs + "ms: " + resultData );
return resultData;
}
protected int getNumberBroadcastsReceived(String receiverName, String action) throws Exception {
final Intent intent = new Intent(ACTION_GET_COUNTERS);
intent.putExtra(EXTRA_ACTION, ACTION_RESTRICT_BACKGROUND_CHANGED);
intent.putExtra(EXTRA_RECEIVER_NAME, receiverName);
final String resultData = sendOrderedBroadcast(intent);
assertNotNull("timeout waiting for ordered broadcast result", resultData);
return Integer.valueOf(resultData);
}
protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception {
final Intent intent = new Intent(ACTION_GET_RESTRICT_BACKGROUND_STATUS);
final String resultData = sendOrderedBroadcast(intent);
assertNotNull("timeout waiting for ordered broadcast result", resultData);
final String actualStatus = toString(Integer.parseInt(resultData));
assertEquals("wrong status", toString(expectedStatus), actualStatus);
}
protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
assertBackgroundState(); // Sanity check.
assertNetworkAccess(expectAllowed);
}
protected void assertForegroundNetworkAccess() throws Exception {
assertForegroundState(); // Sanity check.
assertNetworkAccess(true);
}
protected void assertForegroundServiceNetworkAccess() throws Exception {
assertForegroundServiceState(); // Sanity check.
assertNetworkAccess(true);
}
/**
* Whether this device suport this type of test.
*
* <p>Should be overridden when necessary, and explicitly used before each test. Example:
*
* <pre><code>
* public void testSomething() {
* if (!isSupported()) return;
* </code></pre>
*
* @return {@code true} by default.
*/
protected boolean isSupported() throws Exception {
return true;
}
/**
* Asserts that an app always have access while on foreground or running a foreground service.
*
* <p>This method will launch an activity and a foreground service to make the assertion, but
* will finish the activity / stop the service afterwards.
*/
protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception{
// Checks foreground first.
launchActivity();
assertForegroundNetworkAccess();
finishActivity();
// Then foreground service
startForegroundService();
assertForegroundServiceNetworkAccess();
stopForegroundService();
}
protected final void assertBackgroundState() throws Exception {
final int maxTries = 30;
ProcessState state = null;
for (int i = 1; i <= maxTries; i++) {
state = getProcessStateByUid(mUid);
Log.v(TAG, "assertBackgroundState(): status for app2 (" + mUid + ") on attempt #" + i
+ ": " + state);
if (isBackground(state.state)) {
return;
}
Log.d(TAG, "App not on background state on attempt #" + i
+ "; sleeping 1s before trying again");
SystemClock.sleep(SECOND_IN_MS);
}
fail("App2 is not on background state after " + maxTries + " attempts: " + state );
}
protected final void assertForegroundState() throws Exception {
final int maxTries = 30;
ProcessState state = null;
for (int i = 1; i <= maxTries; i++) {
state = getProcessStateByUid(mUid);
Log.v(TAG, "assertForegroundState(): status for app2 (" + mUid + ") on attempt #" + i
+ ": " + state);
if (!isBackground(state.state)) {
return;
}
Log.d(TAG, "App not on foreground state on attempt #" + i
+ "; sleeping 1s before trying again");
SystemClock.sleep(SECOND_IN_MS);
}
fail("App2 is not on foreground state after " + maxTries + " attempts: " + state );
}
protected final void assertForegroundServiceState() throws Exception {
final int maxTries = 30;
ProcessState state = null;
for (int i = 1; i <= maxTries; i++) {
state = getProcessStateByUid(mUid);
Log.v(TAG, "assertForegroundServiceState(): status for app2 (" + mUid + ") on attempt #"
+ i + ": " + state);
if (state.state == PROCESS_STATE_FOREGROUND_SERVICE) {
return;
}
Log.d(TAG, "App not on foreground service state on attempt #" + i
+ "; sleeping 1s before trying again");
SystemClock.sleep(SECOND_IN_MS);
}
fail("App2 is not on foreground service state after " + maxTries + " attempts: " + state );
}
/**
* Returns whether an app state should be considered "background" for restriction purposes.
*/
protected boolean isBackground(int state) {
return state > PROCESS_STATE_FOREGROUND_SERVICE;
}
/**
* Asserts whether the active network is available or not.
*/
private void assertNetworkAccess(boolean expectAvailable) throws Exception {
final Intent intent = new Intent(ACTION_CHECK_NETWORK);
final int maxTries = 5;
String resultData = null;
for (int i = 1; i <= maxTries; i++) {
resultData = sendOrderedBroadcast(intent);
assertNotNull("timeout waiting for ordered broadcast", resultData);
// Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
assertEquals("Wrong network status: " + resultData, 5, parts.length); // Sanity check
final State state = State.valueOf(parts[0]);
final DetailedState detailedState = DetailedState.valueOf(parts[1]);
final boolean connected = Boolean.valueOf(parts[2]);
final String connectionCheckDetails = parts[3];
final String networkInfo = parts[4];
if (expectAvailable) {
if (!connected) {
// Since it's establishing a connection to an external site, it could be flaky.
Log.w(TAG, "Failed to connect to an external site on attempt #" + i +
" (error: " + connectionCheckDetails + ", NetworkInfo: " + networkInfo
+ "); sleeping " + NETWORK_TIMEOUT_MS + "ms before trying again");
SystemClock.sleep(NETWORK_TIMEOUT_MS);
continue;
}
if (state != State.CONNECTED) {
Log.d(TAG, "State (" + state + ") not set to CONNECTED on attempt #" + i
+ "; sleeping 1s before trying again");
SystemClock.sleep(SECOND_IN_MS);
} else {
assertEquals("wrong detailed state for " + networkInfo,
DetailedState.CONNECTED, detailedState);
return;
}
return;
} else {
assertFalse("should not be connected: " + connectionCheckDetails
+ " (network info: " + networkInfo + ")", connected);
if (state != State.DISCONNECTED) {
// When the network info state change, it's possible the app still get the
// previous value, so we need to retry a couple times.
Log.d(TAG, "State (" + state + ") not set to DISCONNECTED on attempt #" + i
+ "; sleeping 1s before trying again");
SystemClock.sleep(SECOND_IN_MS);
} else {
assertEquals("wrong detailed state for " + networkInfo,
DetailedState.BLOCKED, detailedState);
return;
}
}
}
fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries
+ " attempts. Last data: " + resultData);
}
protected String executeShellCommand(String command) throws Exception {
final String result = runShellCommand(mInstrumentation, command).trim();
if (DEBUG) Log.d(TAG, "Command '" + command + "' returned '" + result + "'");
return result;
}
/**
* Runs a Shell command which is not expected to generate output.
*/
protected void executeSilentShellCommand(String command) throws Exception {
final String result = executeShellCommand(command);
assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty());
}
/**
* Asserts the result of a command, wait and re-running it a couple times if necessary.
*/
protected void assertDelayedShellCommand(String command, final String expectedResult)
throws Exception {
assertDelayedShellCommand(command, 5, 1, expectedResult);
}
protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
final String expectedResult) throws Exception {
assertDelayedShellCommand(command, maxTries, napTimeSeconds, new ExpectResultChecker() {
@Override
public boolean isExpected(String result) {
return expectedResult.equals(result);
}
@Override
public String getExpected() {
return expectedResult;
}
});
}
protected void assertDelayedShellCommand(String command, ExpectResultChecker checker)
throws Exception {
assertDelayedShellCommand(command, 5, 1, checker);
}
protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
ExpectResultChecker checker) throws Exception {
String result = "";
for (int i = 1; i <= maxTries; i++) {
result = executeShellCommand(command).trim();
if (checker.isExpected(result)) return;
Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
+ checker.getExpected() + "' on attempt #" + i
+ "; sleeping " + napTimeSeconds + "s before trying again");
SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
}
fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
+ maxTries
+ " attempts. Last result: '" + result + "'");
}
/**
* Puts the device in a state where the active network is metered, or fail if it can't achieve
* that state.
*/
protected void setMeteredNetwork() throws Exception {
final NetworkInfo info = mCm.getActiveNetworkInfo();
final boolean metered = mCm.isActiveNetworkMetered();
if (metered) {
Log.d(TAG, "Active network already metered: " + info);
return;
} else {
Log.w(TAG, "Active network not metered: " + info);
}
final String netId = setWifiMeteredStatus(true);
// Set flag so status is reverted on resetMeteredNetwork();
mMeteredWifi = netId;
// Sanity check.
assertWifiMeteredStatus(netId, true);
assertActiveNetworkMetered(true);
}
/**
* Puts the device in a state where the active network is not metered, or fail if it can't
* achieve that state.
* <p>It assumes the device has a valid WI-FI connection.
*/
protected void resetMeteredNetwork() throws Exception {
if (mMeteredWifi != null) {
Log.i(TAG, "resetMeteredNetwork(): SID '" + mMeteredWifi
+ "' was set as metered by test case; resetting it");
setWifiMeteredStatus(mMeteredWifi, false);
} else {
final NetworkInfo info = mCm.getActiveNetworkInfo();
assertNotNull("Could not get active network", info);
if (!mCm.isActiveNetworkMetered()) {
Log.d(TAG, "Active network is not metered: " + info);
} else if (info.getType() == ConnectivityManager.TYPE_WIFI) {
Log.i(TAG, "Setting active WI-FI network as metered: " + info );
setWifiMeteredStatus(false);
} else {
fail("Active network is not WI-FI hence cannot be set as non-metered: " + info);
}
}
assertActiveNetworkMetered(false); // Sanity check.
}
private void assertActiveNetworkMetered(boolean expected) throws Exception {
final int maxTries = 5;
NetworkInfo info = null;
for (int i = 1; i <= maxTries; i++) {
info = mCm.getActiveNetworkInfo();
if (info != null) {
break;
}
Log.v(TAG, "No active network info on attempt #" + i
+ "; sleeping 1s before polling again");
Thread.sleep(SECOND_IN_MS);
}
assertNotNull("No active network after " + maxTries + " attempts", info);
assertEquals("Wrong metered status for active network " + info, expected,
mCm.isActiveNetworkMetered());
}
private String setWifiMeteredStatus(boolean metered) throws Exception {
// We could call setWifiEnabled() here, but it might take sometime to be in a consistent
// state (for example, if one of the saved network is not properly authenticated), so it's
// better to let the hostside test take care of that.
assertTrue("wi-fi is disabled", mWfm.isWifiEnabled());
// TODO: if it's not guaranteed the device has wi-fi, we need to change the tests
// to make the actual verification of restrictions optional.
final String ssid = mWfm.getConnectionInfo().getSSID();
return setWifiMeteredStatus(ssid, metered);
}
private String setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
assertNotNull("null SSID", ssid);
final String netId = ssid.trim().replaceAll("\"", ""); // remove quotes, if any.
assertFalse("empty SSID", ssid.isEmpty());
Log.i(TAG, "Setting wi-fi network " + netId + " metered status to " + metered);
final String setCommand = "cmd netpolicy set metered-network " + netId + " " + metered;
assertDelayedShellCommand(setCommand, "");
return netId;
}
private void assertWifiMeteredStatus(String netId, boolean status) throws Exception {
final String command = "cmd netpolicy list wifi-networks";
final String expectedLine = netId + ";" + status;
assertDelayedShellCommand(command, new ExpectResultChecker() {
@Override
public boolean isExpected(String result) {
return result.contains(expectedLine);
}
@Override
public String getExpected() {
return "line containing " + expectedLine;
}
});
}
protected void setRestrictBackground(boolean enabled) throws Exception {
executeShellCommand("cmd netpolicy set restrict-background " + enabled);
final String output = executeShellCommand("cmd netpolicy get restrict-background ");
final String expectedSuffix = enabled ? "enabled" : "disabled";
// TODO: use MoreAsserts?
assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
output.endsWith(expectedSuffix));
}
protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid);
assertRestrictBackgroundWhitelist(uid, true);
}
protected void removeRestrictBackgroundWhitelist(int uid) throws Exception {
executeShellCommand("cmd netpolicy remove restrict-background-whitelist " + uid);
assertRestrictBackgroundWhitelist(uid, false);
}
protected void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
assertRestrictBackground("restrict-background-whitelist", uid, expected);
}
protected void addRestrictBackgroundBlacklist(int uid) throws Exception {
executeShellCommand("cmd netpolicy add restrict-background-blacklist " + uid);
assertRestrictBackgroundBlacklist(uid, true);
}
protected void removeRestrictBackgroundBlacklist(int uid) throws Exception {
executeShellCommand("cmd netpolicy remove restrict-background-blacklist " + uid);
assertRestrictBackgroundBlacklist(uid, false);
}
protected void assertRestrictBackgroundBlacklist(int uid, boolean expected) throws Exception {
assertRestrictBackground("restrict-background-blacklist", uid, expected);
}
private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
final int maxTries = 5;
boolean actual = false;
final String expectedUid = Integer.toString(uid);
String uids = "";
for (int i = 1; i <= maxTries; i++) {
final String output =
executeShellCommand("cmd netpolicy list " + list);
uids = output.split(":")[1];
for (String candidate : uids.split(" ")) {
actual = candidate.trim().equals(expectedUid);
if (expected == actual) {
return;
}
}
Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
+ expected + ", got " + actual + "); sleeping 1s before polling again");
SystemClock.sleep(SECOND_IN_MS);
}
fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
+ ". Full list: " + uids);
}
protected void assertPowerSaveModeWhitelist(String packageName, boolean expected)
throws Exception {
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
assertDelayedShellCommand("dumpsys deviceidle whitelist =" + packageName,
Boolean.toString(expected));
}
protected void addPowerSaveModeWhitelist(String packageName) throws Exception {
Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
executeShellCommand("dumpsys deviceidle whitelist +" + packageName);
assertPowerSaveModeWhitelist(packageName, true); // Sanity check
}
protected void removePowerSaveModeWhitelist(String packageName) throws Exception {
Log.i(TAG, "Removing package " + packageName + " from power-save-mode whitelist");
// TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
// need to use netpolicy for whitelisting
executeShellCommand("dumpsys deviceidle whitelist -" + packageName);
assertPowerSaveModeWhitelist(packageName, false); // Sanity check
}
protected void turnBatteryOff() throws Exception {
executeSilentShellCommand("cmd battery unplug");
}
protected void turnBatteryOn() throws Exception {
executeSilentShellCommand("cmd battery reset");
}
protected void turnScreenOff() throws Exception {
executeSilentShellCommand("input keyevent KEYCODE_SLEEP");
}
protected void turnScreenOn() throws Exception {
executeSilentShellCommand("input keyevent KEYCODE_WAKEUP");
executeSilentShellCommand("wm dismiss-keyguard");
}
protected void setBatterySaverMode(boolean enabled) throws Exception {
Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
if (enabled) {
turnBatteryOff();
executeSilentShellCommand("cmd battery unplug");
executeSilentShellCommand("settings put global low_power 1");
} else {
executeSilentShellCommand("settings put global low_power 0");
turnBatteryOn();
}
}
protected void setDozeMode(boolean enabled) throws Exception {
// Sanity check, since tests should check beforehand....
assertTrue("Device does not support Doze Mode", isDozeModeEnabled());
Log.i(TAG, "Setting Doze Mode to " + enabled);
if (enabled) {
turnBatteryOff();
turnScreenOff();
executeShellCommand("dumpsys deviceidle force-idle deep");
} else {
turnScreenOn();
turnBatteryOn();
executeShellCommand("dumpsys deviceidle unforce");
}
// Sanity check.
assertDozeMode(enabled);
}
protected void assertDozeMode(boolean enabled) throws Exception {
assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
}
protected boolean isDozeModeEnabled() throws Exception {
final String result = executeShellCommand("cmd deviceidle enabled deep").trim();
return result.equals("1");
}
protected void setAppIdle(boolean enabled) throws Exception {
Log.i(TAG, "Setting app idle to " + enabled);
executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
assertAppIdle(enabled); // Sanity check
}
protected void assertAppIdle(boolean enabled) throws Exception {
assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 10, 2, "Idle=" + enabled);
}
/**
* Starts a service that will register a broadcast receiver to receive
* {@code RESTRICT_BACKGROUND_CHANGE} intents.
* <p>
* The service must run in a separate app because otherwise it would be killed every time
* {@link #runDeviceTests(String, String)} is executed.
*/
protected void registerBroadcastReceiver() throws Exception {
executeShellCommand("am startservice com.android.cts.net.hostside.app2/.MyService");
// Wait until receiver is ready.
final int maxTries = 5;
for (int i = 1; i <= maxTries; i++) {
final String message =
sendOrderedBroadcast(new Intent(ACTION_RECEIVER_READY), SECOND_IN_MS);
Log.d(TAG, "app2 receiver acked: " + message);
if (message != null) {
return;
}
Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
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");
assertForegroundServiceState();
}
protected void stopForegroundService() throws Exception {
executeShellCommand(
"am startservice -f 2 com.android.cts.net.hostside.app2/.MyForegroundService");
// NOTE: cannot assert state because it depends on whether activity was on top before.
}
/**
* Launches an activity on app2 so its process is elevated to foreground status.
*/
protected void launchActivity() throws Exception {
turnScreenOn();
executeShellCommand("am start com.android.cts.net.hostside.app2/.MyActivity");
final int maxTries = 30;
ProcessState state = null;
for (int i = 1; i <= maxTries; i++) {
state = getProcessStateByUid(mUid);
if (state.state == PROCESS_STATE_TOP) return;
Log.w(TAG, "launchActivity(): uid " + mUid + " not on TOP state on attempt #" + i
+ "; turning screen on and sleeping 1s before checking again");
turnScreenOn();
SystemClock.sleep(SECOND_IN_MS);
}
fail("App2 is not on foreground state after " + maxTries + " attempts: " + state);
}
/**
* Finishes an activity on app2 so its process is demoted fromforeground status.
*/
protected void finishActivity() throws Exception {
executeShellCommand("am broadcast -a "
+ " com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY "
+ "--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:
return "DISABLED";
case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
return "WHITELISTED";
case RESTRICT_BACKGROUND_STATUS_ENABLED:
return "ENABLED";
default:
return "UNKNOWN_STATUS_" + status;
}
}
private ProcessState getProcessStateByUid(int uid) throws Exception {
return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid));
}
private static class ProcessState {
private final String fullState;
final int state;
ProcessState(String fullState) {
this.fullState = fullState;
try {
this.state = Integer.parseInt(fullState.split(" ")[0]);
} catch (Exception e) {
throw new IllegalArgumentException("Could not parse " + fullState);
}
}
@Override
public String toString() {
return fullState;
}
}
/**
* Helper class used to assert the result of a Shell command.
*/
protected static interface ExpectResultChecker {
/**
* Checkes whether the result of the command matched the expectation.
*/
boolean isExpected(String result);
/**
* Gets the expected result so it's displayed on log and failure messages.
*/
String getExpected();
}
}