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