Added CTS tests to check background network access while on power save mode.

BUG: 27127112
Change-Id: Ifa3019d7b94459d737a9dff80b4b36a2dd43aca5
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 59de759..3f80b5a 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
@@ -22,7 +22,6 @@
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
 
-import java.io.IOException;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -42,7 +41,8 @@
 abstract class AbstractRestrictBackgroundNetworkTestCase extends InstrumentationTestCase {
     protected static final String TAG = "RestrictBackgroundNetworkTests";
 
-    private static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
+    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;
@@ -60,6 +60,10 @@
     private static final String RESULT_SEPARATOR = ";";
     private static final String STATUS_NETWORK_UNAVAILABLE_PREFIX = "NetworkUnavailable:";
     private static final String STATUS_NETWORK_AVAILABLE_PREFIX = "NetworkAvailable:";
+    private static final int NETWORK_TIMEOUT_MS = 15 * 1000;
+
+    // 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;
@@ -134,8 +138,7 @@
             }
         }, null, 0, null, null);
 
-        final String resultData = result.poll(60, TimeUnit.SECONDS);
-        assertNotNull("timeout waiting for ordered broadcast result", resultData);
+        final String resultData = result.poll(ORDERED_BROADCAST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
         Log.d(TAG, "Ordered broadcast response: " + resultData);
         return resultData;
     }
@@ -145,6 +148,7 @@
         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);
     }
 
@@ -153,25 +157,73 @@
         final String resultData = sendOrderedBroadcast(intent);
         final String[] resultItems = resultData.split(RESULT_SEPARATOR);
         final String actualApiStatus = toString(Integer.parseInt(resultItems[0]));
-        final String actualNetworkStatus = resultItems[1];
-
         // First asserts the API returns the proper value...
         assertEquals("wrong status", toString(expectedApiStatus), actualApiStatus);
 
         //...then the actual network status in the background thread.
-        final String expectedPrefix = expectedApiStatus == RESTRICT_BACKGROUND_STATUS_ENABLED ?
-                        STATUS_NETWORK_UNAVAILABLE_PREFIX : STATUS_NETWORK_AVAILABLE_PREFIX;
-        assertTrue("Wrong network status for API status " + actualApiStatus + ": "
-                + actualNetworkStatus, actualNetworkStatus.startsWith(expectedPrefix));
+        final String networkStatus = getNetworkStatus(resultItems);
+        assertNetworkStatus(expectedApiStatus != RESTRICT_BACKGROUND_STATUS_ENABLED, networkStatus);
     }
 
-    protected String executeShellCommand(String command) throws IOException {
+    protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
+        final Intent intent = new Intent(ACTION_CHECK_NETWORK);
+        final String resultData = sendOrderedBroadcast(intent);
+        final String[] resultItems = resultData.split(RESULT_SEPARATOR);
+        final String networkStatus = getNetworkStatus(resultItems);
+        assertNetworkStatus(expectAllowed, networkStatus);
+    }
+
+    private String getNetworkStatus(String[] resultItems) {
+        return resultItems.length < 2 ? null : resultItems[1];
+    }
+
+    private void assertNetworkStatus(boolean expectAvailable, String status) throws Exception {
+        if (status == null) {
+            Log.d(TAG, "timeout waiting for ordered broadcast");
+            if (expectAvailable) {
+                fail("did not get network status when access was allowed");
+            }
+            return;
+        }
+        final String expectedPrefix = expectAvailable ?
+                STATUS_NETWORK_AVAILABLE_PREFIX : STATUS_NETWORK_UNAVAILABLE_PREFIX;
+        assertTrue("Wrong network status (" + status + ") when expectedAvailable is "
+                + expectAvailable, status.startsWith(expectedPrefix));
+    }
+
+    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;
     }
 
-    protected void setMeteredNetwork() throws IOException {
+    /**
+     * 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, String expectedResult)
+            throws Exception {
+        final int maxTries = 5;
+        for (int i = 1; i <= maxTries; i++) {
+            final String result = executeShellCommand(command).trim();
+            if (result.equals(expectedResult))
+                return;
+            Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
+                    + expectedResult + "' on attempt #; sleeping 1s before polling again");
+            Thread.sleep(1000);
+        }
+        fail("Command '" + command + "' did not return '" + expectedResult + "' after " + maxTries
+                + " attempts");
+    }
+
+    protected void setMeteredNetwork() throws Exception {
         final NetworkInfo info = mCm.getActiveNetworkInfo();
         final boolean metered = mCm.isActiveNetworkMetered();
         if (metered) {
@@ -185,7 +237,7 @@
         mResetMeteredWifi = true;
     }
 
-    protected String setWifiMeteredStatus(boolean metered) throws IOException {
+    protected String setWifiMeteredStatus(boolean metered) throws Exception {
         mWfm.setWifiEnabled(true);
         // 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.
@@ -206,7 +258,7 @@
         return netId;
     }
 
-    protected void setRestrictBackground(boolean enabled) throws IOException {
+    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";
@@ -242,6 +294,40 @@
         fail("whitelist check for uid " + uid + " failed: expected " + expected + ", got " + actual);
     }
 
+    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 setPowerSaveMode(boolean enabled) throws Exception {
+        Log.i(TAG, "Setting power mode to " + enabled);
+        if (enabled) {
+            executeSilentShellCommand("cmd battery unplug");
+            executeSilentShellCommand("settings put global low_power 1");
+        } else {
+            executeSilentShellCommand("cmd battery reset");
+        }
+    }
+
     /**
      * Starts a service that will register a broadcast receiver to receive
      * {@code RESTRICT_BACKGROUND_CHANGE} intents.
@@ -249,7 +335,7 @@
      * The service must run in a separate app because otherwise it would be killed every time
      * {@link #runDeviceTests(String, String)} is executed.
      */
-    protected void registerApp2BroadcastReceiver() throws IOException {
+    protected void registerApp2BroadcastReceiver() throws Exception {
         executeShellCommand("am startservice com.android.cts.net.hostside.app2/.MyService");
     }
 
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeTest.java
new file mode 100644
index 0000000..29a0309
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/BatterySaverModeTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+public class BatterySaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        setPowerSaveMode(false);
+        assertPowerSaveModeWhitelist(TEST_APP2_PKG, false); // Sanity check
+        registerApp2BroadcastReceiver();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        setPowerSaveMode(false);
+    }
+
+    public void testBackgroundNetworkAccess_enabled() throws Exception {
+        setPowerSaveMode(true);
+        assertBackgroundNetworkAccess(false);
+    }
+
+    public void testBackgroundNetworkAccess_whitelisted() throws Exception {
+        setPowerSaveMode(true);
+        assertBackgroundNetworkAccess(false);
+        addPowerSaveModeWhitelist(TEST_APP2_PKG);
+        assertBackgroundNetworkAccess(true);
+        removePowerSaveModeWhitelist(TEST_APP2_PKG);
+        assertBackgroundNetworkAccess(false);
+    }
+
+    public void testBackgroundNetworkAccess_disabled() throws Exception {
+        setPowerSaveMode(false);
+        assertBackgroundNetworkAccess(true);
+    }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index c62189e..ccb1fe9 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -30,6 +30,13 @@
         registerApp2BroadcastReceiver();
    }
 
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        setRestrictBackground(false);
+    }
+
     public void testGetRestrictBackgroundStatus_disabled() throws Exception {
         removeRestrictBackgroundWhitelist(mUid);
         assertRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
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 65c7b60..f32bd44 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
@@ -110,7 +110,10 @@
             setResultData(null);
             return;
         }
-        data.append(apiStatus).append(RESULT_SEPARATOR).append(netStatus);
+        data.append(apiStatus).append(RESULT_SEPARATOR);
+        if (netStatus != null) {
+            data.append(netStatus);
+        }
         Log.d(TAG, "checkNetwork: returning " + data);
         setResultData(data.toString());
     }
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index 21e5aa7..4786450 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -17,6 +17,7 @@
 package com.android.cts.net;
 
 import com.android.ddmlib.Log;
+import com.android.tradefed.device.DeviceNotAvailableException;
 
 public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkTestCase {
 
@@ -39,24 +40,24 @@
         uninstallPackage(TEST_APP2_PKG, true);
     }
 
-    public void testGetRestrictBackgroundStatus_disabled() throws Exception {
+    public void testDataSaverMode_disabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
                 "testGetRestrictBackgroundStatus_disabled");
     }
 
-    public void testGetRestrictBackgroundStatus_whitelisted() throws Exception {
+    public void testDataSaverMode_whitelisted() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
                 "testGetRestrictBackgroundStatus_whitelisted");
     }
 
-    public void testGetRestrictBackgroundStatus_enabled() throws Exception {
+    public void testDataSaverMode_enabled() throws Exception {
         runDeviceTests(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
                 "testGetRestrictBackgroundStatus_enabled");
     }
 
-    public void testGetRestrictBackgroundStatus_uninstall() throws Exception {
+    public void testDataSaverMode_reinstall() throws Exception {
         final int oldUid = getUid(TEST_PKG);
-        testGetRestrictBackgroundStatus_whitelisted();
+        testDataSaverMode_whitelisted();
 
         uninstallPackage(TEST_PKG, true);
         assertPackageUninstalled(TEST_PKG);
@@ -68,6 +69,32 @@
         assertRestrictBackgroundWhitelist(newUid, false);
     }
 
+    public void testBatterySaverMode_disabled() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeTest",
+                "testBackgroundNetworkAccess_disabled");
+    }
+
+    public void testBatterySaverMode_whitelisted() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeTest",
+                "testBackgroundNetworkAccess_whitelisted");
+    }
+
+    public void testBatterySaverMode_enabled() throws Exception {
+        runDeviceTests(TEST_PKG, TEST_PKG + ".BatterySaverModeTest",
+                "testBackgroundNetworkAccess_enabled");
+    }
+
+    public void testBatterySaverMode_reinstall() throws Exception {
+        testBatterySaverMode_whitelisted();
+
+        uninstallPackage(TEST_PKG, true);
+        assertPackageUninstalled(TEST_PKG);
+        assertPowerSaveModeWhitelist(TEST_PKG, false);
+
+        installPackage(TEST_APK);
+        assertPowerSaveModeWhitelist(TEST_PKG, false);
+    }
+
     private void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
         final int max_tries = 5;
         boolean actual = false;
@@ -84,4 +111,29 @@
         fail("whitelist check for uid " + uid + " failed: expected "
                 + expected + ", got " + actual);
     }
+
+    private 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
+        assertDelayedCommand("dumpsys deviceidle whitelist =" + packageName,
+                Boolean.toString(expected));
+    }
+
+    /**
+     * Asserts the result of a command, wait and re-running it a couple times if necessary.
+     */
+    private void assertDelayedCommand(String command, String expectedResult)
+            throws InterruptedException, DeviceNotAvailableException {
+        final int maxTries = 5;
+        for (int i = 1; i <= maxTries; i++) {
+            final String result = runCommand(command).trim();
+            if (result.equals(expectedResult)) return;
+            Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
+                    + expectedResult + "' on attempt #; sleeping 1s before polling again");
+            Thread.sleep(1000);
+        }
+        fail("Command '" + command + "' did not return '" + expectedResult + "' after " + maxTries
+                + " attempts");
+    }
 }