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