• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.cts.util.SystemUtil.runShellCommand;
20 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
21 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
22 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
23 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
24 
25 import java.util.concurrent.LinkedBlockingQueue;
26 import java.util.concurrent.TimeUnit;
27 
28 import android.app.Instrumentation;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.net.ConnectivityManager;
33 import android.net.NetworkInfo;
34 import android.net.NetworkInfo.DetailedState;
35 import android.net.NetworkInfo.State;
36 import android.net.wifi.WifiManager;
37 import android.os.SystemClock;
38 import android.service.notification.NotificationListenerService;
39 import android.test.InstrumentationTestCase;
40 import android.util.Log;
41 
42 /**
43  * Superclass for tests related to background network restrictions.
44  */
45 abstract class AbstractRestrictBackgroundNetworkTestCase extends InstrumentationTestCase {
46     protected static final String TAG = "RestrictBackgroundNetworkTests";
47 
48     protected static final String TEST_PKG = "com.android.cts.net.hostside";
49     protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
50 
51     private static final int SLEEP_TIME_SEC = 1;
52     private static final boolean DEBUG = true;
53 
54     // Constants below must match values defined on app2's Common.java
55     private static final String MANIFEST_RECEIVER = "ManifestReceiver";
56     private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
57     private static final String ACTION_GET_COUNTERS =
58             "com.android.cts.net.hostside.app2.action.GET_COUNTERS";
59     private static final String ACTION_GET_RESTRICT_BACKGROUND_STATUS =
60             "com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS";
61     private static final String ACTION_CHECK_NETWORK =
62             "com.android.cts.net.hostside.app2.action.CHECK_NETWORK";
63     private static final String ACTION_RECEIVER_READY =
64             "com.android.cts.net.hostside.app2.action.RECEIVER_READY";
65     static final String ACTION_SEND_NOTIFICATION =
66             "com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION";
67     private static final String EXTRA_ACTION = "com.android.cts.net.hostside.app2.extra.ACTION";
68     private static final String EXTRA_RECEIVER_NAME =
69             "com.android.cts.net.hostside.app2.extra.RECEIVER_NAME";
70     private static final String EXTRA_NOTIFICATION_ID =
71             "com.android.cts.net.hostside.app2.extra.NOTIFICATION_ID";
72     private static final String NETWORK_STATUS_SEPARATOR = "\\|";
73     private static final int SECOND_IN_MS = 1000;
74     static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
75     private static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
76 
77 
78     // Must be higher than NETWORK_TIMEOUT_MS
79     private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4;
80 
81     protected Context mContext;
82     protected Instrumentation mInstrumentation;
83     protected ConnectivityManager mCm;
84     protected WifiManager mWfm;
85     protected int mUid;
86     private String mMeteredWifi;
87 
88     @Override
setUp()89     protected void setUp() throws Exception {
90         super.setUp();
91 
92         mInstrumentation = getInstrumentation();
93         mContext = mInstrumentation.getContext();
94         mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
95         mWfm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
96         mUid = getUid(TEST_APP2_PKG);
97         final int myUid = getUid(mContext.getPackageName());
98 
99         Log.i(TAG, "Apps status on " + getName() + ":\n"
100                 + "\ttest app: uid=" + myUid + ", state=" + getProcessStateByUid(myUid) + "\n"
101                 + "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
102    }
103 
getUid(String packageName)104     protected int getUid(String packageName) throws Exception {
105         return mContext.getPackageManager().getPackageUid(packageName, 0);
106     }
107 
assertRestrictBackgroundChangedReceived(int expectedCount)108     protected void assertRestrictBackgroundChangedReceived(int expectedCount) throws Exception {
109         assertRestrictBackgroundChangedReceived(DYNAMIC_RECEIVER, expectedCount);
110         assertRestrictBackgroundChangedReceived(MANIFEST_RECEIVER, 0);
111     }
112 
assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)113     protected void assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)
114             throws Exception {
115         int attempts = 0;
116         int count = 0;
117         final int maxAttempts = 5;
118         do {
119             attempts++;
120             count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
121             if (count == expectedCount) {
122                 break;
123             }
124             Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
125                     + attempts + " attempts; sleeping "
126                     + SLEEP_TIME_SEC + " seconds before trying again");
127             SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
128         } while (attempts <= maxAttempts);
129         assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
130                 + maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
131     }
132 
sendOrderedBroadcast(Intent intent)133     protected String sendOrderedBroadcast(Intent intent) throws Exception {
134         return sendOrderedBroadcast(intent, ORDERED_BROADCAST_TIMEOUT_MS);
135     }
136 
sendOrderedBroadcast(Intent intent, int timeoutMs)137     protected String sendOrderedBroadcast(Intent intent, int timeoutMs) throws Exception {
138         final LinkedBlockingQueue<String> result = new LinkedBlockingQueue<>(1);
139         Log.d(TAG, "Sending ordered broadcast: " + intent);
140         mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
141 
142             @Override
143             public void onReceive(Context context, Intent intent) {
144                 final String resultData = getResultData();
145                 if (resultData == null) {
146                     Log.e(TAG, "Received null data from ordered intent");
147                     return;
148                 }
149                 result.offer(resultData);
150             }
151         }, null, 0, null, null);
152 
153         final String resultData = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
154         Log.d(TAG, "Ordered broadcast response after " + timeoutMs + "ms: " + resultData );
155         return resultData;
156     }
157 
getNumberBroadcastsReceived(String receiverName, String action)158     protected int getNumberBroadcastsReceived(String receiverName, String action) throws Exception {
159         final Intent intent = new Intent(ACTION_GET_COUNTERS);
160         intent.putExtra(EXTRA_ACTION, ACTION_RESTRICT_BACKGROUND_CHANGED);
161         intent.putExtra(EXTRA_RECEIVER_NAME, receiverName);
162         final String resultData = sendOrderedBroadcast(intent);
163         assertNotNull("timeout waiting for ordered broadcast result", resultData);
164         return Integer.valueOf(resultData);
165     }
166 
assertRestrictBackgroundStatus(int expectedStatus)167     protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception {
168         final Intent intent = new Intent(ACTION_GET_RESTRICT_BACKGROUND_STATUS);
169         final String resultData = sendOrderedBroadcast(intent);
170         assertNotNull("timeout waiting for ordered broadcast result", resultData);
171         final String actualStatus = toString(Integer.parseInt(resultData));
172         assertEquals("wrong status", toString(expectedStatus), actualStatus);
173     }
174 
assertBackgroundNetworkAccess(boolean expectAllowed)175     protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
176         assertBackgroundState(); // Sanity check.
177         assertNetworkAccess(expectAllowed);
178     }
179 
assertForegroundNetworkAccess()180     protected void assertForegroundNetworkAccess() throws Exception {
181         assertForegroundState(); // Sanity check.
182         assertNetworkAccess(true);
183     }
184 
assertForegroundServiceNetworkAccess()185     protected void assertForegroundServiceNetworkAccess() throws Exception {
186         assertForegroundServiceState(); // Sanity check.
187         assertNetworkAccess(true);
188     }
189 
190     /**
191      * Whether this device suport this type of test.
192      *
193      * <p>Should be overridden when necessary, and explicitly used before each test. Example:
194      *
195      * <pre><code>
196      * public void testSomething() {
197      *    if (!isSupported()) return;
198      * </code></pre>
199      *
200      * @return {@code true} by default.
201      */
isSupported()202     protected boolean isSupported() throws Exception {
203         return true;
204     }
205 
206     /**
207      * Asserts that an app always have access while on foreground or running a foreground service.
208      *
209      * <p>This method will launch an activity and a foreground service to make the assertion, but
210      * will finish the activity / stop the service afterwards.
211      */
assertsForegroundAlwaysHasNetworkAccess()212     protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception{
213         // Checks foreground first.
214         launchActivity();
215         assertForegroundNetworkAccess();
216         finishActivity();
217 
218         // Then foreground service
219         startForegroundService();
220         assertForegroundServiceNetworkAccess();
221         stopForegroundService();
222     }
223 
assertBackgroundState()224     protected final void assertBackgroundState() throws Exception {
225         final int maxTries = 30;
226         ProcessState state = null;
227         for (int i = 1; i <= maxTries; i++) {
228             state = getProcessStateByUid(mUid);
229             Log.v(TAG, "assertBackgroundState(): status for app2 (" + mUid + ") on attempt #" + i
230                     + ": " + state);
231             if (isBackground(state.state)) {
232                 return;
233             }
234             Log.d(TAG, "App not on background state on attempt #" + i
235                     + "; sleeping 1s before trying again");
236             SystemClock.sleep(SECOND_IN_MS);
237         }
238         fail("App2 is not on background state after " + maxTries + " attempts: " + state );
239     }
240 
assertForegroundState()241     protected final void assertForegroundState() throws Exception {
242         final int maxTries = 30;
243         ProcessState state = null;
244         for (int i = 1; i <= maxTries; i++) {
245             state = getProcessStateByUid(mUid);
246             Log.v(TAG, "assertForegroundState(): status for app2 (" + mUid + ") on attempt #" + i
247                     + ": " + state);
248             if (!isBackground(state.state)) {
249                 return;
250             }
251             Log.d(TAG, "App not on foreground state on attempt #" + i
252                     + "; sleeping 1s before trying again");
253             SystemClock.sleep(SECOND_IN_MS);
254         }
255         fail("App2 is not on foreground state after " + maxTries + " attempts: " + state );
256     }
257 
assertForegroundServiceState()258     protected final void assertForegroundServiceState() throws Exception {
259         final int maxTries = 30;
260         ProcessState state = null;
261         for (int i = 1; i <= maxTries; i++) {
262             state = getProcessStateByUid(mUid);
263             Log.v(TAG, "assertForegroundServiceState(): status for app2 (" + mUid + ") on attempt #"
264                     + i + ": " + state);
265             if (state.state == PROCESS_STATE_FOREGROUND_SERVICE) {
266                 return;
267             }
268             Log.d(TAG, "App not on foreground service state on attempt #" + i
269                     + "; sleeping 1s before trying again");
270             SystemClock.sleep(SECOND_IN_MS);
271         }
272         fail("App2 is not on foreground service state after " + maxTries + " attempts: " + state );
273     }
274 
275     /**
276      * Returns whether an app state should be considered "background" for restriction purposes.
277      */
isBackground(int state)278     protected boolean isBackground(int state) {
279         return state > PROCESS_STATE_FOREGROUND_SERVICE;
280     }
281 
282     /**
283      * Asserts whether the active network is available or not.
284      */
assertNetworkAccess(boolean expectAvailable)285     private void assertNetworkAccess(boolean expectAvailable) throws Exception {
286         final Intent intent = new Intent(ACTION_CHECK_NETWORK);
287 
288         final int maxTries = 5;
289         String resultData = null;
290         for (int i = 1; i <= maxTries; i++) {
291             resultData = sendOrderedBroadcast(intent);
292             assertNotNull("timeout waiting for ordered broadcast", resultData);
293 
294             // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
295             final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
296             assertEquals("Wrong network status: " + resultData, 5, parts.length); // Sanity check
297             final State state = State.valueOf(parts[0]);
298             final DetailedState detailedState = DetailedState.valueOf(parts[1]);
299             final boolean connected = Boolean.valueOf(parts[2]);
300             final String connectionCheckDetails = parts[3];
301             final String networkInfo = parts[4];
302 
303             if (expectAvailable) {
304                 if (!connected) {
305                     // Since it's establishing a connection to an external site, it could be flaky.
306                     Log.w(TAG, "Failed to connect to an external site on attempt #" + i +
307                             " (error: " + connectionCheckDetails + ", NetworkInfo: " + networkInfo
308                             + "); sleeping " + NETWORK_TIMEOUT_MS + "ms before trying again");
309                     SystemClock.sleep(NETWORK_TIMEOUT_MS);
310                     continue;
311                 }
312                 if (state != State.CONNECTED) {
313                     Log.d(TAG, "State (" + state + ") not set to CONNECTED on attempt #" + i
314                             + "; sleeping 1s before trying again");
315                     SystemClock.sleep(SECOND_IN_MS);
316                 } else {
317                     assertEquals("wrong detailed state for " + networkInfo,
318                             DetailedState.CONNECTED, detailedState);
319                     return;
320                 }
321                 return;
322             } else {
323                 assertFalse("should not be connected: " + connectionCheckDetails
324                         + " (network info: " + networkInfo + ")", connected);
325                 if (state != State.DISCONNECTED) {
326                     // When the network info state change, it's possible the app still get the
327                     // previous value, so we need to retry a couple times.
328                     Log.d(TAG, "State (" + state + ") not set to DISCONNECTED on attempt #" + i
329                             + "; sleeping 1s before trying again");
330                     SystemClock.sleep(SECOND_IN_MS);
331                 } else {
332                     assertEquals("wrong detailed state for " + networkInfo,
333                             DetailedState.BLOCKED, detailedState);
334                    return;
335                 }
336             }
337         }
338         fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries
339                 + " attempts. Last data: " + resultData);
340     }
341 
executeShellCommand(String command)342     protected String executeShellCommand(String command) throws Exception {
343         final String result = runShellCommand(mInstrumentation, command).trim();
344         if (DEBUG) Log.d(TAG, "Command '" + command + "' returned '" + result + "'");
345         return result;
346     }
347 
348     /**
349      * Runs a Shell command which is not expected to generate output.
350      */
executeSilentShellCommand(String command)351     protected void executeSilentShellCommand(String command) throws Exception {
352         final String result = executeShellCommand(command);
353         assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty());
354     }
355 
356     /**
357      * Asserts the result of a command, wait and re-running it a couple times if necessary.
358      */
assertDelayedShellCommand(String command, final String expectedResult)359     protected void assertDelayedShellCommand(String command, final String expectedResult)
360             throws Exception {
361         assertDelayedShellCommand(command, 5, 1, expectedResult);
362     }
363 
assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds, final String expectedResult)364     protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
365             final String expectedResult) throws Exception {
366         assertDelayedShellCommand(command, maxTries, napTimeSeconds, new ExpectResultChecker() {
367 
368             @Override
369             public boolean isExpected(String result) {
370                 return expectedResult.equals(result);
371             }
372 
373             @Override
374             public String getExpected() {
375                 return expectedResult;
376             }
377         });
378     }
379 
assertDelayedShellCommand(String command, ExpectResultChecker checker)380     protected void assertDelayedShellCommand(String command, ExpectResultChecker checker)
381             throws Exception {
382         assertDelayedShellCommand(command, 5, 1, checker);
383     }
assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds, ExpectResultChecker checker)384     protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
385             ExpectResultChecker checker) throws Exception {
386         String result = "";
387         for (int i = 1; i <= maxTries; i++) {
388             result = executeShellCommand(command).trim();
389             if (checker.isExpected(result)) return;
390             Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
391                     + checker.getExpected() + "' on attempt #" + i
392                     + "; sleeping " + napTimeSeconds + "s before trying again");
393             SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
394         }
395         fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
396                 + maxTries
397                 + " attempts. Last result: '" + result + "'");
398     }
399 
400     /**
401      * Puts the device in a state where the active network is metered, or fail if it can't achieve
402      * that state.
403      */
setMeteredNetwork()404     protected void setMeteredNetwork() throws Exception {
405         final NetworkInfo info = mCm.getActiveNetworkInfo();
406         final boolean metered = mCm.isActiveNetworkMetered();
407         if (metered) {
408             Log.d(TAG, "Active network already metered: " + info);
409             return;
410         } else {
411             Log.w(TAG, "Active network not metered: " + info);
412         }
413         final String netId = setWifiMeteredStatus(true);
414 
415         // Set flag so status is reverted on resetMeteredNetwork();
416         mMeteredWifi = netId;
417         // Sanity check.
418         assertWifiMeteredStatus(netId, true);
419         assertActiveNetworkMetered(true);
420     }
421 
422     /**
423      * Puts the device in a state where the active network is not metered, or fail if it can't
424      * achieve that state.
425      * <p>It assumes the device has a valid WI-FI connection.
426      */
resetMeteredNetwork()427     protected void resetMeteredNetwork() throws Exception {
428         if (mMeteredWifi != null) {
429             Log.i(TAG, "resetMeteredNetwork(): SID '" + mMeteredWifi
430                     + "' was set as metered by test case; resetting it");
431             setWifiMeteredStatus(mMeteredWifi, false);
432         } else {
433             final NetworkInfo info = mCm.getActiveNetworkInfo();
434             assertNotNull("Could not get active network", info);
435             if (!mCm.isActiveNetworkMetered()) {
436                 Log.d(TAG, "Active network is not metered: " + info);
437             } else if (info.getType() == ConnectivityManager.TYPE_WIFI) {
438                 Log.i(TAG, "Setting active WI-FI network as metered: " + info );
439                 setWifiMeteredStatus(false);
440             } else {
441                 fail("Active network is not WI-FI hence cannot be set as non-metered: " + info);
442             }
443         }
444         assertActiveNetworkMetered(false); // Sanity check.
445     }
446 
assertActiveNetworkMetered(boolean expected)447     private void assertActiveNetworkMetered(boolean expected) throws Exception {
448         final int maxTries = 5;
449         NetworkInfo info = null;
450         for (int i = 1; i <= maxTries; i++) {
451             info = mCm.getActiveNetworkInfo();
452             if (info != null) {
453                 break;
454             }
455             Log.v(TAG, "No active network info on attempt #" + i
456                     + "; sleeping 1s before polling again");
457             Thread.sleep(SECOND_IN_MS);
458         }
459         assertNotNull("No active network after " + maxTries + " attempts", info);
460         assertEquals("Wrong metered status for active network " + info, expected,
461                 mCm.isActiveNetworkMetered());
462     }
463 
setWifiMeteredStatus(boolean metered)464     private String setWifiMeteredStatus(boolean metered) throws Exception {
465         // We could call setWifiEnabled() here, but it might take sometime to be in a consistent
466         // state (for example, if one of the saved network is not properly authenticated), so it's
467         // better to let the hostside test take care of that.
468         assertTrue("wi-fi is disabled", mWfm.isWifiEnabled());
469         // TODO: if it's not guaranteed the device has wi-fi, we need to change the tests
470         // to make the actual verification of restrictions optional.
471         final String ssid = mWfm.getConnectionInfo().getSSID();
472         return setWifiMeteredStatus(ssid, metered);
473     }
474 
setWifiMeteredStatus(String ssid, boolean metered)475     private String setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
476         assertNotNull("null SSID", ssid);
477         final String netId = ssid.trim().replaceAll("\"", ""); // remove quotes, if any.
478         assertFalse("empty SSID", ssid.isEmpty());
479 
480         Log.i(TAG, "Setting wi-fi network " + netId + " metered status to " + metered);
481         final String setCommand = "cmd netpolicy set metered-network " + netId + " " + metered;
482         assertDelayedShellCommand(setCommand, "");
483 
484         return netId;
485     }
486 
assertWifiMeteredStatus(String netId, boolean status)487     private void assertWifiMeteredStatus(String netId, boolean status) throws Exception {
488         final String command = "cmd netpolicy list wifi-networks";
489         final String expectedLine = netId + ";" + status;
490         assertDelayedShellCommand(command, new ExpectResultChecker() {
491 
492             @Override
493             public boolean isExpected(String result) {
494                 return result.contains(expectedLine);
495             }
496 
497             @Override
498             public String getExpected() {
499                 return "line containing " + expectedLine;
500             }
501         });
502     }
503 
setRestrictBackground(boolean enabled)504     protected void setRestrictBackground(boolean enabled) throws Exception {
505         executeShellCommand("cmd netpolicy set restrict-background " + enabled);
506         final String output = executeShellCommand("cmd netpolicy get restrict-background ");
507         final String expectedSuffix = enabled ? "enabled" : "disabled";
508         // TODO: use MoreAsserts?
509         assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
510                 output.endsWith(expectedSuffix));
511       }
512 
addRestrictBackgroundWhitelist(int uid)513     protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
514         executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid);
515         assertRestrictBackgroundWhitelist(uid, true);
516     }
517 
removeRestrictBackgroundWhitelist(int uid)518     protected void removeRestrictBackgroundWhitelist(int uid) throws Exception {
519         executeShellCommand("cmd netpolicy remove restrict-background-whitelist " + uid);
520         assertRestrictBackgroundWhitelist(uid, false);
521     }
522 
assertRestrictBackgroundWhitelist(int uid, boolean expected)523     protected void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
524         assertRestrictBackground("restrict-background-whitelist", uid, expected);
525     }
526 
addRestrictBackgroundBlacklist(int uid)527     protected void addRestrictBackgroundBlacklist(int uid) throws Exception {
528         executeShellCommand("cmd netpolicy add restrict-background-blacklist " + uid);
529         assertRestrictBackgroundBlacklist(uid, true);
530     }
531 
removeRestrictBackgroundBlacklist(int uid)532     protected void removeRestrictBackgroundBlacklist(int uid) throws Exception {
533         executeShellCommand("cmd netpolicy remove restrict-background-blacklist " + uid);
534         assertRestrictBackgroundBlacklist(uid, false);
535     }
536 
assertRestrictBackgroundBlacklist(int uid, boolean expected)537     protected void assertRestrictBackgroundBlacklist(int uid, boolean expected) throws Exception {
538         assertRestrictBackground("restrict-background-blacklist", uid, expected);
539     }
540 
assertRestrictBackground(String list, int uid, boolean expected)541     private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
542         final int maxTries = 5;
543         boolean actual = false;
544         final String expectedUid = Integer.toString(uid);
545         String uids = "";
546         for (int i = 1; i <= maxTries; i++) {
547             final String output =
548                     executeShellCommand("cmd netpolicy list " + list);
549             uids = output.split(":")[1];
550             for (String candidate : uids.split(" ")) {
551                 actual = candidate.trim().equals(expectedUid);
552                 if (expected == actual) {
553                     return;
554                 }
555             }
556             Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
557                     + expected + ", got " + actual + "); sleeping 1s before polling again");
558             SystemClock.sleep(SECOND_IN_MS);
559         }
560         fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
561                 + ". Full list: " + uids);
562     }
563 
assertPowerSaveModeWhitelist(String packageName, boolean expected)564     protected void assertPowerSaveModeWhitelist(String packageName, boolean expected)
565             throws Exception {
566         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
567         // need to use netpolicy for whitelisting
568         assertDelayedShellCommand("dumpsys deviceidle whitelist =" + packageName,
569                 Boolean.toString(expected));
570     }
571 
addPowerSaveModeWhitelist(String packageName)572     protected void addPowerSaveModeWhitelist(String packageName) throws Exception {
573         Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
574         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
575         // need to use netpolicy for whitelisting
576         executeShellCommand("dumpsys deviceidle whitelist +" + packageName);
577         assertPowerSaveModeWhitelist(packageName, true); // Sanity check
578     }
579 
removePowerSaveModeWhitelist(String packageName)580     protected void removePowerSaveModeWhitelist(String packageName) throws Exception {
581         Log.i(TAG, "Removing package " + packageName + " from power-save-mode whitelist");
582         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
583         // need to use netpolicy for whitelisting
584         executeShellCommand("dumpsys deviceidle whitelist -" + packageName);
585         assertPowerSaveModeWhitelist(packageName, false); // Sanity check
586     }
587 
turnBatteryOff()588     protected void turnBatteryOff() throws Exception {
589         executeSilentShellCommand("cmd battery unplug");
590     }
591 
turnBatteryOn()592     protected void turnBatteryOn() throws Exception {
593         executeSilentShellCommand("cmd battery reset");
594     }
595 
turnScreenOff()596     protected void turnScreenOff() throws Exception {
597         executeSilentShellCommand("input keyevent KEYCODE_SLEEP");
598     }
599 
turnScreenOn()600     protected void turnScreenOn() throws Exception {
601         executeSilentShellCommand("input keyevent KEYCODE_WAKEUP");
602         executeSilentShellCommand("wm dismiss-keyguard");
603     }
604 
setBatterySaverMode(boolean enabled)605     protected void setBatterySaverMode(boolean enabled) throws Exception {
606         Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
607         if (enabled) {
608             turnBatteryOff();
609             executeSilentShellCommand("cmd battery unplug");
610             executeSilentShellCommand("settings put global low_power 1");
611         } else {
612             turnBatteryOn();
613         }
614     }
615 
setDozeMode(boolean enabled)616     protected void setDozeMode(boolean enabled) throws Exception {
617         // Sanity check, since tests should check beforehand....
618         assertTrue("Device does not support Doze Mode", isDozeModeEnabled());
619 
620         Log.i(TAG, "Setting Doze Mode to " + enabled);
621         if (enabled) {
622             turnBatteryOff();
623             turnScreenOff();
624             executeShellCommand("dumpsys deviceidle force-idle deep");
625         } else {
626             turnScreenOn();
627             turnBatteryOn();
628             executeShellCommand("dumpsys deviceidle unforce");
629         }
630         // Sanity check.
631         assertDozeMode(enabled);
632     }
633 
assertDozeMode(boolean enabled)634     protected void assertDozeMode(boolean enabled) throws Exception {
635         assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
636     }
637 
isDozeModeEnabled()638     protected boolean isDozeModeEnabled() throws Exception {
639         final String result = executeShellCommand("cmd deviceidle enabled deep").trim();
640         return result.equals("1");
641     }
642 
setAppIdle(boolean enabled)643     protected void setAppIdle(boolean enabled) throws Exception {
644         Log.i(TAG, "Setting app idle to " + enabled);
645         executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
646         assertAppIdle(enabled); // Sanity check
647     }
648 
assertAppIdle(boolean enabled)649     protected void assertAppIdle(boolean enabled) throws Exception {
650         assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 10, 2, "Idle=" + enabled);
651     }
652 
653     /**
654      * Starts a service that will register a broadcast receiver to receive
655      * {@code RESTRICT_BACKGROUND_CHANGE} intents.
656      * <p>
657      * The service must run in a separate app because otherwise it would be killed every time
658      * {@link #runDeviceTests(String, String)} is executed.
659      */
registerBroadcastReceiver()660     protected void registerBroadcastReceiver() throws Exception {
661         executeShellCommand("am startservice com.android.cts.net.hostside.app2/.MyService");
662         // Wait until receiver is ready.
663         final int maxTries = 5;
664         for (int i = 1; i <= maxTries; i++) {
665             final String message =
666                     sendOrderedBroadcast(new Intent(ACTION_RECEIVER_READY), SECOND_IN_MS);
667             Log.d(TAG, "app2 receiver acked: " + message);
668             if (message != null) {
669                 return;
670             }
671             Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
672             SystemClock.sleep(SECOND_IN_MS);
673         }
674         fail("app2 receiver is not ready");
675     }
676 
677     /**
678      * Registers a {@link NotificationListenerService} implementation that will execute the
679      * notification actions right after the notification is sent.
680      */
registerNotificationListenerService()681     protected void registerNotificationListenerService() throws Exception {
682         final StringBuilder listeners = new StringBuilder(getNotificationListenerServices());
683         if (listeners.length() > 0) {
684             listeners.append(":");
685         }
686         listeners.append(MyNotificationListenerService.getId());
687         executeShellCommand("settings put secure enabled_notification_listeners " + listeners);
688         final String newListeners = getNotificationListenerServices();
689         assertEquals("Failed to set 'enabled_notification_listeners'",
690                 listeners.toString(), newListeners);
691     }
692 
getNotificationListenerServices()693     private String getNotificationListenerServices() throws Exception {
694         return executeShellCommand("settings get secure enabled_notification_listeners");
695     }
696 
setPendingIntentWhitelistDuration(int durationMs)697     protected void setPendingIntentWhitelistDuration(int durationMs) throws Exception {
698         final String command = String.format(
699                 "settings put global device_idle_constants %s=%d",
700                 "notification_whitelist_duration", durationMs);
701         executeSilentShellCommand(command);
702     }
703 
resetDeviceIdleSettings()704     protected void resetDeviceIdleSettings() throws Exception {
705         executeShellCommand("settings delete global device_idle_constants");
706     }
707 
startForegroundService()708     protected void startForegroundService() throws Exception {
709         executeShellCommand(
710                 "am startservice -f 1 com.android.cts.net.hostside.app2/.MyForegroundService");
711         assertForegroundServiceState();
712     }
713 
stopForegroundService()714     protected void stopForegroundService() throws Exception {
715         executeShellCommand(
716                 "am startservice -f 2 com.android.cts.net.hostside.app2/.MyForegroundService");
717         // NOTE: cannot assert state because it depends on whether activity was on top before.
718     }
719 
720     /**
721      * Launches an activity on app2 so its process is elevated to foreground status.
722      */
launchActivity()723     protected void launchActivity() throws Exception {
724         turnScreenOn();
725         executeShellCommand("am start com.android.cts.net.hostside.app2/.MyActivity");
726         assertForegroundState();
727     }
728 
729     /**
730      * Finishes an activity on app2 so its process is demoted fromforeground status.
731      */
finishActivity()732     protected void finishActivity() throws Exception {
733         executeShellCommand("am broadcast -a "
734                 + " com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY "
735                 + "--receiver-foreground --receiver-registered-only");
736     }
737 
sendNotification(int notificationId)738     protected void sendNotification(int notificationId) {
739         final Intent intent = new Intent(ACTION_SEND_NOTIFICATION);
740         intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId);
741         Log.d(TAG, "Sending broadcast: " + intent);
742         mContext.sendBroadcast(intent);
743     }
744 
toString(int status)745     private String toString(int status) {
746         switch (status) {
747             case RESTRICT_BACKGROUND_STATUS_DISABLED:
748                 return "DISABLED";
749             case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
750                 return "WHITELISTED";
751             case RESTRICT_BACKGROUND_STATUS_ENABLED:
752                 return "ENABLED";
753             default:
754                 return "UNKNOWN_STATUS_" + status;
755         }
756     }
757 
getProcessStateByUid(int uid)758     private ProcessState getProcessStateByUid(int uid) throws Exception {
759         return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid));
760     }
761 
762     private static class ProcessState {
763         private final String fullState;
764         final int state;
765 
ProcessState(String fullState)766         ProcessState(String fullState) {
767             this.fullState = fullState;
768             try {
769                 this.state = Integer.parseInt(fullState.split(" ")[0]);
770             } catch (Exception e) {
771                 throw new IllegalArgumentException("Could not parse " + fullState);
772             }
773         }
774 
775         @Override
toString()776         public String toString() {
777             return fullState;
778         }
779     }
780 
781     /**
782      * Helper class used to assert the result of a Shell command.
783      */
784     protected static interface ExpectResultChecker {
785         /**
786          * Checkes whether the result of the command matched the expectation.
787          */
isExpected(String result)788         boolean isExpected(String result);
789         /**
790          * Gets the expected result so it's displayed on log and failure messages.
791          */
getExpected()792         String getExpected();
793     }
794 }
795