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