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