Home | History | Annotate | Download | only in hostside
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.cts.net.hostside;
     18 
     19 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
     20 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
     21 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
     22 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
     23 import static android.os.BatteryManager.BATTERY_PLUGGED_AC;
     24 import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
     25 import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
     26 
     27 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
     28 
     29 import android.app.ActivityManager;
     30 import android.app.Instrumentation;
     31 import android.app.NotificationManager;
     32 import android.content.BroadcastReceiver;
     33 import android.content.ComponentName;
     34 import android.content.Context;
     35 import android.content.Intent;
     36 import android.content.IntentFilter;
     37 import android.content.pm.PackageManager;
     38 import android.net.ConnectivityManager;
     39 import android.net.NetworkInfo;
     40 import android.net.NetworkInfo.DetailedState;
     41 import android.net.NetworkInfo.State;
     42 import android.net.wifi.WifiManager;
     43 import android.os.BatteryManager;
     44 import android.os.Binder;
     45 import android.os.Bundle;
     46 import android.os.SystemClock;
     47 import android.os.SystemProperties;
     48 import android.provider.Settings;
     49 import android.service.notification.NotificationListenerService;
     50 import android.test.InstrumentationTestCase;
     51 import android.text.TextUtils;
     52 import android.util.Log;
     53 
     54 import com.android.compatibility.common.util.BatteryUtils;
     55 
     56 import java.util.concurrent.CountDownLatch;
     57 import java.util.concurrent.LinkedBlockingQueue;
     58 import java.util.concurrent.TimeUnit;
     59 
     60 /**
     61  * Superclass for tests related to background network restrictions.
     62  */
     63 abstract class AbstractRestrictBackgroundNetworkTestCase extends InstrumentationTestCase {
     64     protected static final String TAG = "RestrictBackgroundNetworkTests";
     65 
     66     protected static final String TEST_PKG = "com.android.cts.net.hostside";
     67     protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
     68 
     69     private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
     70     private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
     71 
     72     private static final int SLEEP_TIME_SEC = 1;
     73     private static final boolean DEBUG = true;
     74 
     75     // Constants below must match values defined on app2's Common.java
     76     private static final String MANIFEST_RECEIVER = "ManifestReceiver";
     77     private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
     78 
     79     private static final String ACTION_RECEIVER_READY =
     80             "com.android.cts.net.hostside.app2.action.RECEIVER_READY";
     81     static final String ACTION_SHOW_TOAST =
     82             "com.android.cts.net.hostside.app2.action.SHOW_TOAST";
     83 
     84     protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
     85     protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
     86     protected static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
     87     protected static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
     88     protected static final String NOTIFICATION_TYPE_ACTION = "ACTION";
     89     protected static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
     90     protected static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
     91 
     92     // TODO: Update BatteryManager.BATTERY_PLUGGED_ANY as @TestApi
     93     public static final int BATTERY_PLUGGED_ANY =
     94             BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
     95 
     96     private static final String NETWORK_STATUS_SEPARATOR = "\\|";
     97     private static final int SECOND_IN_MS = 1000;
     98     static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
     99     private static int PROCESS_STATE_FOREGROUND_SERVICE;
    100 
    101     private static final int PROCESS_STATE_TOP = 2;
    102 
    103     private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
    104 
    105     protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
    106     protected static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
    107 
    108     private static final int BATTERY_STATE_TIMEOUT_MS = 5000;
    109     private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 500;
    110 
    111     private static final int FOREGROUND_PROC_NETWORK_TIMEOUT_MS = 6000;
    112 
    113     // Must be higher than NETWORK_TIMEOUT_MS
    114     private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4;
    115 
    116     private static final IntentFilter BATTERY_CHANGED_FILTER =
    117             new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    118 
    119     private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
    120 
    121     protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 5_000; // 5 sec
    122 
    123     protected Context mContext;
    124     protected Instrumentation mInstrumentation;
    125     protected ConnectivityManager mCm;
    126     protected WifiManager mWfm;
    127     protected int mUid;
    128     private int mMyUid;
    129     private String mMeteredWifi;
    130     private MyServiceClient mServiceClient;
    131     private String mDeviceIdleConstantsSetting;
    132     private boolean mSupported;
    133     private boolean mIsLocationOn;
    134 
    135     @Override
    136     protected void setUp() throws Exception {
    137         super.setUp();
    138 
    139         PROCESS_STATE_FOREGROUND_SERVICE = (Integer) ActivityManager.class
    140                 .getDeclaredField("PROCESS_STATE_FOREGROUND_SERVICE").get(null);
    141         mInstrumentation = getInstrumentation();
    142         mContext = mInstrumentation.getContext();
    143         mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
    144         mWfm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
    145         mUid = getUid(TEST_APP2_PKG);
    146         mMyUid = getUid(mContext.getPackageName());
    147         mServiceClient = new MyServiceClient(mContext);
    148         mServiceClient.bind();
    149         mDeviceIdleConstantsSetting = "device_idle_constants";
    150         mIsLocationOn = isLocationOn();
    151         if (!mIsLocationOn) {
    152             enableLocation();
    153         }
    154         mSupported = setUpActiveNetworkMeteringState();
    155         setAppIdle(false);
    156 
    157         Log.i(TAG, "Apps status on " + getName() + ":\n"
    158                 + "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
    159                 + "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
    160 
    161         // app_idle_constants set in NetPolicyTestsPreparer.setUp() is not always sucessful (suspect
    162         // timing issue), here we set it again to make sure.
    163         final String appIdleConstants = "parole_duration=0,stable_charging_threshold=0";
    164         executeShellCommand("settings put global app_idle_constants " + appIdleConstants);
    165         final String currentConstants =
    166                 executeShellCommand("settings get global app_idle_constants");
    167         assertEquals(appIdleConstants, currentConstants);
    168    }
    169 
    170     @Override
    171     protected void tearDown() throws Exception {
    172         if (!mIsLocationOn) {
    173             disableLocation();
    174         }
    175         mServiceClient.unbind();
    176 
    177         super.tearDown();
    178     }
    179 
    180     private void enableLocation() throws Exception {
    181         Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE,
    182                 Settings.Secure.LOCATION_MODE_SENSORS_ONLY);
    183         assertEquals(Settings.Secure.LOCATION_MODE_SENSORS_ONLY,
    184                 Settings.Secure.getInt(mContext.getContentResolver(),
    185                         Settings.Secure.LOCATION_MODE));
    186     }
    187 
    188     private void disableLocation() throws Exception {
    189         Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE,
    190                 Settings.Secure.LOCATION_MODE_OFF);
    191         assertEquals(Settings.Secure.LOCATION_MODE_OFF,
    192                 Settings.Secure.getInt(mContext.getContentResolver(),
    193                         Settings.Secure.LOCATION_MODE));
    194     }
    195 
    196     private boolean isLocationOn() throws Exception {
    197         return Settings.Secure.getInt(mContext.getContentResolver(),
    198                 Settings.Secure.LOCATION_MODE) != Settings.Secure.LOCATION_MODE_OFF;
    199     }
    200 
    201     protected int getUid(String packageName) throws Exception {
    202         return mContext.getPackageManager().getPackageUid(packageName, 0);
    203     }
    204 
    205     protected void assertRestrictBackgroundChangedReceived(int expectedCount) throws Exception {
    206         assertRestrictBackgroundChangedReceived(DYNAMIC_RECEIVER, expectedCount);
    207         assertRestrictBackgroundChangedReceived(MANIFEST_RECEIVER, 0);
    208     }
    209 
    210     protected void assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)
    211             throws Exception {
    212         int attempts = 0;
    213         int count = 0;
    214         final int maxAttempts = 5;
    215         do {
    216             attempts++;
    217             count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
    218             if (count >= expectedCount) {
    219                 break;
    220             }
    221             Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
    222                     + attempts + " attempts; sleeping "
    223                     + SLEEP_TIME_SEC + " seconds before trying again");
    224             SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
    225         } while (attempts <= maxAttempts);
    226         assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
    227                 + maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
    228     }
    229 
    230     protected String sendOrderedBroadcast(Intent intent) throws Exception {
    231         return sendOrderedBroadcast(intent, ORDERED_BROADCAST_TIMEOUT_MS);
    232     }
    233 
    234     protected String sendOrderedBroadcast(Intent intent, int timeoutMs) throws Exception {
    235         final LinkedBlockingQueue<String> result = new LinkedBlockingQueue<>(1);
    236         Log.d(TAG, "Sending ordered broadcast: " + intent);
    237         mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
    238 
    239             @Override
    240             public void onReceive(Context context, Intent intent) {
    241                 final String resultData = getResultData();
    242                 if (resultData == null) {
    243                     Log.e(TAG, "Received null data from ordered intent");
    244                     return;
    245                 }
    246                 result.offer(resultData);
    247             }
    248         }, null, 0, null, null);
    249 
    250         final String resultData = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
    251         Log.d(TAG, "Ordered broadcast response after " + timeoutMs + "ms: " + resultData );
    252         return resultData;
    253     }
    254 
    255     protected int getNumberBroadcastsReceived(String receiverName, String action) throws Exception {
    256         return mServiceClient.getCounters(receiverName, action);
    257     }
    258 
    259     protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception {
    260         final String status = mServiceClient.getRestrictBackgroundStatus();
    261         assertNotNull("didn't get API status from app2", status);
    262         final String actualStatus = toString(Integer.parseInt(status));
    263         assertEquals("wrong status", toString(expectedStatus), actualStatus);
    264     }
    265 
    266     protected void assertMyRestrictBackgroundStatus(int expectedStatus) throws Exception {
    267         final int actualStatus = mCm.getRestrictBackgroundStatus();
    268         assertEquals("Wrong status", toString(expectedStatus), toString(actualStatus));
    269     }
    270 
    271     protected boolean isMyRestrictBackgroundStatus(int expectedStatus) throws Exception {
    272         final int actualStatus = mCm.getRestrictBackgroundStatus();
    273         if (expectedStatus != actualStatus) {
    274             Log.d(TAG, "Expected: " + toString(expectedStatus)
    275                     + " but actual: " + toString(actualStatus));
    276             return false;
    277         }
    278         return true;
    279     }
    280 
    281     protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
    282         assertBackgroundState(); // Sanity check.
    283         assertNetworkAccess(expectAllowed /* expectAvailable */, false /* needScreenOn */);
    284     }
    285 
    286     protected void assertForegroundNetworkAccess() throws Exception {
    287         assertForegroundState(); // Sanity check.
    288         // We verified that app is in foreground state but if the screen turns-off while
    289         // verifying for network access, the app will go into background state (in case app's
    290         // foreground status was due to top activity). So, turn the screen on when verifying
    291         // network connectivity.
    292         assertNetworkAccess(true /* expectAvailable */, true /* needScreenOn */);
    293     }
    294 
    295     protected void assertForegroundServiceNetworkAccess() throws Exception {
    296         assertForegroundServiceState(); // Sanity check.
    297         assertNetworkAccess(true /* expectAvailable */, false /* needScreenOn */);
    298     }
    299 
    300     /**
    301      * Whether this device suport this type of test.
    302      *
    303      * <p>Should be overridden when necessary (but always calling
    304      * {@code super.isSupported()} first), and explicitly used before each test
    305      * Example:
    306      *
    307      * <pre><code>
    308      * public void testSomething() {
    309      *    if (!isSupported()) return;
    310      * </code></pre>
    311      *
    312      * @return {@code true} by default.
    313      */
    314     protected boolean isSupported() throws Exception {
    315         return mSupported;
    316     }
    317 
    318     protected boolean isBatterySaverSupported() {
    319         return BatteryUtils.isBatterySaverSupported();
    320     }
    321 
    322     /**
    323      * Asserts that an app always have access while on foreground or running a foreground service.
    324      *
    325      * <p>This method will launch an activity and a foreground service to make the assertion, but
    326      * will finish the activity / stop the service afterwards.
    327      */
    328     protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception{
    329         // Checks foreground first.
    330         launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
    331         finishActivity();
    332 
    333         // Then foreground service
    334         launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
    335         stopForegroundService();
    336     }
    337 
    338     protected final void assertBackgroundState() throws Exception {
    339         final int maxTries = 30;
    340         ProcessState state = null;
    341         for (int i = 1; i <= maxTries; i++) {
    342             state = getProcessStateByUid(mUid);
    343             Log.v(TAG, "assertBackgroundState(): status for app2 (" + mUid + ") on attempt #" + i
    344                     + ": " + state);
    345             if (isBackground(state.state)) {
    346                 return;
    347             }
    348             Log.d(TAG, "App not on background state (" + state + ") on attempt #" + i
    349                     + "; sleeping 1s before trying again");
    350             SystemClock.sleep(SECOND_IN_MS);
    351         }
    352         fail("App2 is not on background state after " + maxTries + " attempts: " + state );
    353     }
    354 
    355     protected final void assertForegroundState() throws Exception {
    356         final int maxTries = 30;
    357         ProcessState state = null;
    358         for (int i = 1; i <= maxTries; i++) {
    359             state = getProcessStateByUid(mUid);
    360             Log.v(TAG, "assertForegroundState(): status for app2 (" + mUid + ") on attempt #" + i
    361                     + ": " + state);
    362             if (!isBackground(state.state)) {
    363                 return;
    364             }
    365             Log.d(TAG, "App not on foreground state on attempt #" + i
    366                     + "; sleeping 1s before trying again");
    367             turnScreenOn();
    368             SystemClock.sleep(SECOND_IN_MS);
    369         }
    370         fail("App2 is not on foreground state after " + maxTries + " attempts: " + state );
    371     }
    372 
    373     protected final void assertForegroundServiceState() throws Exception {
    374         final int maxTries = 30;
    375         ProcessState state = null;
    376         for (int i = 1; i <= maxTries; i++) {
    377             state = getProcessStateByUid(mUid);
    378             Log.v(TAG, "assertForegroundServiceState(): status for app2 (" + mUid + ") on attempt #"
    379                     + i + ": " + state);
    380             if (state.state == PROCESS_STATE_FOREGROUND_SERVICE) {
    381                 return;
    382             }
    383             Log.d(TAG, "App not on foreground service state on attempt #" + i
    384                     + "; sleeping 1s before trying again");
    385             SystemClock.sleep(SECOND_IN_MS);
    386         }
    387         fail("App2 is not on foreground service state after " + maxTries + " attempts: " + state );
    388     }
    389 
    390     /**
    391      * As per CDD requirements, if the device doesn't support data saver mode then
    392      * ConnectivityManager.getRestrictBackgroundStatus() will always return
    393      * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
    394      * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
    395      * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
    396      */
    397     protected boolean isDataSaverSupported() throws Exception {
    398         assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
    399         try {
    400             setRestrictBackground(true);
    401             return !isMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
    402         } finally {
    403             setRestrictBackground(false);
    404         }
    405     }
    406 
    407     /**
    408      * Returns whether an app state should be considered "background" for restriction purposes.
    409      */
    410     protected boolean isBackground(int state) {
    411         return state > PROCESS_STATE_FOREGROUND_SERVICE;
    412     }
    413 
    414     /**
    415      * Asserts whether the active network is available or not.
    416      */
    417     private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn)
    418             throws Exception {
    419         final int maxTries = 5;
    420         String error = null;
    421         int timeoutMs = 500;
    422 
    423         for (int i = 1; i <= maxTries; i++) {
    424             error = checkNetworkAccess(expectAvailable);
    425 
    426             if (error.isEmpty()) return;
    427 
    428             // TODO: ideally, it should retry only when it cannot connect to an external site,
    429             // or no retry at all! But, currently, the initial change fails almost always on
    430             // battery saver tests because the netd changes are made asynchronously.
    431             // Once b/27803922 is fixed, this retry mechanism should be revisited.
    432 
    433             Log.w(TAG, "Network status didn't match for expectAvailable=" + expectAvailable
    434                     + " on attempt #" + i + ": " + error + "\n"
    435                     + "Sleeping " + timeoutMs + "ms before trying again");
    436             if (needScreenOn) {
    437                 turnScreenOn();
    438             }
    439             // No sleep after the last turn
    440             if (i < maxTries) {
    441                 SystemClock.sleep(timeoutMs);
    442             }
    443             // Exponential back-off.
    444             timeoutMs = Math.min(timeoutMs*2, NETWORK_TIMEOUT_MS);
    445         }
    446         dumpOnFailure();
    447         fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries
    448                 + " attempts.\nLast error: " + error);
    449     }
    450 
    451     private void dumpOnFailure() throws Exception {
    452         dumpAllNetworkRules();
    453         Log.d(TAG, "Usagestats dump: " + getUsageStatsDump());
    454         executeShellCommand("settings get global app_idle_constants");
    455     }
    456 
    457     private void dumpAllNetworkRules() throws Exception {
    458         final String networkManagementDump = runShellCommand(mInstrumentation,
    459                 "dumpsys network_management").trim();
    460         final String networkPolicyDump = runShellCommand(mInstrumentation,
    461                 "dumpsys netpolicy").trim();
    462         TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
    463         splitter.setString(networkManagementDump);
    464         String next;
    465         Log.d(TAG, ">>> Begin network_management dump");
    466         while (splitter.hasNext()) {
    467             next = splitter.next();
    468             Log.d(TAG, next);
    469         }
    470         Log.d(TAG, "<<< End network_management dump");
    471         splitter.setString(networkPolicyDump);
    472         Log.d(TAG, ">>> Begin netpolicy dump");
    473         while (splitter.hasNext()) {
    474             next = splitter.next();
    475             Log.d(TAG, next);
    476         }
    477         Log.d(TAG, "<<< End netpolicy dump");
    478     }
    479 
    480     /**
    481      * Checks whether the network is available as expected.
    482      *
    483      * @return error message with the mismatch (or empty if assertion passed).
    484      */
    485     private String checkNetworkAccess(boolean expectAvailable) throws Exception {
    486         final String resultData = mServiceClient.checkNetworkStatus();
    487         return checkForAvailabilityInResultData(resultData, expectAvailable);
    488     }
    489 
    490     private String checkForAvailabilityInResultData(String resultData, boolean expectAvailable) {
    491         if (resultData == null) {
    492             assertNotNull("Network status from app2 is null", resultData);
    493         }
    494         // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
    495         final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
    496         assertEquals("Wrong network status: " + resultData, 5, parts.length); // Sanity check
    497         final State state = parts[0].equals("null") ? null : State.valueOf(parts[0]);
    498         final DetailedState detailedState = parts[1].equals("null")
    499                 ? null : DetailedState.valueOf(parts[1]);
    500         final boolean connected = Boolean.valueOf(parts[2]);
    501         final String connectionCheckDetails = parts[3];
    502         final String networkInfo = parts[4];
    503 
    504         final StringBuilder errors = new StringBuilder();
    505         final State expectedState;
    506         final DetailedState expectedDetailedState;
    507         if (expectAvailable) {
    508             expectedState = State.CONNECTED;
    509             expectedDetailedState = DetailedState.CONNECTED;
    510         } else {
    511             expectedState = State.DISCONNECTED;
    512             expectedDetailedState = DetailedState.BLOCKED;
    513         }
    514 
    515         if (expectAvailable != connected) {
    516             errors.append(String.format("External site connection failed: expected %s, got %s\n",
    517                     expectAvailable, connected));
    518         }
    519         if (expectedState != state || expectedDetailedState != detailedState) {
    520             errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
    521                     expectedState, expectedDetailedState, state, detailedState));
    522         }
    523 
    524         if (errors.length() > 0) {
    525             errors.append("\tnetworkInfo: " + networkInfo + "\n");
    526             errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
    527         }
    528         return errors.toString();
    529     }
    530 
    531     protected boolean isLowRamDevice() {
    532         final ActivityManager am = (ActivityManager) mContext.getSystemService(
    533             Context.ACTIVITY_SERVICE);
    534         return am.isLowRamDevice();
    535     }
    536 
    537     protected String executeShellCommand(String command) throws Exception {
    538         final String result = runShellCommand(mInstrumentation, command).trim();
    539         if (DEBUG) Log.d(TAG, "Command '" + command + "' returned '" + result + "'");
    540         return result;
    541     }
    542 
    543     /**
    544      * Runs a Shell command which is not expected to generate output.
    545      */
    546     protected void executeSilentShellCommand(String command) throws Exception {
    547         final String result = executeShellCommand(command);
    548         assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty());
    549     }
    550 
    551     /**
    552      * Asserts the result of a command, wait and re-running it a couple times if necessary.
    553      */
    554     protected void assertDelayedShellCommand(String command, final String expectedResult)
    555             throws Exception {
    556         assertDelayedShellCommand(command, 5, 1, expectedResult);
    557     }
    558 
    559     protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
    560             final String expectedResult) throws Exception {
    561         assertDelayedShellCommand(command, maxTries, napTimeSeconds, new ExpectResultChecker() {
    562 
    563             @Override
    564             public boolean isExpected(String result) {
    565                 return expectedResult.equals(result);
    566             }
    567 
    568             @Override
    569             public String getExpected() {
    570                 return expectedResult;
    571             }
    572         });
    573     }
    574 
    575     protected void assertDelayedShellCommand(String command, ExpectResultChecker checker)
    576             throws Exception {
    577         assertDelayedShellCommand(command, 5, 1, checker);
    578     }
    579     protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
    580             ExpectResultChecker checker) throws Exception {
    581         String result = "";
    582         for (int i = 1; i <= maxTries; i++) {
    583             result = executeShellCommand(command).trim();
    584             if (checker.isExpected(result)) return;
    585             Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
    586                     + checker.getExpected() + "' on attempt #" + i
    587                     + "; sleeping " + napTimeSeconds + "s before trying again");
    588             SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
    589         }
    590         fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
    591                 + maxTries
    592                 + " attempts. Last result: '" + result + "'");
    593     }
    594 
    595     /**
    596      * Sets the initial metering state for the active network.
    597      *
    598      * <p>It's called on setup and by default does nothing - it's up to the
    599      * subclasses to override.
    600      *
    601      * @return whether the tests in the subclass are supported on this device.
    602      */
    603     protected boolean setUpActiveNetworkMeteringState() throws Exception {
    604         return true;
    605     }
    606 
    607     /**
    608      * Makes sure the active network is not metered.
    609      *
    610      * <p>If the device does not supoprt un-metered networks (for example if it
    611      * only has cellular data but not wi-fi), it should return {@code false};
    612      * otherwise, it should return {@code true} (or fail if the un-metered
    613      * network could not be set).
    614      *
    615      * @return {@code true} if the network is now unmetered.
    616      */
    617     protected boolean setUnmeteredNetwork() throws Exception {
    618         final NetworkInfo info = mCm.getActiveNetworkInfo();
    619         assertNotNull("Could not get active network", info);
    620         if (!mCm.isActiveNetworkMetered()) {
    621             Log.d(TAG, "Active network is not metered: " + info);
    622         } else if (info.getType() == ConnectivityManager.TYPE_WIFI) {
    623             Log.i(TAG, "Setting active WI-FI network as not metered: " + info );
    624             setWifiMeteredStatus(false);
    625         } else {
    626             Log.d(TAG, "Active network cannot be set to un-metered: " + info);
    627             return false;
    628         }
    629         assertActiveNetworkMetered(false); // Sanity check.
    630         return true;
    631     }
    632 
    633     /**
    634      * Enables metering on the active network if supported.
    635      *
    636      * <p>If the device does not support metered networks it should return
    637      * {@code false}; otherwise, it should return {@code true} (or fail if the
    638      * metered network could not be set).
    639      *
    640      * @return {@code true} if the network is now metered.
    641      */
    642     protected boolean setMeteredNetwork() throws Exception {
    643         final NetworkInfo info = mCm.getActiveNetworkInfo();
    644         final boolean metered = mCm.isActiveNetworkMetered();
    645         if (metered) {
    646             Log.d(TAG, "Active network already metered: " + info);
    647             return true;
    648         } else if (info.getType() != ConnectivityManager.TYPE_WIFI) {
    649             Log.w(TAG, "Active network does not support metering: " + info);
    650             return false;
    651         } else {
    652             Log.w(TAG, "Active network not metered: " + info);
    653         }
    654         final String netId = setWifiMeteredStatus(true);
    655 
    656         // Set flag so status is reverted on resetMeteredNetwork();
    657         mMeteredWifi = netId;
    658         // Sanity check.
    659         assertWifiMeteredStatus(netId, true);
    660         assertActiveNetworkMetered(true);
    661         return true;
    662     }
    663 
    664     /**
    665      * Resets the device metering state to what it was before the test started.
    666      *
    667      * <p>This reverts any metering changes made by {@code setMeteredNetwork}.
    668      */
    669     protected void resetMeteredNetwork() throws Exception {
    670         if (mMeteredWifi != null) {
    671             Log.i(TAG, "resetMeteredNetwork(): SID '" + mMeteredWifi
    672                     + "' was set as metered by test case; resetting it");
    673             setWifiMeteredStatus(mMeteredWifi, false);
    674             assertActiveNetworkMetered(false); // Sanity check.
    675         }
    676     }
    677 
    678     private void assertActiveNetworkMetered(boolean expected) throws Exception {
    679         final int maxTries = 5;
    680         NetworkInfo info = null;
    681         for (int i = 1; i <= maxTries; i++) {
    682             info = mCm.getActiveNetworkInfo();
    683             if (info == null) {
    684                 Log.v(TAG, "No active network info on attempt #" + i
    685                         + "; sleeping 1s before polling again");
    686             } else if (mCm.isActiveNetworkMetered() != expected) {
    687                 Log.v(TAG, "Wrong metered status for active network " + info + "; expected="
    688                         + expected + "; sleeping 1s before polling again");
    689             } else {
    690                 break;
    691             }
    692             Thread.sleep(SECOND_IN_MS);
    693         }
    694         assertNotNull("No active network after " + maxTries + " attempts", info);
    695         assertEquals("Wrong metered status for active network " + info, expected,
    696                 mCm.isActiveNetworkMetered());
    697     }
    698 
    699     private String setWifiMeteredStatus(boolean metered) throws Exception {
    700         // We could call setWifiEnabled() here, but it might take sometime to be in a consistent
    701         // state (for example, if one of the saved network is not properly authenticated), so it's
    702         // better to let the hostside test take care of that.
    703         assertTrue("wi-fi is disabled", mWfm.isWifiEnabled());
    704         // TODO: if it's not guaranteed the device has wi-fi, we need to change the tests
    705         // to make the actual verification of restrictions optional.
    706         final String ssid = mWfm.getConnectionInfo().getSSID();
    707         return setWifiMeteredStatus(ssid, metered);
    708     }
    709 
    710     private String setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
    711         assertNotNull("null SSID", ssid);
    712         final String netId = ssid.trim().replaceAll("\"", ""); // remove quotes, if any.
    713         assertFalse("empty SSID", ssid.isEmpty());
    714 
    715         Log.i(TAG, "Setting wi-fi network " + netId + " metered status to " + metered);
    716         final String setCommand = "cmd netpolicy set metered-network " + netId + " " + metered;
    717         assertDelayedShellCommand(setCommand, "");
    718 
    719         return netId;
    720     }
    721 
    722     private void assertWifiMeteredStatus(String netId, boolean status) throws Exception {
    723         final String command = "cmd netpolicy list wifi-networks";
    724         final String expectedLine = netId + ";" + status;
    725         assertDelayedShellCommand(command, new ExpectResultChecker() {
    726 
    727             @Override
    728             public boolean isExpected(String result) {
    729                 return result.contains(expectedLine);
    730             }
    731 
    732             @Override
    733             public String getExpected() {
    734                 return "line containing " + expectedLine;
    735             }
    736         });
    737     }
    738 
    739     protected void setRestrictBackground(boolean enabled) throws Exception {
    740         executeShellCommand("cmd netpolicy set restrict-background " + enabled);
    741         final String output = executeShellCommand("cmd netpolicy get restrict-background ");
    742         final String expectedSuffix = enabled ? "enabled" : "disabled";
    743         // TODO: use MoreAsserts?
    744         assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
    745                 output.endsWith(expectedSuffix));
    746       }
    747 
    748     protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
    749         executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid);
    750         assertRestrictBackgroundWhitelist(uid, true);
    751         // UID policies live by the Highlander rule: "There can be only one".
    752         // Hence, if app is whitelisted, it should not be blacklisted.
    753         assertRestrictBackgroundBlacklist(uid, false);
    754     }
    755 
    756     protected void removeRestrictBackgroundWhitelist(int uid) throws Exception {
    757         executeShellCommand("cmd netpolicy remove restrict-background-whitelist " + uid);
    758         assertRestrictBackgroundWhitelist(uid, false);
    759     }
    760 
    761     protected void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
    762         assertRestrictBackground("restrict-background-whitelist", uid, expected);
    763     }
    764 
    765     protected void addRestrictBackgroundBlacklist(int uid) throws Exception {
    766         executeShellCommand("cmd netpolicy add restrict-background-blacklist " + uid);
    767         assertRestrictBackgroundBlacklist(uid, true);
    768         // UID policies live by the Highlander rule: "There can be only one".
    769         // Hence, if app is blacklisted, it should not be whitelisted.
    770         assertRestrictBackgroundWhitelist(uid, false);
    771     }
    772 
    773     protected void removeRestrictBackgroundBlacklist(int uid) throws Exception {
    774         executeShellCommand("cmd netpolicy remove restrict-background-blacklist " + uid);
    775         assertRestrictBackgroundBlacklist(uid, false);
    776     }
    777 
    778     protected void assertRestrictBackgroundBlacklist(int uid, boolean expected) throws Exception {
    779         assertRestrictBackground("restrict-background-blacklist", uid, expected);
    780     }
    781 
    782     protected void addAppIdleWhitelist(int uid) throws Exception {
    783         executeShellCommand("cmd netpolicy add app-idle-whitelist " + uid);
    784         assertAppIdleWhitelist(uid, true);
    785     }
    786 
    787     protected void removeAppIdleWhitelist(int uid) throws Exception {
    788         executeShellCommand("cmd netpolicy remove app-idle-whitelist " + uid);
    789         assertAppIdleWhitelist(uid, false);
    790     }
    791 
    792     protected void assertAppIdleWhitelist(int uid, boolean expected) throws Exception {
    793         assertRestrictBackground("app-idle-whitelist", uid, expected);
    794     }
    795 
    796     private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
    797         final int maxTries = 5;
    798         boolean actual = false;
    799         final String expectedUid = Integer.toString(uid);
    800         String uids = "";
    801         for (int i = 1; i <= maxTries; i++) {
    802             final String output =
    803                     executeShellCommand("cmd netpolicy list " + list);
    804             uids = output.split(":")[1];
    805             for (String candidate : uids.split(" ")) {
    806                 actual = candidate.trim().equals(expectedUid);
    807                 if (expected == actual) {
    808                     return;
    809                 }
    810             }
    811             Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
    812                     + expected + ", got " + actual + "); sleeping 1s before polling again");
    813             SystemClock.sleep(SECOND_IN_MS);
    814         }
    815         fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
    816                 + ". Full list: " + uids);
    817     }
    818 
    819     protected void addTempPowerSaveModeWhitelist(String packageName, long duration)
    820             throws Exception {
    821         Log.i(TAG, "Adding pkg " + packageName + " to temp-power-save-mode whitelist");
    822         executeShellCommand("dumpsys deviceidle tempwhitelist -d " + duration + " " + packageName);
    823     }
    824 
    825     protected void assertPowerSaveModeWhitelist(String packageName, boolean expected)
    826             throws Exception {
    827         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
    828         // need to use netpolicy for whitelisting
    829         assertDelayedShellCommand("dumpsys deviceidle whitelist =" + packageName,
    830                 Boolean.toString(expected));
    831     }
    832 
    833     protected void addPowerSaveModeWhitelist(String packageName) throws Exception {
    834         Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
    835         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
    836         // need to use netpolicy for whitelisting
    837         executeShellCommand("dumpsys deviceidle whitelist +" + packageName);
    838         assertPowerSaveModeWhitelist(packageName, true); // Sanity check
    839     }
    840 
    841     protected void removePowerSaveModeWhitelist(String packageName) throws Exception {
    842         Log.i(TAG, "Removing package " + packageName + " from power-save-mode whitelist");
    843         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
    844         // need to use netpolicy for whitelisting
    845         executeShellCommand("dumpsys deviceidle whitelist -" + packageName);
    846         assertPowerSaveModeWhitelist(packageName, false); // Sanity check
    847     }
    848 
    849     protected void assertPowerSaveModeExceptIdleWhitelist(String packageName, boolean expected)
    850             throws Exception {
    851         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
    852         // need to use netpolicy for whitelisting
    853         assertDelayedShellCommand("dumpsys deviceidle except-idle-whitelist =" + packageName,
    854                 Boolean.toString(expected));
    855     }
    856 
    857     protected void addPowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
    858         Log.i(TAG, "Adding package " + packageName + " to power-save-mode-except-idle whitelist");
    859         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
    860         // need to use netpolicy for whitelisting
    861         executeShellCommand("dumpsys deviceidle except-idle-whitelist +" + packageName);
    862         assertPowerSaveModeExceptIdleWhitelist(packageName, true); // Sanity check
    863     }
    864 
    865     protected void removePowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
    866         Log.i(TAG, "Removing package " + packageName
    867                 + " from power-save-mode-except-idle whitelist");
    868         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
    869         // need to use netpolicy for whitelisting
    870         executeShellCommand("dumpsys deviceidle except-idle-whitelist reset");
    871         assertPowerSaveModeExceptIdleWhitelist(packageName, false); // Sanity check
    872     }
    873 
    874     protected void turnBatteryOn() throws Exception {
    875         executeSilentShellCommand("cmd battery unplug");
    876         executeSilentShellCommand("cmd battery set status "
    877                 + BatteryManager.BATTERY_STATUS_DISCHARGING);
    878         assertBatteryState(false);
    879     }
    880 
    881     protected void turnBatteryOff() throws Exception {
    882         executeSilentShellCommand("cmd battery set ac " + BATTERY_PLUGGED_ANY);
    883         executeSilentShellCommand("cmd battery set level 100");
    884         executeSilentShellCommand("cmd battery set status "
    885                 + BatteryManager.BATTERY_STATUS_CHARGING);
    886         assertBatteryState(true);
    887     }
    888 
    889     private void assertBatteryState(boolean pluggedIn) throws Exception {
    890         final long endTime = SystemClock.elapsedRealtime() + BATTERY_STATE_TIMEOUT_MS;
    891         while (isDevicePluggedIn() != pluggedIn && SystemClock.elapsedRealtime() <= endTime) {
    892             Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
    893         }
    894         if (isDevicePluggedIn() != pluggedIn) {
    895             fail("Timed out waiting for the plugged-in state to change,"
    896                     + " expected pluggedIn: " + pluggedIn);
    897         }
    898     }
    899 
    900     private boolean isDevicePluggedIn() {
    901         final Intent batteryIntent = mContext.registerReceiver(null, BATTERY_CHANGED_FILTER);
    902         return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
    903     }
    904 
    905     protected void turnScreenOff() throws Exception {
    906         executeSilentShellCommand("input keyevent KEYCODE_SLEEP");
    907     }
    908 
    909     protected void turnScreenOn() throws Exception {
    910         executeSilentShellCommand("input keyevent KEYCODE_WAKEUP");
    911         executeSilentShellCommand("wm dismiss-keyguard");
    912     }
    913 
    914     protected void setBatterySaverMode(boolean enabled) throws Exception {
    915         Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
    916         if (enabled) {
    917             turnBatteryOn();
    918             executeSilentShellCommand("cmd power set-mode 1");
    919         } else {
    920             executeSilentShellCommand("cmd power set-mode 0");
    921             turnBatteryOff();
    922         }
    923     }
    924 
    925     protected void setDozeMode(boolean enabled) throws Exception {
    926         // Sanity check, since tests should check beforehand....
    927         assertTrue("Device does not support Doze Mode", isDozeModeEnabled());
    928 
    929         Log.i(TAG, "Setting Doze Mode to " + enabled);
    930         if (enabled) {
    931             turnBatteryOn();
    932             turnScreenOff();
    933             executeShellCommand("dumpsys deviceidle force-idle deep");
    934         } else {
    935             turnScreenOn();
    936             turnBatteryOff();
    937             executeShellCommand("dumpsys deviceidle unforce");
    938         }
    939         // Sanity check.
    940         assertDozeMode(enabled);
    941     }
    942 
    943     protected void assertDozeMode(boolean enabled) throws Exception {
    944         assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
    945     }
    946 
    947     protected boolean isDozeModeEnabled() throws Exception {
    948         final String result = executeShellCommand("cmd deviceidle enabled deep").trim();
    949         return result.equals("1");
    950     }
    951 
    952     protected void setAppIdle(boolean enabled) throws Exception {
    953         Log.i(TAG, "Setting app idle to " + enabled);
    954         executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
    955         assertAppIdle(enabled); // Sanity check
    956     }
    957 
    958     private String getUsageStatsDump() throws Exception {
    959         final String output = runShellCommand(mInstrumentation, "dumpsys usagestats").trim();
    960         final StringBuilder sb = new StringBuilder();
    961         final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
    962         splitter.setString(output);
    963         String str;
    964         while (splitter.hasNext()) {
    965             str = splitter.next();
    966             if (str.contains("package=")
    967                     && !str.contains(TEST_PKG) && !str.contains(TEST_APP2_PKG)) {
    968                 continue;
    969             }
    970             if (str.trim().startsWith("config=") || str.trim().startsWith("time=")) {
    971                 continue;
    972             }
    973             sb.append(str).append('\n');
    974         }
    975         return sb.toString();
    976     }
    977 
    978     protected void assertAppIdle(boolean enabled) throws Exception {
    979         try {
    980             assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 15, 2, "Idle=" + enabled);
    981         } catch (Throwable e) {
    982             Log.d(TAG, "UsageStats dump:\n" + getUsageStatsDump());
    983             executeShellCommand("settings get global app_idle_constants");
    984             throw e;
    985         }
    986     }
    987 
    988     /**
    989      * Starts a service that will register a broadcast receiver to receive
    990      * {@code RESTRICT_BACKGROUND_CHANGE} intents.
    991      * <p>
    992      * The service must run in a separate app because otherwise it would be killed every time
    993      * {@link #runDeviceTests(String, String)} is executed.
    994      */
    995     protected void registerBroadcastReceiver() throws Exception {
    996         mServiceClient.registerBroadcastReceiver();
    997 
    998         final Intent intent = new Intent(ACTION_RECEIVER_READY)
    999                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
   1000         // Wait until receiver is ready.
   1001         final int maxTries = 10;
   1002         for (int i = 1; i <= maxTries; i++) {
   1003             final String message = sendOrderedBroadcast(intent, SECOND_IN_MS * 4);
   1004             Log.d(TAG, "app2 receiver acked: " + message);
   1005             if (message != null) {
   1006                 return;
   1007             }
   1008             Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
   1009             SystemClock.sleep(SECOND_IN_MS);
   1010         }
   1011         fail("app2 receiver is not ready");
   1012     }
   1013 
   1014     protected void registerNetworkCallback(INetworkCallback cb) throws Exception {
   1015         mServiceClient.registerNetworkCallback(cb);
   1016     }
   1017 
   1018     /**
   1019      * Registers a {@link NotificationListenerService} implementation that will execute the
   1020      * notification actions right after the notification is sent.
   1021      */
   1022     protected void registerNotificationListenerService() throws Exception {
   1023         executeShellCommand("cmd notification allow_listener "
   1024                 + MyNotificationListenerService.getId());
   1025         final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
   1026         final ComponentName listenerComponent = MyNotificationListenerService.getComponentName();
   1027         assertTrue(listenerComponent + " has not been granted access",
   1028                 nm.isNotificationListenerAccessGranted(listenerComponent));
   1029     }
   1030 
   1031     protected void setPendingIntentWhitelistDuration(int durationMs) throws Exception {
   1032         executeSilentShellCommand(String.format(
   1033                 "settings put global %s %s=%d", mDeviceIdleConstantsSetting,
   1034                 "notification_whitelist_duration", durationMs));
   1035     }
   1036 
   1037     protected void resetDeviceIdleSettings() throws Exception {
   1038         executeShellCommand(String.format("settings delete global %s",
   1039                 mDeviceIdleConstantsSetting));
   1040     }
   1041 
   1042     protected void launchComponentAndAssertNetworkAccess(int type) throws Exception {
   1043         if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
   1044             startForegroundService();
   1045             assertForegroundServiceNetworkAccess();
   1046             return;
   1047         } else if (type == TYPE_COMPONENT_ACTIVTIY) {
   1048             turnScreenOn();
   1049             // Wait for screen-on state to propagate through the system.
   1050             SystemClock.sleep(2000);
   1051             final CountDownLatch latch = new CountDownLatch(1);
   1052             final Intent launchIntent = getIntentForComponent(type);
   1053             final Bundle extras = new Bundle();
   1054             final String[] errors = new String[]{null};
   1055             extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, errors));
   1056             launchIntent.putExtras(extras);
   1057             mContext.startActivity(launchIntent);
   1058             if (latch.await(FOREGROUND_PROC_NETWORK_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
   1059                 if (!errors[0].isEmpty()) {
   1060                     if (errors[0] == APP_NOT_FOREGROUND_ERROR) {
   1061                         // App didn't come to foreground when the activity is started, so try again.
   1062                         assertForegroundNetworkAccess();
   1063                     } else {
   1064                         dumpOnFailure();
   1065                         fail("Network is not available for app2 (" + mUid + "): " + errors[0]);
   1066                     }
   1067                 }
   1068             } else {
   1069                 dumpOnFailure();
   1070                 fail("Timed out waiting for network availability status from app2 (" + mUid + ")");
   1071             }
   1072         } else {
   1073             throw new IllegalArgumentException("Unknown type: " + type);
   1074         }
   1075     }
   1076 
   1077     private void startForegroundService() throws Exception {
   1078         final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_FOREGROUND_SERVICE);
   1079         mContext.startForegroundService(launchIntent);
   1080         assertForegroundServiceState();
   1081     }
   1082 
   1083     private Intent getIntentForComponent(int type) {
   1084         final Intent intent = new Intent();
   1085         if (type == TYPE_COMPONENT_ACTIVTIY) {
   1086             intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS))
   1087                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1088         } else if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
   1089             intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS))
   1090                     .setFlags(1);
   1091         } else {
   1092             fail("Unknown type: " + type);
   1093         }
   1094         return intent;
   1095     }
   1096 
   1097     protected void stopForegroundService() throws Exception {
   1098         executeShellCommand(String.format("am startservice -f 2 %s/%s",
   1099                 TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS));
   1100         // NOTE: cannot assert state because it depends on whether activity was on top before.
   1101     }
   1102 
   1103     private Binder getNewNetworkStateObserver(final CountDownLatch latch,
   1104             final String[] errors) {
   1105         return new INetworkStateObserver.Stub() {
   1106             @Override
   1107             public boolean isForeground() {
   1108                 try {
   1109                     final ProcessState state = getProcessStateByUid(mUid);
   1110                     return !isBackground(state.state);
   1111                 } catch (Exception e) {
   1112                     Log.d(TAG, "Error while reading the proc state for " + mUid + ": " + e);
   1113                     return false;
   1114                 }
   1115             }
   1116 
   1117             @Override
   1118             public void onNetworkStateChecked(String resultData) {
   1119                 errors[0] = resultData == null
   1120                         ? APP_NOT_FOREGROUND_ERROR
   1121                         : checkForAvailabilityInResultData(resultData, true);
   1122                 latch.countDown();
   1123             }
   1124         };
   1125     }
   1126 
   1127     /**
   1128      * Finishes an activity on app2 so its process is demoted fromforeground status.
   1129      */
   1130     protected void finishActivity() throws Exception {
   1131         executeShellCommand("am broadcast -a "
   1132                 + " com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY "
   1133                 + "--receiver-foreground --receiver-registered-only");
   1134     }
   1135 
   1136     protected void sendNotification(int notificationId, String notificationType) throws Exception {
   1137         Log.d(TAG, "Sending notification broadcast (id=" + notificationId
   1138                 + ", type=" + notificationType);
   1139         mServiceClient.sendNotification(notificationId, notificationType);
   1140     }
   1141 
   1142     protected String showToast() {
   1143         final Intent intent = new Intent(ACTION_SHOW_TOAST);
   1144         intent.setPackage(TEST_APP2_PKG);
   1145         Log.d(TAG, "Sending request to show toast");
   1146         try {
   1147             return sendOrderedBroadcast(intent, 3 * SECOND_IN_MS);
   1148         } catch (Exception e) {
   1149             return "";
   1150         }
   1151     }
   1152 
   1153     private String toString(int status) {
   1154         switch (status) {
   1155             case RESTRICT_BACKGROUND_STATUS_DISABLED:
   1156                 return "DISABLED";
   1157             case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
   1158                 return "WHITELISTED";
   1159             case RESTRICT_BACKGROUND_STATUS_ENABLED:
   1160                 return "ENABLED";
   1161             default:
   1162                 return "UNKNOWN_STATUS_" + status;
   1163         }
   1164     }
   1165 
   1166     private ProcessState getProcessStateByUid(int uid) throws Exception {
   1167         return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid));
   1168     }
   1169 
   1170     private static class ProcessState {
   1171         private final String fullState;
   1172         final int state;
   1173 
   1174         ProcessState(String fullState) {
   1175             this.fullState = fullState;
   1176             try {
   1177                 this.state = Integer.parseInt(fullState.split(" ")[0]);
   1178             } catch (Exception e) {
   1179                 throw new IllegalArgumentException("Could not parse " + fullState);
   1180             }
   1181         }
   1182 
   1183         @Override
   1184         public String toString() {
   1185             return fullState;
   1186         }
   1187     }
   1188 
   1189     /**
   1190      * Helper class used to assert the result of a Shell command.
   1191      */
   1192     protected static interface ExpectResultChecker {
   1193         /**
   1194          * Checkes whether the result of the command matched the expectation.
   1195          */
   1196         boolean isExpected(String result);
   1197         /**
   1198          * Gets the expected result so it's displayed on log and failure messages.
   1199          */
   1200         String getExpected();
   1201     }
   1202 }
   1203