• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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