• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.RESTRICT_BACKGROUND_STATUS_DISABLED;
20 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
21 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
22 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
23 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
24 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
25 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
26 import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE;
27 
28 import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
29 import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
30 
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertNotEquals;
33 import static org.junit.Assert.assertNotNull;
34 import static org.junit.Assert.assertTrue;
35 import static org.junit.Assert.fail;
36 
37 import android.app.ActivityManager;
38 import android.app.Instrumentation;
39 import android.app.UiAutomation;
40 import android.content.Context;
41 import android.content.pm.PackageManager;
42 import android.location.LocationManager;
43 import android.net.ConnectivityManager;
44 import android.net.ConnectivityManager.NetworkCallback;
45 import android.net.Network;
46 import android.net.NetworkCapabilities;
47 import android.net.NetworkPolicyManager;
48 import android.net.wifi.WifiConfiguration;
49 import android.net.wifi.WifiManager;
50 import android.net.wifi.WifiManager.ActionListener;
51 import android.os.PersistableBundle;
52 import android.os.Process;
53 import android.os.UserHandle;
54 import android.telephony.CarrierConfigManager;
55 import android.telephony.SubscriptionManager;
56 import android.telephony.data.ApnSetting;
57 import android.util.Log;
58 
59 import androidx.test.platform.app.InstrumentationRegistry;
60 import androidx.test.uiautomator.UiDevice;
61 
62 import com.android.compatibility.common.util.AppStandbyUtils;
63 import com.android.compatibility.common.util.BatteryUtils;
64 import com.android.compatibility.common.util.PollingCheck;
65 import com.android.compatibility.common.util.ShellIdentityUtils;
66 import com.android.compatibility.common.util.ThrowingRunnable;
67 
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.List;
71 import java.util.concurrent.BlockingQueue;
72 import java.util.concurrent.CountDownLatch;
73 import java.util.concurrent.LinkedBlockingQueue;
74 import java.util.concurrent.TimeUnit;
75 
76 public class NetworkPolicyTestUtils {
77 
78     // android.telephony.CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS
79     // TODO: Expose it as a @TestApi instead of copying the constant
80     private static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS =
81             "carrier_metered_apn_types_strings";
82 
83     private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 10_000;
84 
85     private static ConnectivityManager mCm;
86     private static WifiManager mWm;
87     private static CarrierConfigManager mCarrierConfigManager;
88     private static NetworkPolicyManager sNpm;
89 
90     private static Boolean mBatterySaverSupported;
91     private static Boolean mDataSaverSupported;
92     private static Boolean mDozeModeSupported;
93     private static Boolean mAppStandbySupported;
94 
NetworkPolicyTestUtils()95     private NetworkPolicyTestUtils() {}
96 
isBatterySaverSupported()97     public static boolean isBatterySaverSupported() {
98         if (mBatterySaverSupported == null) {
99             mBatterySaverSupported = BatteryUtils.isBatterySaverSupported();
100         }
101         return mBatterySaverSupported;
102     }
103 
isWear()104     private static boolean isWear() {
105         return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
106     }
107 
108     /**
109      * As per CDD requirements, if the device doesn't support data saver mode then
110      * ConnectivityManager.getRestrictBackgroundStatus() will always return
111      * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
112      * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
113      * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
114      */
isDataSaverSupported()115     public static boolean isDataSaverSupported() {
116         if (isWear()) {
117             return false;
118         }
119         if (mDataSaverSupported == null) {
120             assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
121             try {
122                 setRestrictBackgroundInternal(true);
123                 mDataSaverSupported = !isMyRestrictBackgroundStatus(
124                         RESTRICT_BACKGROUND_STATUS_DISABLED);
125             } finally {
126                 setRestrictBackgroundInternal(false);
127             }
128         }
129         return mDataSaverSupported;
130     }
131 
isDozeModeSupported()132     public static boolean isDozeModeSupported() {
133         if (mDozeModeSupported == null) {
134             final String result = executeShellCommand("cmd deviceidle enabled deep");
135             mDozeModeSupported = result.equals("1");
136         }
137         return mDozeModeSupported;
138     }
139 
isAppStandbySupported()140     public static boolean isAppStandbySupported() {
141         if (mAppStandbySupported == null) {
142             mAppStandbySupported = AppStandbyUtils.isAppStandbyEnabled();
143         }
144         return mAppStandbySupported;
145     }
146 
isLowRamDevice()147     public static boolean isLowRamDevice() {
148         final ActivityManager am = (ActivityManager) getContext().getSystemService(
149                 Context.ACTIVITY_SERVICE);
150         return am.isLowRamDevice();
151     }
152 
153     /** Forces JobScheduler to run the job if constraints are met. */
forceRunJob(String pkg, int jobId)154     public static void forceRunJob(String pkg, int jobId) {
155         executeShellCommand("cmd jobscheduler run -f -u " + UserHandle.myUserId()
156                 + " " + pkg + " " + jobId);
157     }
158 
isLocationEnabled()159     public static boolean isLocationEnabled() {
160         final LocationManager lm = (LocationManager) getContext().getSystemService(
161                 Context.LOCATION_SERVICE);
162         return lm.isLocationEnabled();
163     }
164 
setLocationEnabled(boolean enabled)165     public static void setLocationEnabled(boolean enabled) {
166         final LocationManager lm = (LocationManager) getContext().getSystemService(
167                 Context.LOCATION_SERVICE);
168         lm.setLocationEnabledForUser(enabled, Process.myUserHandle());
169         assertEquals("Couldn't change location enabled state", lm.isLocationEnabled(), enabled);
170         Log.d(TAG, "Changed location enabled state to " + enabled);
171     }
172 
isActiveNetworkMetered(boolean metered)173     public static boolean isActiveNetworkMetered(boolean metered) {
174         return getConnectivityManager().isActiveNetworkMetered() == metered;
175     }
176 
canChangeActiveNetworkMeteredness()177     public static boolean canChangeActiveNetworkMeteredness() {
178         final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
179         return networkCapabilities.hasTransport(TRANSPORT_WIFI)
180                 || networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
181     }
182 
183     /**
184      * Updates the meteredness of the active network. Right now we can only change meteredness
185      * of either Wifi or cellular network, so if the active network is not either of these, this
186      * will throw an exception.
187      *
188      * @return a {@link ThrowingRunnable} object that can used to reset the meteredness change
189      *         made by this method.
190      */
setupActiveNetworkMeteredness(boolean metered)191     public static ThrowingRunnable setupActiveNetworkMeteredness(boolean metered) throws Exception {
192         if (isActiveNetworkMetered(metered)) {
193             return null;
194         }
195         final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
196         if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) {
197             final String ssid = getWifiSsid();
198             setWifiMeteredStatus(ssid, metered);
199             return () -> setWifiMeteredStatus(ssid, !metered);
200         } else if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
201             final int subId = SubscriptionManager.getActiveDataSubscriptionId();
202             setCellularMeteredStatus(subId, metered);
203             return () -> setCellularMeteredStatus(subId, !metered);
204         } else {
205             // Right now, we don't have a way to change meteredness of networks other
206             // than Wi-Fi or Cellular, so just throw an exception.
207             throw new IllegalStateException("Can't change meteredness of current active network");
208         }
209     }
210 
getWifiSsid()211     private static String getWifiSsid() {
212         final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
213         try {
214             uiAutomation.adoptShellPermissionIdentity();
215             final String ssid = getWifiManager().getConnectionInfo().getSSID();
216             assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
217             return ssid;
218         } finally {
219             uiAutomation.dropShellPermissionIdentity();
220         }
221     }
222 
getActiveNetworkCapabilities()223     static NetworkCapabilities getActiveNetworkCapabilities() {
224         final Network activeNetwork = getConnectivityManager().getActiveNetwork();
225         assertNotNull("No active network available", activeNetwork);
226         return getConnectivityManager().getNetworkCapabilities(activeNetwork);
227     }
228 
setWifiMeteredStatus(String ssid, boolean metered)229     private static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
230         final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
231         try {
232             uiAutomation.adoptShellPermissionIdentity();
233             final WifiConfiguration currentConfig = getWifiConfiguration(ssid);
234             currentConfig.meteredOverride = metered
235                     ? METERED_OVERRIDE_METERED : METERED_OVERRIDE_NONE;
236             BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();
237             getWifiManager().save(currentConfig, createActionListener(
238                     blockingQueue, Integer.MAX_VALUE));
239             Integer resultCode = blockingQueue.poll(TIMEOUT_CHANGE_METEREDNESS_MS,
240                     TimeUnit.MILLISECONDS);
241             if (resultCode == null) {
242                 fail("Timed out waiting for meteredness to change; ssid=" + ssid
243                         + ", metered=" + metered);
244             } else if (resultCode != Integer.MAX_VALUE) {
245                 fail("Error overriding the meteredness; ssid=" + ssid
246                         + ", metered=" + metered + ", error=" + resultCode);
247             }
248             final boolean success = assertActiveNetworkMetered(metered, false /* throwOnFailure */);
249             if (!success) {
250                 Log.i(TAG, "Retry connecting to wifi; ssid=" + ssid);
251                 blockingQueue = new LinkedBlockingQueue<>();
252                 getWifiManager().connect(currentConfig, createActionListener(
253                         blockingQueue, Integer.MAX_VALUE));
254                 resultCode = blockingQueue.poll(TIMEOUT_CHANGE_METEREDNESS_MS,
255                         TimeUnit.MILLISECONDS);
256                 if (resultCode == null) {
257                     fail("Timed out waiting for wifi to connect; ssid=" + ssid);
258                 } else if (resultCode != Integer.MAX_VALUE) {
259                     fail("Error connecting to wifi; ssid=" + ssid
260                             + ", error=" + resultCode);
261                 }
262                 assertActiveNetworkMetered(metered, true /* throwOnFailure */);
263             }
264         } finally {
265             uiAutomation.dropShellPermissionIdentity();
266         }
267     }
268 
getWifiConfiguration(String ssid)269     private static WifiConfiguration getWifiConfiguration(String ssid) {
270         final List<String> ssids = new ArrayList<>();
271         for (WifiConfiguration config : getWifiManager().getConfiguredNetworks()) {
272             if (config.SSID.equals(ssid)) {
273                 return config;
274             }
275             ssids.add(config.SSID);
276         }
277         fail("Couldn't find the wifi config; ssid=" + ssid
278                 + ", all=" + Arrays.toString(ssids.toArray()));
279         return null;
280     }
281 
createActionListener(BlockingQueue<Integer> blockingQueue, int successCode)282     private static ActionListener createActionListener(BlockingQueue<Integer> blockingQueue,
283             int successCode) {
284         return new ActionListener() {
285             @Override
286             public void onSuccess() {
287                 blockingQueue.offer(successCode);
288             }
289 
290             @Override
291             public void onFailure(int reason) {
292                 blockingQueue.offer(reason);
293             }
294         };
295     }
296 
297     private static void setCellularMeteredStatus(int subId, boolean metered) throws Exception {
298         final PersistableBundle bundle = new PersistableBundle();
299         bundle.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
300                 new String[] {ApnSetting.TYPE_MMS_STRING});
301         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(getCarrierConfigManager(),
302                 (cm) -> cm.overrideConfig(subId, metered ? null : bundle));
303         assertActiveNetworkMetered(metered, true /* throwOnFailure */);
304     }
305 
306     private static boolean assertActiveNetworkMetered(boolean expectedMeteredStatus,
307             boolean throwOnFailure) throws Exception {
308         final CountDownLatch latch = new CountDownLatch(1);
309         final NetworkCallback networkCallback = new NetworkCallback() {
310             @Override
311             public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
312                 final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
313                 if (metered == expectedMeteredStatus) {
314                     latch.countDown();
315                 }
316             }
317         };
318         // Registering a callback here guarantees onCapabilitiesChanged is called immediately
319         // with the current setting. Therefore, if the setting has already been changed,
320         // this method will return right away, and if not it will wait for the setting to change.
321         getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
322         try {
323             if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
324                 final String errorMsg = "Timed out waiting for active network metered status "
325                         + "to change to " + expectedMeteredStatus + "; network = "
326                         + getConnectivityManager().getActiveNetwork();
327                 if (throwOnFailure) {
328                     fail(errorMsg);
329                 }
330                 Log.w(TAG, errorMsg);
331                 return false;
332             }
333             return true;
334         } finally {
335             getConnectivityManager().unregisterNetworkCallback(networkCallback);
336         }
337     }
338 
339     public static void setRestrictBackground(boolean enabled) {
340         if (!isDataSaverSupported()) {
341             return;
342         }
343         setRestrictBackgroundInternal(enabled);
344     }
345 
346     private static void setRestrictBackgroundInternal(boolean enabled) {
347         executeShellCommand("cmd netpolicy set restrict-background " + enabled);
348         final String output = executeShellCommand("cmd netpolicy get restrict-background");
349         final String expectedSuffix = enabled ? "enabled" : "disabled";
350         assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
351                 output.endsWith(expectedSuffix));
352     }
353 
354     public static boolean isMyRestrictBackgroundStatus(int expectedStatus) {
355         final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
356         if (expectedStatus != actualStatus) {
357             Log.d(TAG, "MyRestrictBackgroundStatus: "
358                     + "Expected: " + restrictBackgroundValueToString(expectedStatus)
359                     + "; Actual: " + restrictBackgroundValueToString(actualStatus));
360             return false;
361         }
362         return true;
363     }
364 
365     // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
366     private static String unquoteSSID(String ssid) {
367         // SSID is returned surrounded by quotes if it can be decoded as UTF-8.
368         // Otherwise it's guaranteed not to start with a quote.
369         if (ssid.charAt(0) == '"') {
370             return ssid.substring(1, ssid.length() - 1);
371         } else {
372             return ssid;
373         }
374     }
375 
376     public static String restrictBackgroundValueToString(int status) {
377         switch (status) {
378             case RESTRICT_BACKGROUND_STATUS_DISABLED:
379                 return "DISABLED";
380             case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
381                 return "WHITELISTED";
382             case RESTRICT_BACKGROUND_STATUS_ENABLED:
383                 return "ENABLED";
384             default:
385                 return "UNKNOWN_STATUS_" + status;
386         }
387     }
388 
389     public static void clearSnoozeTimestamps() {
390         executeShellCommand("dumpsys netpolicy --unsnooze");
391     }
392 
393     public static String executeShellCommand(String command) {
394         final String result = runShellCommandOrThrow(command).trim();
395         Log.d(TAG, "Output of '" + command + "': '" + result + "'");
396         return result;
397     }
398 
399     public static void assertMyRestrictBackgroundStatus(int expectedStatus) {
400         final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
401         assertEquals(restrictBackgroundValueToString(expectedStatus),
402                 restrictBackgroundValueToString(actualStatus));
403     }
404 
405     public static ConnectivityManager getConnectivityManager() {
406         if (mCm == null) {
407             mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
408         }
409         return mCm;
410     }
411 
412     public static WifiManager getWifiManager() {
413         if (mWm == null) {
414             mWm = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
415         }
416         return mWm;
417     }
418 
419     public static CarrierConfigManager getCarrierConfigManager() {
420         if (mCarrierConfigManager == null) {
421             mCarrierConfigManager = (CarrierConfigManager) getContext().getSystemService(
422                     Context.CARRIER_CONFIG_SERVICE);
423         }
424         return mCarrierConfigManager;
425     }
426 
427     public static NetworkPolicyManager getNetworkPolicyManager() {
428         if (sNpm == null) {
429             sNpm = getContext().getSystemService(NetworkPolicyManager.class);
430         }
431         return sNpm;
432     }
433 
434     public static Context getContext() {
435         return getInstrumentation().getContext();
436     }
437 
438     public static Instrumentation getInstrumentation() {
439         return InstrumentationRegistry.getInstrumentation();
440     }
441 
442     public static UiDevice getUiDevice() {
443         return UiDevice.getInstance(getInstrumentation());
444     }
445 
446     // When power saver mode or restrict background enabled or adding any white/black list into
447     // those modes, NetworkPolicy may need to take some time to update the rules of uids. So having
448     // this function and using PollingCheck to try to make sure the uid has updated and reduce the
449     // flaky rate.
450     public static void assertNetworkingBlockedStatusForUid(int uid, boolean metered,
451             boolean expectedResult) throws Exception {
452         PollingCheck.waitFor(() -> (expectedResult == isUidNetworkingBlocked(uid, metered)));
453     }
454 
455     public static void assertIsUidRestrictedOnMeteredNetworks(int uid, boolean expectedResult)
456             throws Exception {
457         PollingCheck.waitFor(() -> (expectedResult == isUidRestrictedOnMeteredNetworks(uid)));
458     }
459 
460     public static boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork) {
461         final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
462         try {
463             uiAutomation.adoptShellPermissionIdentity();
464             return getNetworkPolicyManager().isUidNetworkingBlocked(uid, meteredNetwork);
465         } finally {
466             uiAutomation.dropShellPermissionIdentity();
467         }
468     }
469 
470     public static boolean isUidRestrictedOnMeteredNetworks(int uid) {
471         final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
472         try {
473             uiAutomation.adoptShellPermissionIdentity();
474             return getNetworkPolicyManager().isUidRestrictedOnMeteredNetworks(uid);
475         } finally {
476             uiAutomation.dropShellPermissionIdentity();
477         }
478     }
479 }
480