1 /* 2 * Copyright (C) 2022 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 android.jobscheduler.cts; 18 19 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; 20 import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; 21 import static android.net.NetworkCapabilities.TRANSPORT_WIFI; 22 23 import static com.android.compatibility.common.util.TestUtils.waitUntil; 24 25 import static junit.framework.Assert.fail; 26 27 import static org.junit.Assert.assertEquals; 28 import static org.junit.Assert.assertFalse; 29 30 import android.Manifest; 31 import android.annotation.NonNull; 32 import android.app.Instrumentation; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.IntentFilter; 36 import android.content.pm.PackageManager; 37 import android.location.LocationManager; 38 import android.net.ConnectivityManager; 39 import android.net.Network; 40 import android.net.NetworkCapabilities; 41 import android.net.NetworkPolicyManager; 42 import android.net.NetworkRequest; 43 import android.net.wifi.WifiConfiguration; 44 import android.net.wifi.WifiManager; 45 import android.os.Handler; 46 import android.os.Looper; 47 import android.os.Message; 48 import android.provider.Settings; 49 import android.util.Log; 50 51 import com.android.compatibility.common.util.CallbackAsserter; 52 import com.android.compatibility.common.util.ShellIdentityUtils; 53 import com.android.compatibility.common.util.SystemUtil; 54 55 import junit.framework.AssertionFailedError; 56 57 import java.util.List; 58 import java.util.concurrent.CountDownLatch; 59 import java.util.concurrent.TimeUnit; 60 import java.util.concurrent.atomic.AtomicReference; 61 import java.util.regex.Matcher; 62 import java.util.regex.Pattern; 63 64 public class NetworkingHelper { 65 private static final String TAG = "JsNetworkingUtils"; 66 67 private static final String RESTRICT_BACKGROUND_GET_CMD = 68 "cmd netpolicy get restrict-background"; 69 private static final String RESTRICT_BACKGROUND_ON_CMD = 70 "cmd netpolicy set restrict-background true"; 71 private static final String RESTRICT_BACKGROUND_OFF_CMD = 72 "cmd netpolicy set restrict-background false"; 73 74 private final Context mContext; 75 private final Instrumentation mInstrumentation; 76 77 private final ConnectivityManager mConnectivityManager; 78 private final WifiManager mWifiManager; 79 80 /** Whether the device running these tests supports WiFi. */ 81 private final boolean mHasWifi; 82 /** Whether the device running these tests supports ethernet. */ 83 private final boolean mHasEthernet; 84 /** Whether the device running these tests supports telephony. */ 85 private final boolean mHasTelephony; 86 87 private final boolean mInitialAirplaneModeState; 88 private final boolean mInitialDataSaverState; 89 private final String mInitialLocationMode; 90 private final boolean mInitialWiFiState; 91 private String mInitialWiFiMeteredState; 92 private String mInitialWiFiSSID; 93 NetworkingHelper(@onNull Instrumentation instrumentation, @NonNull Context context)94 NetworkingHelper(@NonNull Instrumentation instrumentation, @NonNull Context context) 95 throws Exception { 96 mContext = context; 97 mInstrumentation = instrumentation; 98 99 mConnectivityManager = context.getSystemService(ConnectivityManager.class); 100 mWifiManager = context.getSystemService(WifiManager.class); 101 102 PackageManager packageManager = mContext.getPackageManager(); 103 mHasWifi = packageManager.hasSystemFeature(PackageManager.FEATURE_WIFI); 104 mHasEthernet = packageManager.hasSystemFeature(PackageManager.FEATURE_ETHERNET); 105 mHasTelephony = packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY); 106 107 mInitialAirplaneModeState = isAirplaneModeOn(); 108 mInitialDataSaverState = isDataSaverEnabled(); 109 mInitialLocationMode = Settings.Secure.getString( 110 mContext.getContentResolver(), Settings.Secure.LOCATION_MODE); 111 mInitialWiFiState = mHasWifi && isWifiEnabled(); 112 } 113 114 /** Ensures that the device has a wifi network saved. */ ensureSavedWifiNetwork()115 void ensureSavedWifiNetwork() throws Exception { 116 if (!mHasWifi) { 117 return; 118 } 119 final List<WifiConfiguration> savedNetworks = 120 ShellIdentityUtils.invokeMethodWithShellPermissions( 121 mWifiManager, WifiManager::getConfiguredNetworks); 122 assertFalse("Need at least one saved wifi network", savedNetworks.isEmpty()); 123 124 setWifiState(true); 125 if (mInitialWiFiSSID == null) { 126 mInitialWiFiSSID = getWifiSSID(); 127 mInitialWiFiMeteredState = getWifiMeteredStatus(mInitialWiFiSSID); 128 } 129 } 130 131 // Returns "true", "false", or "none". getWifiMeteredStatus(String ssid)132 private String getWifiMeteredStatus(String ssid) { 133 // Interestingly giving the SSID as an argument to list wifi-networks 134 // only works iff the network in question has the "false" policy. 135 // Also unfortunately runShellCommand does not pass the command to the interpreter 136 // so it's not possible to | grep the ssid. 137 final String command = "cmd netpolicy list wifi-networks"; 138 final String policyString = SystemUtil.runShellCommand(command); 139 140 final Matcher m = Pattern.compile(ssid + ";(true|false|none)", 141 Pattern.MULTILINE | Pattern.UNIX_LINES).matcher(policyString); 142 if (!m.find()) { 143 fail("Unexpected format from cmd netpolicy (when looking for " + ssid + "): " 144 + policyString); 145 } 146 return m.group(1); 147 } 148 149 @NonNull getWifiSSID()150 private String getWifiSSID() throws Exception { 151 // Location needs to be enabled to get the WiFi information. 152 setLocationMode(String.valueOf(Settings.Secure.LOCATION_MODE_ON)); 153 final AtomicReference<String> ssid = new AtomicReference<>(); 154 SystemUtil.runWithShellPermissionIdentity( 155 () -> ssid.set(mWifiManager.getConnectionInfo().getSSID()), 156 Manifest.permission.ACCESS_FINE_LOCATION); 157 return unquoteSSID(ssid.get()); 158 } 159 hasEthernetConnection()160 boolean hasEthernetConnection() { 161 if (!mHasEthernet) return false; 162 Network[] networks = mConnectivityManager.getAllNetworks(); 163 for (Network network : networks) { 164 NetworkCapabilities networkCapabilities = 165 mConnectivityManager.getNetworkCapabilities(network); 166 if (networkCapabilities != null 167 && networkCapabilities.hasTransport(TRANSPORT_ETHERNET)) { 168 return true; 169 } 170 } 171 return false; 172 } 173 hasWifiFeature()174 boolean hasWifiFeature() { 175 return mHasWifi; 176 } 177 isAirplaneModeOn()178 boolean isAirplaneModeOn() throws Exception { 179 final String output = SystemUtil.runShellCommand(mInstrumentation, 180 "cmd connectivity airplane-mode").trim(); 181 return "enabled".equals(output); 182 } 183 isDataSaverEnabled()184 boolean isDataSaverEnabled() throws Exception { 185 return SystemUtil 186 .runShellCommand(mInstrumentation, RESTRICT_BACKGROUND_GET_CMD) 187 .contains("enabled"); 188 } 189 isWiFiConnected()190 boolean isWiFiConnected() { 191 if (!mWifiManager.isWifiEnabled()) { 192 return false; 193 } 194 final Network network = mConnectivityManager.getActiveNetwork(); 195 if (network == null) { 196 return false; 197 } 198 final NetworkCapabilities networkCapabilities = 199 mConnectivityManager.getNetworkCapabilities(network); 200 return networkCapabilities != null 201 && networkCapabilities.hasTransport(TRANSPORT_WIFI) 202 && networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED); 203 } 204 isWifiEnabled()205 boolean isWifiEnabled() { 206 return mWifiManager.isWifiEnabled(); 207 } 208 209 /** 210 * Tries to set all network statuses to {@code enabled}. 211 * However, this does not support ethernet connections. 212 * Confirm that {@link #hasEthernetConnection()} returns false before relying on this. 213 */ setAllNetworksEnabled(boolean enabled)214 void setAllNetworksEnabled(boolean enabled) throws Exception { 215 if (mHasWifi) { 216 setWifiState(enabled); 217 } 218 setAirplaneMode(!enabled); 219 } 220 setAirplaneMode(boolean on)221 void setAirplaneMode(boolean on) throws Exception { 222 if (isAirplaneModeOn() == on) { 223 return; 224 } 225 final CallbackAsserter airplaneModeBroadcastAsserter = CallbackAsserter.forBroadcast( 226 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED)); 227 SystemUtil.runShellCommand(mInstrumentation, 228 "cmd connectivity airplane-mode " + (on ? "enable" : "disable")); 229 airplaneModeBroadcastAsserter.assertCalled("Didn't get airplane mode changed broadcast", 230 15 /* 15 seconds */); 231 if (!on && mHasWifi) { 232 // Try to trigger some network connection. 233 setWifiState(true); 234 } 235 waitUntil("Airplane mode didn't change to " + (on ? " on" : " off"), 60 /* seconds */, 236 () -> { 237 // Airplane mode only affects the cellular network. If the device doesn't 238 // support cellular, then we can only check that the airplane mode toggle is on. 239 if (!mHasTelephony) { 240 return on == isAirplaneModeOn(); 241 } 242 if (on) { 243 Network[] networks = mConnectivityManager.getAllNetworks(); 244 for (Network network : networks) { 245 NetworkCapabilities networkCapabilities = 246 mConnectivityManager.getNetworkCapabilities(network); 247 if (networkCapabilities != null && networkCapabilities 248 .hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { 249 return false; 250 } 251 } 252 return true; 253 } else { 254 return mConnectivityManager.getActiveNetwork() != null; 255 } 256 }); 257 // Wait some time for the network changes to propagate. Can't use 258 // waitUntil(isAirplaneModeOn() == on) because the response quickly gives the new 259 // airplane mode status even though the network changes haven't propagated all the way to 260 // JobScheduler. 261 Thread.sleep(5000); 262 } 263 264 /** 265 * Sets Data Saver to the desired on/off state. 266 */ setDataSaverEnabled(boolean enabled)267 void setDataSaverEnabled(boolean enabled) throws Exception { 268 SystemUtil.runShellCommand(mInstrumentation, 269 enabled ? RESTRICT_BACKGROUND_ON_CMD : RESTRICT_BACKGROUND_OFF_CMD); 270 final NetworkPolicyManager networkPolicyManager = 271 mContext.getSystemService(NetworkPolicyManager.class); 272 waitUntil("Data saver " + (enabled ? "not enabled" : "still enabled"), 273 () -> enabled == SystemUtil.runWithShellPermissionIdentity( 274 () -> networkPolicyManager.getRestrictBackground(), 275 Manifest.permission.MANAGE_NETWORK_POLICY)); 276 } 277 setLocationMode(String mode)278 private void setLocationMode(String mode) throws Exception { 279 Settings.Secure.putString(mContext.getContentResolver(), 280 Settings.Secure.LOCATION_MODE, mode); 281 final LocationManager locationManager = mContext.getSystemService(LocationManager.class); 282 final boolean wantEnabled = !String.valueOf(Settings.Secure.LOCATION_MODE_OFF).equals(mode); 283 waitUntil("Location " + (wantEnabled ? "not enabled" : "still enabled"), 284 () -> wantEnabled == locationManager.isLocationEnabled()); 285 } 286 setWifiMeteredState(boolean metered)287 void setWifiMeteredState(boolean metered) throws Exception { 288 if (metered) { 289 // Make sure unmetered cellular networks don't interfere. 290 setAirplaneMode(true); 291 setWifiState(true); 292 } 293 final String ssid = getWifiSSID(); 294 setWifiMeteredState(ssid, metered ? "true" : "false"); 295 } 296 297 // metered should be "true", "false" or "none" setWifiMeteredState(String ssid, String metered)298 private void setWifiMeteredState(String ssid, String metered) { 299 if (metered.equals(getWifiMeteredStatus(ssid))) { 300 return; 301 } 302 SystemUtil.runShellCommand("cmd netpolicy set metered-network " + ssid + " " + metered); 303 assertEquals(getWifiMeteredStatus(ssid), metered); 304 } 305 306 /** 307 * Set Wifi connection to specific state, and block until we've verified 308 * that we are in the state. 309 * Taken from {@link android.net.http.cts.ApacheHttpClientTest}. 310 */ setWifiState(final boolean enable)311 void setWifiState(final boolean enable) throws Exception { 312 if (enable != isWiFiConnected()) { 313 NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build(); 314 NetworkCapabilities nc = new NetworkCapabilities.Builder() 315 .addTransportType(TRANSPORT_WIFI) 316 .addCapability(NET_CAPABILITY_VALIDATED) 317 .build(); 318 NetworkTracker tracker = new NetworkTracker(nc, enable, mConnectivityManager); 319 mConnectivityManager.registerNetworkCallback(nr, tracker); 320 321 if (enable) { 322 SystemUtil.runShellCommand("svc wifi enable"); 323 waitUntil("Failed to enable Wifi", 30 /* seconds */, 324 this::isWifiEnabled); 325 //noinspection deprecation 326 SystemUtil.runWithShellPermissionIdentity(mWifiManager::reconnect, 327 android.Manifest.permission.NETWORK_SETTINGS); 328 } else { 329 SystemUtil.runShellCommand("svc wifi disable"); 330 } 331 332 tracker.waitForStateChange(); 333 334 assertEquals("Wifi must be " + (enable ? "connected to" : "disconnected from") 335 + " an access point for this test.", enable, isWiFiConnected()); 336 337 mConnectivityManager.unregisterNetworkCallback(tracker); 338 } 339 } 340 tearDown()341 void tearDown() throws Exception { 342 // Restore initial restrict background data usage policy 343 setDataSaverEnabled(mInitialDataSaverState); 344 345 // Ensure that we leave WiFi in its previous state. 346 if (mHasWifi) { 347 if (mInitialWiFiSSID != null) { 348 setWifiMeteredState(mInitialWiFiSSID, mInitialWiFiMeteredState); 349 } 350 if (mWifiManager.isWifiEnabled() != mInitialWiFiState) { 351 try { 352 setWifiState(mInitialWiFiState); 353 } catch (AssertionFailedError e) { 354 // Don't fail the test just because wifi state wasn't set in tearDown. 355 Log.e(TAG, "Failed to return wifi state to " + mInitialWiFiState, e); 356 } 357 } 358 } 359 360 // Restore initial airplane mode status. Do it after setting wifi in case wifi was 361 // originally metered. 362 if (isAirplaneModeOn() != mInitialAirplaneModeState) { 363 setAirplaneMode(mInitialAirplaneModeState); 364 } 365 366 setLocationMode(mInitialLocationMode); 367 } 368 unquoteSSID(String ssid)369 private String unquoteSSID(String ssid) { 370 // SSID is returned surrounded by quotes if it can be decoded as UTF-8. 371 // Otherwise it's guaranteed not to start with a quote. 372 if (ssid.charAt(0) == '"') { 373 return ssid.substring(1, ssid.length() - 1); 374 } else { 375 return ssid; 376 } 377 } 378 379 static class NetworkTracker extends ConnectivityManager.NetworkCallback { 380 private static final int MSG_CHECK_ACTIVE_NETWORK = 1; 381 private final ConnectivityManager mConnectivityManager; 382 383 private final CountDownLatch mReceiveLatch = new CountDownLatch(1); 384 385 private final NetworkCapabilities mExpectedCapabilities; 386 387 private final boolean mExpectedConnected; 388 389 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 390 @Override 391 public void handleMessage(Message msg) { 392 if (msg.what == MSG_CHECK_ACTIVE_NETWORK) { 393 checkActiveNetwork(); 394 } 395 } 396 }; 397 NetworkTracker(NetworkCapabilities expectedCapabilities, boolean expectedConnected, ConnectivityManager cm)398 NetworkTracker(NetworkCapabilities expectedCapabilities, boolean expectedConnected, 399 ConnectivityManager cm) { 400 mExpectedCapabilities = expectedCapabilities; 401 mExpectedConnected = expectedConnected; 402 mConnectivityManager = cm; 403 } 404 405 @Override onAvailable(Network network)406 public void onAvailable(Network network) { 407 // Available doesn't mean it's the active network. We need to check that separately. 408 checkActiveNetwork(); 409 } 410 411 @Override onLost(Network network)412 public void onLost(Network network) { 413 checkActiveNetwork(); 414 } 415 waitForStateChange()416 boolean waitForStateChange() throws InterruptedException { 417 checkActiveNetwork(); 418 return mReceiveLatch.await(60, TimeUnit.SECONDS); 419 } 420 checkActiveNetwork()421 private void checkActiveNetwork() { 422 mHandler.removeMessages(MSG_CHECK_ACTIVE_NETWORK); 423 if (mReceiveLatch.getCount() == 0) { 424 return; 425 } 426 427 Network activeNetwork = mConnectivityManager.getActiveNetwork(); 428 if (mExpectedConnected) { 429 if (activeNetwork != null && mExpectedCapabilities.satisfiedByNetworkCapabilities( 430 mConnectivityManager.getNetworkCapabilities(activeNetwork))) { 431 mReceiveLatch.countDown(); 432 } else { 433 mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000); 434 } 435 } else { 436 if (activeNetwork == null 437 || !mExpectedCapabilities.satisfiedByNetworkCapabilities( 438 mConnectivityManager.getNetworkCapabilities(activeNetwork))) { 439 mReceiveLatch.countDown(); 440 } else { 441 mHandler.sendEmptyMessageDelayed(MSG_CHECK_ACTIVE_NETWORK, 5000); 442 } 443 } 444 } 445 } 446 } 447