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