• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 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.net.cts;
18 
19 import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
20 import static android.content.pm.PackageManager.FEATURE_WIFI;
21 import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
22 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
23 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
24 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
25 import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver;
26 import static android.net.cts.util.CtsNetUtils.HTTP_PORT;
27 import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION;
28 import static android.net.cts.util.CtsNetUtils.TEST_HOST;
29 import static android.net.cts.util.CtsNetUtils.TestNetworkCallback;
30 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
31 import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
32 import static android.system.OsConstants.AF_INET;
33 import static android.system.OsConstants.AF_INET6;
34 import static android.system.OsConstants.AF_UNSPEC;
35 
36 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
37 
38 import android.annotation.NonNull;
39 import android.app.Instrumentation;
40 import android.app.PendingIntent;
41 import android.app.UiAutomation;
42 import android.content.ContentResolver;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.IntentFilter;
46 import android.content.pm.PackageManager;
47 import android.content.res.Resources;
48 import android.net.ConnectivityManager;
49 import android.net.ConnectivityManager.NetworkCallback;
50 import android.net.IpSecManager;
51 import android.net.IpSecManager.UdpEncapsulationSocket;
52 import android.net.LinkProperties;
53 import android.net.Network;
54 import android.net.NetworkCapabilities;
55 import android.net.NetworkConfig;
56 import android.net.NetworkInfo;
57 import android.net.NetworkInfo.DetailedState;
58 import android.net.NetworkInfo.State;
59 import android.net.NetworkRequest;
60 import android.net.SocketKeepalive;
61 import android.net.cts.util.CtsNetUtils;
62 import android.net.util.KeepaliveUtils;
63 import android.net.wifi.WifiManager;
64 import android.os.Build;
65 import android.os.Looper;
66 import android.os.MessageQueue;
67 import android.os.SystemClock;
68 import android.os.SystemProperties;
69 import android.os.VintfRuntimeInfo;
70 import android.platform.test.annotations.AppModeFull;
71 import android.provider.Settings;
72 import android.test.AndroidTestCase;
73 import android.text.TextUtils;
74 import android.util.Log;
75 import android.util.Pair;
76 
77 import androidx.test.InstrumentationRegistry;
78 
79 import libcore.io.Streams;
80 
81 import java.io.FileDescriptor;
82 import java.io.IOException;
83 import java.io.InputStream;
84 import java.io.InputStreamReader;
85 import java.io.OutputStream;
86 import java.net.HttpURLConnection;
87 import java.net.Inet4Address;
88 import java.net.Inet6Address;
89 import java.net.InetAddress;
90 import java.net.InetSocketAddress;
91 import java.net.Socket;
92 import java.net.URL;
93 import java.net.UnknownHostException;
94 import java.nio.charset.StandardCharsets;
95 import java.util.ArrayList;
96 import java.util.Collection;
97 import java.util.HashMap;
98 import java.util.concurrent.CountDownLatch;
99 import java.util.concurrent.Executor;
100 import java.util.concurrent.LinkedBlockingQueue;
101 import java.util.concurrent.TimeUnit;
102 import java.util.function.Supplier;
103 import java.util.regex.Matcher;
104 import java.util.regex.Pattern;
105 
106 public class ConnectivityManagerTest extends AndroidTestCase {
107 
108     private static final String TAG = ConnectivityManagerTest.class.getSimpleName();
109 
110     public static final int TYPE_MOBILE = ConnectivityManager.TYPE_MOBILE;
111     public static final int TYPE_WIFI = ConnectivityManager.TYPE_WIFI;
112 
113     private static final int HOST_ADDRESS = 0x7f000001;// represent ip 127.0.0.1
114     private static final int CONNECT_TIMEOUT_MS = 2000;
115     private static final int KEEPALIVE_CALLBACK_TIMEOUT_MS = 2000;
116     private static final int KEEPALIVE_SOCKET_TIMEOUT_MS = 5000;
117     private static final int INTERVAL_KEEPALIVE_RETRY_MS = 500;
118     private static final int MAX_KEEPALIVE_RETRY_COUNT = 3;
119     private static final int MIN_KEEPALIVE_INTERVAL = 10;
120     private static final int NETWORK_CHANGE_METEREDNESS_TIMEOUT = 5000;
121     private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
122     private static final long INTERVAL_MULTIPATH_PREF_CHECK_MS = 500;
123     // device could have only one interface: data, wifi.
124     private static final int MIN_NUM_NETWORK_TYPES = 1;
125 
126     // Minimum supported keepalive counts for wifi and cellular.
127     public static final int MIN_SUPPORTED_CELLULAR_KEEPALIVE_COUNT = 1;
128     public static final int MIN_SUPPORTED_WIFI_KEEPALIVE_COUNT = 3;
129 
130     private static final String NETWORK_METERED_MULTIPATH_PREFERENCE_RES_NAME =
131             "config_networkMeteredMultipathPreference";
132     private static final String KEEPALIVE_ALLOWED_UNPRIVILEGED_RES_NAME =
133             "config_allowedUnprivilegedKeepalivePerUid";
134     private static final String KEEPALIVE_RESERVED_PER_SLOT_RES_NAME =
135             "config_reservedPrivilegedKeepaliveSlots";
136 
137     private Context mContext;
138     private Instrumentation mInstrumentation;
139     private ConnectivityManager mCm;
140     private WifiManager mWifiManager;
141     private PackageManager mPackageManager;
142     private final HashMap<Integer, NetworkConfig> mNetworks =
143             new HashMap<Integer, NetworkConfig>();
144     boolean mWifiConnectAttempted;
145     private UiAutomation mUiAutomation;
146     private CtsNetUtils mCtsNetUtils;
147     private boolean mShellPermissionIdentityAdopted;
148 
149     @Override
setUp()150     protected void setUp() throws Exception {
151         super.setUp();
152         Looper.prepare();
153         mContext = getContext();
154         mInstrumentation = InstrumentationRegistry.getInstrumentation();
155         mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
156         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
157         mPackageManager = mContext.getPackageManager();
158         mCtsNetUtils = new CtsNetUtils(mContext);
159         mWifiConnectAttempted = false;
160 
161         // Get com.android.internal.R.array.networkAttributes
162         int resId = mContext.getResources().getIdentifier("networkAttributes", "array", "android");
163         String[] naStrings = mContext.getResources().getStringArray(resId);
164         //TODO: What is the "correct" way to determine if this is a wifi only device?
165         boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
166         for (String naString : naStrings) {
167             try {
168                 NetworkConfig n = new NetworkConfig(naString);
169                 if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
170                     continue;
171                 }
172                 mNetworks.put(n.type, n);
173             } catch (Exception e) {}
174         }
175         mUiAutomation = mInstrumentation.getUiAutomation();
176         mShellPermissionIdentityAdopted = false;
177     }
178 
179     @Override
tearDown()180     protected void tearDown() throws Exception {
181         // Return WiFi to its original disabled state after tests that explicitly connect.
182         if (mWifiConnectAttempted) {
183             mCtsNetUtils.disconnectFromWifi(null);
184         }
185         if (mCtsNetUtils.cellConnectAttempted()) {
186             mCtsNetUtils.disconnectFromCell();
187         }
188         dropShellPermissionIdentity();
189         super.tearDown();
190     }
191 
192     /**
193      * Make sure WiFi is connected to an access point if it is not already. If
194      * WiFi is enabled as a result of this function, it will be disabled
195      * automatically in tearDown().
196      */
ensureWifiConnected()197     private Network ensureWifiConnected() {
198         if (mWifiManager.isWifiEnabled()) {
199             return mCtsNetUtils.getWifiNetwork();
200         }
201         mWifiConnectAttempted = true;
202         return mCtsNetUtils.connectToWifi();
203     }
204 
testIsNetworkTypeValid()205     public void testIsNetworkTypeValid() {
206         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE));
207         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_WIFI));
208         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_MMS));
209         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_SUPL));
210         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_DUN));
211         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_HIPRI));
212         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_WIMAX));
213         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_BLUETOOTH));
214         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_DUMMY));
215         assertTrue(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.TYPE_ETHERNET));
216         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_FOTA));
217         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_IMS));
218         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_CBS));
219         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_WIFI_P2P));
220         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.TYPE_MOBILE_IA));
221         assertFalse(mCm.isNetworkTypeValid(-1));
222         assertTrue(mCm.isNetworkTypeValid(0));
223         assertTrue(mCm.isNetworkTypeValid(ConnectivityManager.MAX_NETWORK_TYPE));
224         assertFalse(ConnectivityManager.isNetworkTypeValid(ConnectivityManager.MAX_NETWORK_TYPE+1));
225 
226         NetworkInfo[] ni = mCm.getAllNetworkInfo();
227 
228         for (NetworkInfo n: ni) {
229             assertTrue(ConnectivityManager.isNetworkTypeValid(n.getType()));
230         }
231 
232     }
233 
testSetNetworkPreference()234     public void testSetNetworkPreference() {
235         // getNetworkPreference() and setNetworkPreference() are both deprecated so they do
236         // not preform any action.  Verify they are at least still callable.
237         mCm.setNetworkPreference(mCm.getNetworkPreference());
238     }
239 
testGetActiveNetworkInfo()240     public void testGetActiveNetworkInfo() {
241         NetworkInfo ni = mCm.getActiveNetworkInfo();
242 
243         assertNotNull("You must have an active network connection to complete CTS", ni);
244         assertTrue(ConnectivityManager.isNetworkTypeValid(ni.getType()));
245         assertTrue(ni.getState() == State.CONNECTED);
246     }
247 
testGetActiveNetwork()248     public void testGetActiveNetwork() {
249         Network network = mCm.getActiveNetwork();
250         assertNotNull("You must have an active network connection to complete CTS", network);
251 
252         NetworkInfo ni = mCm.getNetworkInfo(network);
253         assertNotNull("Network returned from getActiveNetwork was invalid", ni);
254 
255         // Similar to testGetActiveNetworkInfo above.
256         assertTrue(ConnectivityManager.isNetworkTypeValid(ni.getType()));
257         assertTrue(ni.getState() == State.CONNECTED);
258     }
259 
testGetNetworkInfo()260     public void testGetNetworkInfo() {
261         for (int type = -1; type <= ConnectivityManager.MAX_NETWORK_TYPE+1; type++) {
262             if (isSupported(type)) {
263                 NetworkInfo ni = mCm.getNetworkInfo(type);
264                 assertTrue("Info shouldn't be null for " + type, ni != null);
265                 State state = ni.getState();
266                 assertTrue("Bad state for " + type, State.UNKNOWN.ordinal() >= state.ordinal()
267                            && state.ordinal() >= State.CONNECTING.ordinal());
268                 DetailedState ds = ni.getDetailedState();
269                 assertTrue("Bad detailed state for " + type,
270                            DetailedState.FAILED.ordinal() >= ds.ordinal()
271                            && ds.ordinal() >= DetailedState.IDLE.ordinal());
272             } else {
273                 assertNull("Info should be null for " + type, mCm.getNetworkInfo(type));
274             }
275         }
276     }
277 
testGetAllNetworkInfo()278     public void testGetAllNetworkInfo() {
279         NetworkInfo[] ni = mCm.getAllNetworkInfo();
280         assertTrue(ni.length >= MIN_NUM_NETWORK_TYPES);
281         for (int type = 0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
282             int desiredFoundCount = (isSupported(type) ? 1 : 0);
283             int foundCount = 0;
284             for (NetworkInfo i : ni) {
285                 if (i.getType() == type) foundCount++;
286             }
287             if (foundCount != desiredFoundCount) {
288                 Log.e(TAG, "failure in testGetAllNetworkInfo.  Dump of returned NetworkInfos:");
289                 for (NetworkInfo networkInfo : ni) Log.e(TAG, "  " + networkInfo);
290             }
291             assertTrue("Unexpected foundCount of " + foundCount + " for type " + type,
292                     foundCount == desiredFoundCount);
293         }
294     }
295 
296     /**
297      * Tests that connections can be opened on WiFi and cellphone networks,
298      * and that they are made from different IP addresses.
299      */
300     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
testOpenConnection()301     public void testOpenConnection() throws Exception {
302         boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
303                 && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
304         if (!canRunTest) {
305             Log.i(TAG,"testOpenConnection cannot execute unless device supports both WiFi "
306                     + "and a cellular connection");
307             return;
308         }
309 
310         Network wifiNetwork = mCtsNetUtils.connectToWifi();
311         Network cellNetwork = mCtsNetUtils.connectToCell();
312         // This server returns the requestor's IP address as the response body.
313         URL url = new URL("http://google-ipv6test.appspot.com/ip.js?fmt=text");
314         String wifiAddressString = httpGet(wifiNetwork, url);
315         String cellAddressString = httpGet(cellNetwork, url);
316 
317         assertFalse(String.format("Same address '%s' on two different networks (%s, %s)",
318                 wifiAddressString, wifiNetwork, cellNetwork),
319                 wifiAddressString.equals(cellAddressString));
320 
321         // Sanity check that the IP addresses that the requests appeared to come from
322         // are actually on the respective networks.
323         assertOnNetwork(wifiAddressString, wifiNetwork);
324         assertOnNetwork(cellAddressString, cellNetwork);
325 
326         assertFalse("Unexpectedly equal: " + wifiNetwork, wifiNetwork.equals(cellNetwork));
327     }
328 
329     /**
330      * Performs a HTTP GET to the specified URL on the specified Network, and returns
331      * the response body decoded as UTF-8.
332      */
httpGet(Network network, URL httpUrl)333     private static String httpGet(Network network, URL httpUrl) throws IOException {
334         HttpURLConnection connection = (HttpURLConnection) network.openConnection(httpUrl);
335         try {
336             InputStream inputStream = connection.getInputStream();
337             return Streams.readFully(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
338         } finally {
339             connection.disconnect();
340         }
341     }
342 
assertOnNetwork(String adressString, Network network)343     private void assertOnNetwork(String adressString, Network network) throws UnknownHostException {
344         InetAddress address = InetAddress.getByName(adressString);
345         LinkProperties linkProperties = mCm.getLinkProperties(network);
346         // To make sure that the request went out on the right network, check that
347         // the IP address seen by the server is assigned to the expected network.
348         // We can only do this for IPv6 addresses, because in IPv4 we will likely
349         // have a private IPv4 address, and that won't match what the server sees.
350         if (address instanceof Inet6Address) {
351             assertContains(linkProperties.getAddresses(), address);
352         }
353     }
354 
assertContains(Collection<T> collection, T element)355     private static<T> void assertContains(Collection<T> collection, T element) {
356         assertTrue(element + " not found in " + collection, collection.contains(element));
357     }
358 
assertStartUsingNetworkFeatureUnsupported(int networkType, String feature)359     private void assertStartUsingNetworkFeatureUnsupported(int networkType, String feature) {
360         try {
361             mCm.startUsingNetworkFeature(networkType, feature);
362             fail("startUsingNetworkFeature is no longer supported in the current API version");
363         } catch (UnsupportedOperationException expected) {}
364     }
365 
assertStopUsingNetworkFeatureUnsupported(int networkType, String feature)366     private void assertStopUsingNetworkFeatureUnsupported(int networkType, String feature) {
367         try {
368             mCm.startUsingNetworkFeature(networkType, feature);
369             fail("stopUsingNetworkFeature is no longer supported in the current API version");
370         } catch (UnsupportedOperationException expected) {}
371     }
372 
assertRequestRouteToHostUnsupported(int networkType, int hostAddress)373     private void assertRequestRouteToHostUnsupported(int networkType, int hostAddress) {
374         try {
375             mCm.requestRouteToHost(networkType, hostAddress);
376             fail("requestRouteToHost is no longer supported in the current API version");
377         } catch (UnsupportedOperationException expected) {}
378     }
379 
testStartUsingNetworkFeature()380     public void testStartUsingNetworkFeature() {
381 
382         final String invalidateFeature = "invalidateFeature";
383         final String mmsFeature = "enableMMS";
384 
385         assertStartUsingNetworkFeatureUnsupported(TYPE_MOBILE, invalidateFeature);
386         assertStopUsingNetworkFeatureUnsupported(TYPE_MOBILE, invalidateFeature);
387         assertStartUsingNetworkFeatureUnsupported(TYPE_WIFI, mmsFeature);
388     }
389 
isSupported(int networkType)390     private boolean isSupported(int networkType) {
391         return mNetworks.containsKey(networkType) ||
392                (networkType == ConnectivityManager.TYPE_VPN) ||
393                (networkType == ConnectivityManager.TYPE_ETHERNET &&
394                        mContext.getSystemService(Context.ETHERNET_SERVICE) != null);
395     }
396 
testIsNetworkSupported()397     public void testIsNetworkSupported() {
398         for (int type = -1; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
399             boolean supported = mCm.isNetworkSupported(type);
400             if (isSupported(type)) {
401                 assertTrue(supported);
402             } else {
403                 assertFalse(supported);
404             }
405         }
406     }
407 
testRequestRouteToHost()408     public void testRequestRouteToHost() {
409         for (int type = -1 ; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) {
410             assertRequestRouteToHostUnsupported(type, HOST_ADDRESS);
411         }
412     }
413 
testTest()414     public void testTest() {
415         mCm.getBackgroundDataSetting();
416     }
417 
makeWifiNetworkRequest()418     private NetworkRequest makeWifiNetworkRequest() {
419         return new NetworkRequest.Builder()
420                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
421                 .build();
422     }
423 
424     /**
425      * Exercises both registerNetworkCallback and unregisterNetworkCallback. This checks to
426      * see if we get a callback for the TRANSPORT_WIFI transport type being available.
427      *
428      * <p>In order to test that a NetworkCallback occurs, we need some change in the network
429      * state (either a transport or capability is now available). The most straightforward is
430      * WiFi. We could add a version that uses the telephony data connection but it's not clear
431      * that it would increase test coverage by much (how many devices have 3G radio but not Wifi?).
432      */
433     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
testRegisterNetworkCallback()434     public void testRegisterNetworkCallback() {
435         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
436             Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
437             return;
438         }
439 
440         // We will register for a WIFI network being available or lost.
441         final TestNetworkCallback callback = new TestNetworkCallback();
442         mCm.registerNetworkCallback(makeWifiNetworkRequest(), callback);
443 
444         final TestNetworkCallback defaultTrackingCallback = new TestNetworkCallback();
445         mCm.registerDefaultNetworkCallback(defaultTrackingCallback);
446 
447         Network wifiNetwork = null;
448 
449         try {
450             ensureWifiConnected();
451 
452             // Now we should expect to get a network callback about availability of the wifi
453             // network even if it was already connected as a state-based action when the callback
454             // is registered.
455             wifiNetwork = callback.waitForAvailable();
456             assertNotNull("Did not receive NetworkCallback.onAvailable for TRANSPORT_WIFI",
457                     wifiNetwork);
458 
459             assertNotNull("Did not receive NetworkCallback.onAvailable for any default network",
460                     defaultTrackingCallback.waitForAvailable());
461         } catch (InterruptedException e) {
462             fail("Broadcast receiver or NetworkCallback wait was interrupted.");
463         } finally {
464             mCm.unregisterNetworkCallback(callback);
465             mCm.unregisterNetworkCallback(defaultTrackingCallback);
466         }
467     }
468 
469     /**
470      * Tests both registerNetworkCallback and unregisterNetworkCallback similarly to
471      * {@link #testRegisterNetworkCallback} except that a {@code PendingIntent} is used instead
472      * of a {@code NetworkCallback}.
473      */
474     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
testRegisterNetworkCallback_withPendingIntent()475     public void testRegisterNetworkCallback_withPendingIntent() {
476         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
477             Log.i(TAG, "testRegisterNetworkCallback cannot execute unless device supports WiFi");
478             return;
479         }
480 
481         // Create a ConnectivityActionReceiver that has an IntentFilter for our locally defined
482         // action, NETWORK_CALLBACK_ACTION.
483         IntentFilter filter = new IntentFilter();
484         filter.addAction(NETWORK_CALLBACK_ACTION);
485 
486         ConnectivityActionReceiver receiver = new ConnectivityActionReceiver(
487                 mCm, ConnectivityManager.TYPE_WIFI, NetworkInfo.State.CONNECTED);
488         mContext.registerReceiver(receiver, filter);
489 
490         // Create a broadcast PendingIntent for NETWORK_CALLBACK_ACTION.
491         Intent intent = new Intent(NETWORK_CALLBACK_ACTION);
492         PendingIntent pendingIntent = PendingIntent.getBroadcast(
493                 mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
494 
495         // We will register for a WIFI network being available or lost.
496         mCm.registerNetworkCallback(makeWifiNetworkRequest(), pendingIntent);
497 
498         try {
499             ensureWifiConnected();
500 
501             // Now we expect to get the Intent delivered notifying of the availability of the wifi
502             // network even if it was already connected as a state-based action when the callback
503             // is registered.
504             assertTrue("Did not receive expected Intent " + intent + " for TRANSPORT_WIFI",
505                     receiver.waitForState());
506         } catch (InterruptedException e) {
507             fail("Broadcast receiver or NetworkCallback wait was interrupted.");
508         } finally {
509             mCm.unregisterNetworkCallback(pendingIntent);
510             pendingIntent.cancel();
511             mContext.unregisterReceiver(receiver);
512         }
513     }
514 
515     /**
516      * Exercises the requestNetwork with NetworkCallback API. This checks to
517      * see if we get a callback for an INTERNET request.
518      */
519     @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
testRequestNetworkCallback()520     public void testRequestNetworkCallback() {
521         final TestNetworkCallback callback = new TestNetworkCallback();
522         mCm.requestNetwork(new NetworkRequest.Builder()
523                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
524                 .build(), callback);
525 
526         try {
527             // Wait to get callback for availability of internet
528             Network internetNetwork = callback.waitForAvailable();
529             assertNotNull("Did not receive NetworkCallback#onAvailable for INTERNET",
530                     internetNetwork);
531         } catch (InterruptedException e) {
532             fail("NetworkCallback wait was interrupted.");
533         } finally {
534             mCm.unregisterNetworkCallback(callback);
535         }
536     }
537 
538     /**
539      * Exercises the requestNetwork with NetworkCallback API with timeout - expected to
540      * fail. Use WIFI and switch Wi-Fi off.
541      */
542     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
testRequestNetworkCallback_onUnavailable()543     public void testRequestNetworkCallback_onUnavailable() {
544         final boolean previousWifiEnabledState = mWifiManager.isWifiEnabled();
545         if (previousWifiEnabledState) {
546             mCtsNetUtils.disconnectFromWifi(null);
547         }
548 
549         final TestNetworkCallback callback = new TestNetworkCallback();
550         mCm.requestNetwork(new NetworkRequest.Builder()
551                 .addTransportType(TRANSPORT_WIFI)
552                 .build(), callback, 100);
553 
554         try {
555             // Wait to get callback for unavailability of requested network
556             assertTrue("Did not receive NetworkCallback#onUnavailable",
557                     callback.waitForUnavailable());
558         } catch (InterruptedException e) {
559             fail("NetworkCallback wait was interrupted.");
560         } finally {
561             mCm.unregisterNetworkCallback(callback);
562             if (previousWifiEnabledState) {
563                 mCtsNetUtils.connectToWifi();
564             }
565         }
566     }
567 
getFirstV4Address(Network network)568     private InetAddress getFirstV4Address(Network network) {
569         LinkProperties linkProperties = mCm.getLinkProperties(network);
570         for (InetAddress address : linkProperties.getAddresses()) {
571             if (address instanceof Inet4Address) {
572                 return address;
573             }
574         }
575         return null;
576     }
577 
578     /** Verify restricted networks cannot be requested. */
579     @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
testRestrictedNetworks()580     public void testRestrictedNetworks() {
581         // Verify we can request unrestricted networks:
582         NetworkRequest request = new NetworkRequest.Builder()
583                 .addCapability(NET_CAPABILITY_INTERNET).build();
584         NetworkCallback callback = new NetworkCallback();
585         mCm.requestNetwork(request, callback);
586         mCm.unregisterNetworkCallback(callback);
587         // Verify we cannot request restricted networks:
588         request = new NetworkRequest.Builder().addCapability(NET_CAPABILITY_IMS).build();
589         callback = new NetworkCallback();
590         try {
591             mCm.requestNetwork(request, callback);
592             fail("No exception thrown when restricted network requested.");
593         } catch (SecurityException expected) {}
594     }
595 
596     // Returns "true", "false" or "none"
getWifiMeteredStatus(String ssid)597     private String getWifiMeteredStatus(String ssid) throws Exception {
598         // Interestingly giving the SSID as an argument to list wifi-networks
599         // only works iff the network in question has the "false" policy.
600         // Also unfortunately runShellCommand does not pass the command to the interpreter
601         // so it's not possible to | grep the ssid.
602         final String command = "cmd netpolicy list wifi-networks";
603         final String policyString = runShellCommand(mInstrumentation, command);
604 
605         final Matcher m = Pattern.compile("^" + ssid + ";(true|false|none)$",
606                 Pattern.MULTILINE | Pattern.UNIX_LINES).matcher(policyString);
607         if (!m.find()) {
608             fail("Unexpected format from cmd netpolicy");
609         }
610         return m.group(1);
611     }
612 
613     // metered should be "true", "false" or "none"
setWifiMeteredStatus(String ssid, String metered)614     private void setWifiMeteredStatus(String ssid, String metered) throws Exception {
615         final String setCommand = "cmd netpolicy set metered-network " + ssid + " " + metered;
616         runShellCommand(mInstrumentation, setCommand);
617         assertEquals(getWifiMeteredStatus(ssid), metered);
618     }
619 
unquoteSSID(String ssid)620     private String unquoteSSID(String ssid) {
621         // SSID is returned surrounded by quotes if it can be decoded as UTF-8.
622         // Otherwise it's guaranteed not to start with a quote.
623         if (ssid.charAt(0) == '"') {
624             return ssid.substring(1, ssid.length() - 1);
625         } else {
626             return ssid;
627         }
628     }
629 
waitForActiveNetworkMetered(boolean requestedMeteredness)630     private void waitForActiveNetworkMetered(boolean requestedMeteredness) throws Exception {
631         final CountDownLatch latch = new CountDownLatch(1);
632         final NetworkCallback networkCallback = new NetworkCallback() {
633             @Override
634             public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
635                 final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
636                 if (metered == requestedMeteredness) {
637                     latch.countDown();
638                 }
639             }
640         };
641         // Registering a callback here guarantees onCapabilitiesChanged is called immediately
642         // with the current setting. Therefore, if the setting has already been changed,
643         // this method will return right away, and if not it will wait for the setting to change.
644         mCm.registerDefaultNetworkCallback(networkCallback);
645         if (!latch.await(NETWORK_CHANGE_METEREDNESS_TIMEOUT, TimeUnit.MILLISECONDS)) {
646             fail("Timed out waiting for active network metered status to change to "
647                  + requestedMeteredness + " ; network = " + mCm.getActiveNetwork());
648         }
649         mCm.unregisterNetworkCallback(networkCallback);
650     }
651 
assertMultipathPreferenceIsEventually(Network network, int oldValue, int expectedValue)652     private void assertMultipathPreferenceIsEventually(Network network, int oldValue,
653             int expectedValue) {
654         // Sanity check : if oldValue == expectedValue, there is no way to guarantee the test
655         // is not flaky.
656         assertNotSame(oldValue, expectedValue);
657 
658         for (int i = 0; i < NUM_TRIES_MULTIPATH_PREF_CHECK; ++i) {
659             final int actualValue = mCm.getMultipathPreference(network);
660             if (actualValue == expectedValue) {
661                 return;
662             }
663             if (actualValue != oldValue) {
664                 fail("Multipath preference is neither previous (" + oldValue
665                         + ") nor expected (" + expectedValue + ")");
666             }
667             SystemClock.sleep(INTERVAL_MULTIPATH_PREF_CHECK_MS);
668         }
669         fail("Timed out waiting for multipath preference to change. expected = "
670                 + expectedValue + " ; actual = " + mCm.getMultipathPreference(network));
671     }
672 
getCurrentMeteredMultipathPreference(ContentResolver resolver)673     private int getCurrentMeteredMultipathPreference(ContentResolver resolver) {
674         final String rawMeteredPref = Settings.Global.getString(resolver,
675                 NETWORK_METERED_MULTIPATH_PREFERENCE);
676         return TextUtils.isEmpty(rawMeteredPref)
677             ? getIntResourceForName(NETWORK_METERED_MULTIPATH_PREFERENCE_RES_NAME)
678             : Integer.parseInt(rawMeteredPref);
679     }
680 
findNextPrefValue(ContentResolver resolver)681     private int findNextPrefValue(ContentResolver resolver) {
682         // A bit of a nuclear hammer, but race conditions in CTS are bad. To be able to
683         // detect a correct setting value without race conditions, the next pref must
684         // be a valid value (range 0..3) that is different from the old setting of the
685         // metered preference and from the unmetered preference.
686         final int meteredPref = getCurrentMeteredMultipathPreference(resolver);
687         final int unmeteredPref = ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED;
688         if (0 != meteredPref && 0 != unmeteredPref) return 0;
689         if (1 != meteredPref && 1 != unmeteredPref) return 1;
690         return 2;
691     }
692 
693     /**
694      * Verify that getMultipathPreference does return appropriate values
695      * for metered and unmetered networks.
696      */
697     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
testGetMultipathPreference()698     public void testGetMultipathPreference() throws Exception {
699         final ContentResolver resolver = mContext.getContentResolver();
700         final Network network = ensureWifiConnected();
701         final String ssid = unquoteSSID(mWifiManager.getConnectionInfo().getSSID());
702         final String oldMeteredSetting = getWifiMeteredStatus(ssid);
703         final String oldMeteredMultipathPreference = Settings.Global.getString(
704                 resolver, NETWORK_METERED_MULTIPATH_PREFERENCE);
705         try {
706             final int initialMeteredPreference = getCurrentMeteredMultipathPreference(resolver);
707             int newMeteredPreference = findNextPrefValue(resolver);
708             Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
709                     Integer.toString(newMeteredPreference));
710             setWifiMeteredStatus(ssid, "true");
711             waitForActiveNetworkMetered(true);
712             assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
713                     NET_CAPABILITY_NOT_METERED), false);
714             assertMultipathPreferenceIsEventually(network, initialMeteredPreference,
715                     newMeteredPreference);
716 
717             final int oldMeteredPreference = newMeteredPreference;
718             newMeteredPreference = findNextPrefValue(resolver);
719             Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
720                     Integer.toString(newMeteredPreference));
721             assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
722                     NET_CAPABILITY_NOT_METERED), false);
723             assertMultipathPreferenceIsEventually(network,
724                     oldMeteredPreference, newMeteredPreference);
725 
726             setWifiMeteredStatus(ssid, "false");
727             waitForActiveNetworkMetered(false);
728             assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
729                     NET_CAPABILITY_NOT_METERED), true);
730             assertMultipathPreferenceIsEventually(network, newMeteredPreference,
731                     ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED);
732         } finally {
733             Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
734                     oldMeteredMultipathPreference);
735             setWifiMeteredStatus(ssid, oldMeteredSetting);
736         }
737     }
738 
739     // TODO: move the following socket keep alive test to dedicated test class.
740     /**
741      * Callback used in tcp keepalive offload that allows caller to wait callback fires.
742      */
743     private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
744         public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
745 
746         public static class CallbackValue {
747             public final CallbackType callbackType;
748             public final int error;
749 
CallbackValue(final CallbackType type, final int error)750             private CallbackValue(final CallbackType type, final int error) {
751                 this.callbackType = type;
752                 this.error = error;
753             }
754 
755             public static class OnStartedCallback extends CallbackValue {
OnStartedCallback()756                 OnStartedCallback() { super(CallbackType.ON_STARTED, 0); }
757             }
758 
759             public static class OnStoppedCallback extends CallbackValue {
OnStoppedCallback()760                 OnStoppedCallback() { super(CallbackType.ON_STOPPED, 0); }
761             }
762 
763             public static class OnErrorCallback extends CallbackValue {
OnErrorCallback(final int error)764                 OnErrorCallback(final int error) { super(CallbackType.ON_ERROR, error); }
765             }
766 
767             @Override
equals(Object o)768             public boolean equals(Object o) {
769                 return o.getClass() == this.getClass()
770                         && this.callbackType == ((CallbackValue) o).callbackType
771                         && this.error == ((CallbackValue) o).error;
772             }
773 
774             @Override
toString()775             public String toString() {
776                 return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error);
777             }
778         }
779 
780         private final LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
781 
782         @Override
onStarted()783         public void onStarted() {
784             mCallbacks.add(new CallbackValue.OnStartedCallback());
785         }
786 
787         @Override
onStopped()788         public void onStopped() {
789             mCallbacks.add(new CallbackValue.OnStoppedCallback());
790         }
791 
792         @Override
onError(final int error)793         public void onError(final int error) {
794             mCallbacks.add(new CallbackValue.OnErrorCallback(error));
795         }
796 
pollCallback()797         public CallbackValue pollCallback() {
798             try {
799                 return mCallbacks.poll(KEEPALIVE_CALLBACK_TIMEOUT_MS,
800                         TimeUnit.MILLISECONDS);
801             } catch (InterruptedException e) {
802                 fail("Callback not seen after " + KEEPALIVE_CALLBACK_TIMEOUT_MS + " ms");
803             }
804             return null;
805         }
expectCallback(CallbackValue expectedCallback)806         private void expectCallback(CallbackValue expectedCallback) {
807             final CallbackValue actualCallback = pollCallback();
808             assertEquals(expectedCallback, actualCallback);
809         }
810 
expectStarted()811         public void expectStarted() {
812             expectCallback(new CallbackValue.OnStartedCallback());
813         }
814 
expectStopped()815         public void expectStopped() {
816             expectCallback(new CallbackValue.OnStoppedCallback());
817         }
818 
expectError(int error)819         public void expectError(int error) {
820             expectCallback(new CallbackValue.OnErrorCallback(error));
821         }
822     }
823 
getAddrByName(final String hostname, final int family)824     private InetAddress getAddrByName(final String hostname, final int family) throws Exception {
825         final InetAddress[] allAddrs = InetAddress.getAllByName(hostname);
826         for (InetAddress addr : allAddrs) {
827             if (family == AF_INET && addr instanceof Inet4Address) return addr;
828 
829             if (family == AF_INET6 && addr instanceof Inet6Address) return addr;
830 
831             if (family == AF_UNSPEC) return addr;
832         }
833         return null;
834     }
835 
getConnectedSocket(final Network network, final String host, final int port, final int socketTimeOut, final int family)836     private Socket getConnectedSocket(final Network network, final String host, final int port,
837             final int socketTimeOut, final int family) throws Exception {
838         final Socket s = network.getSocketFactory().createSocket();
839         try {
840             final InetAddress addr = getAddrByName(host, family);
841             if (addr == null) fail("Fail to get destination address for " + family);
842 
843             final InetSocketAddress sockAddr = new InetSocketAddress(addr, port);
844             s.setSoTimeout(socketTimeOut);
845             s.connect(sockAddr, CONNECT_TIMEOUT_MS);
846         } catch (Exception e) {
847             s.close();
848             throw e;
849         }
850         return s;
851     }
852 
getSupportedKeepalivesForNet(@onNull Network network)853     private int getSupportedKeepalivesForNet(@NonNull Network network) throws Exception {
854         final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
855 
856         // Get number of supported concurrent keepalives for testing network.
857         final int[] keepalivesPerTransport = KeepaliveUtils.getSupportedKeepalives(mContext);
858         return KeepaliveUtils.getSupportedKeepalivesForNetworkCapabilities(
859                 keepalivesPerTransport, nc);
860     }
861 
adoptShellPermissionIdentity()862     private void adoptShellPermissionIdentity() {
863         mUiAutomation.adoptShellPermissionIdentity();
864         mShellPermissionIdentityAdopted = true;
865     }
866 
dropShellPermissionIdentity()867     private void dropShellPermissionIdentity() {
868         if (mShellPermissionIdentityAdopted) {
869             mUiAutomation.dropShellPermissionIdentity();
870             mShellPermissionIdentityAdopted = false;
871         }
872     }
873 
isTcpKeepaliveSupportedByKernel()874     private static boolean isTcpKeepaliveSupportedByKernel() {
875         final String kVersionString = VintfRuntimeInfo.getKernelVersion();
876         return compareMajorMinorVersion(kVersionString, "4.8") >= 0;
877     }
878 
getVersionFromString(String version)879     private static Pair<Integer, Integer> getVersionFromString(String version) {
880         // Only gets major and minor number of the version string.
881         final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
882         final Matcher m = versionPattern.matcher(version);
883         if (m.matches()) {
884             final int major = Integer.parseInt(m.group(1));
885             final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
886             return new Pair<>(major, minor);
887         } else {
888             return new Pair<>(0, 0);
889         }
890     }
891 
892     // TODO: Move to util class.
compareMajorMinorVersion(final String s1, final String s2)893     private static int compareMajorMinorVersion(final String s1, final String s2) {
894         final Pair<Integer, Integer> v1 = getVersionFromString(s1);
895         final Pair<Integer, Integer> v2 = getVersionFromString(s2);
896 
897         if (v1.first == v2.first) {
898             return Integer.compare(v1.second, v2.second);
899         } else {
900             return Integer.compare(v1.first, v2.first);
901         }
902     }
903 
904     /**
905      * Verifies that version string compare logic returns expected result for various cases.
906      * Note that only major and minor number are compared.
907      */
testMajorMinorVersionCompare()908     public void testMajorMinorVersionCompare() {
909         assertEquals(0, compareMajorMinorVersion("4.8.1", "4.8"));
910         assertEquals(1, compareMajorMinorVersion("4.9", "4.8.1"));
911         assertEquals(1, compareMajorMinorVersion("5.0", "4.8"));
912         assertEquals(1, compareMajorMinorVersion("5", "4.8"));
913         assertEquals(0, compareMajorMinorVersion("5", "5.0"));
914         assertEquals(1, compareMajorMinorVersion("5-beta1", "4.8"));
915         assertEquals(0, compareMajorMinorVersion("4.8.0.0", "4.8"));
916         assertEquals(0, compareMajorMinorVersion("4.8-RC1", "4.8"));
917         assertEquals(0, compareMajorMinorVersion("4.8", "4.8"));
918         assertEquals(-1, compareMajorMinorVersion("3.10", "4.8.0"));
919         assertEquals(-1, compareMajorMinorVersion("4.7.10.10", "4.8"));
920     }
921 
922     /**
923      * Verifies that the keepalive API cannot create any keepalive when the maximum number of
924      * keepalives is set to 0.
925      */
926     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
testKeepaliveWifiUnsupported()927     public void testKeepaliveWifiUnsupported() throws Exception {
928         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
929             Log.i(TAG, "testKeepaliveUnsupported cannot execute unless device"
930                     + " supports WiFi");
931             return;
932         }
933 
934         final Network network = ensureWifiConnected();
935         if (getSupportedKeepalivesForNet(network) != 0) return;
936 
937         adoptShellPermissionIdentity();
938 
939         assertEquals(0, createConcurrentSocketKeepalives(network, 1, 0));
940         assertEquals(0, createConcurrentSocketKeepalives(network, 0, 1));
941 
942         dropShellPermissionIdentity();
943     }
944 
945     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
testCreateTcpKeepalive()946     public void testCreateTcpKeepalive() throws Exception {
947         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
948             Log.i(TAG, "testCreateTcpKeepalive cannot execute unless device supports WiFi");
949             return;
950         }
951 
952         adoptShellPermissionIdentity();
953 
954         final Network network = ensureWifiConnected();
955         if (getSupportedKeepalivesForNet(network) == 0) return;
956         // If kernel < 4.8 then it doesn't support TCP keepalive, but it might still support
957         // NAT-T keepalive. If keepalive limits from resource overlay is not zero, TCP keepalive
958         // needs to be supported except if the kernel doesn't support it.
959         if (!isTcpKeepaliveSupportedByKernel()) {
960             // Sanity check to ensure the callback result is expected.
961             assertEquals(0, createConcurrentSocketKeepalives(network, 0, 1));
962             Log.i(TAG, "testCreateTcpKeepalive is skipped for kernel "
963                     + VintfRuntimeInfo.getKernelVersion());
964             return;
965         }
966 
967         final byte[] requestBytes = CtsNetUtils.HTTP_REQUEST.getBytes("UTF-8");
968         // So far only ipv4 tcp keepalive offload is supported.
969         // TODO: add test case for ipv6 tcp keepalive offload when it is supported.
970         try (Socket s = getConnectedSocket(network, TEST_HOST, HTTP_PORT,
971                 KEEPALIVE_SOCKET_TIMEOUT_MS, AF_INET)) {
972 
973             // Should able to start keep alive offload when socket is idle.
974             final Executor executor = mContext.getMainExecutor();
975             final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
976             try (SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
977                 sk.start(MIN_KEEPALIVE_INTERVAL);
978                 callback.expectStarted();
979 
980                 // App should not able to write during keepalive offload.
981                 final OutputStream out = s.getOutputStream();
982                 try {
983                     out.write(requestBytes);
984                     fail("Should not able to write");
985                 } catch (IOException e) { }
986                 // App should not able to read during keepalive offload.
987                 final InputStream in = s.getInputStream();
988                 byte[] responseBytes = new byte[4096];
989                 try {
990                     in.read(responseBytes);
991                     fail("Should not able to read");
992                 } catch (IOException e) { }
993 
994                 // Stop.
995                 sk.stop();
996                 callback.expectStopped();
997             }
998 
999             // Ensure socket is still connected.
1000             assertTrue(s.isConnected());
1001             assertFalse(s.isClosed());
1002 
1003             // Let socket be not idle.
1004             try {
1005                 final OutputStream out = s.getOutputStream();
1006                 out.write(requestBytes);
1007             } catch (IOException e) {
1008                 fail("Failed to write data " + e);
1009             }
1010             // Make sure response data arrives.
1011             final MessageQueue fdHandlerQueue = Looper.getMainLooper().getQueue();
1012             final FileDescriptor fd = s.getFileDescriptor$();
1013             final CountDownLatch mOnReceiveLatch = new CountDownLatch(1);
1014             fdHandlerQueue.addOnFileDescriptorEventListener(fd, EVENT_INPUT, (readyFd, events) -> {
1015                 mOnReceiveLatch.countDown();
1016                 return 0; // Unregister listener.
1017             });
1018             if (!mOnReceiveLatch.await(2, TimeUnit.SECONDS)) {
1019                 fdHandlerQueue.removeOnFileDescriptorEventListener(fd);
1020                 fail("Timeout: no response data");
1021             }
1022 
1023             // Should get ERROR_SOCKET_NOT_IDLE because there is still data in the receive queue
1024             // that has not been read.
1025             try (SocketKeepalive sk = mCm.createSocketKeepalive(network, s, executor, callback)) {
1026                 sk.start(MIN_KEEPALIVE_INTERVAL);
1027                 callback.expectError(SocketKeepalive.ERROR_SOCKET_NOT_IDLE);
1028             }
1029         }
1030     }
1031 
createConcurrentKeepalivesOfType( int requestCount, @NonNull TestSocketKeepaliveCallback callback, Supplier<SocketKeepalive> kaFactory)1032     private ArrayList<SocketKeepalive> createConcurrentKeepalivesOfType(
1033             int requestCount, @NonNull TestSocketKeepaliveCallback callback,
1034             Supplier<SocketKeepalive> kaFactory) {
1035         final ArrayList<SocketKeepalive> kalist = new ArrayList<>();
1036 
1037         int remainingRetries = MAX_KEEPALIVE_RETRY_COUNT;
1038 
1039         // Test concurrent keepalives with the given supplier.
1040         while (kalist.size() < requestCount) {
1041             final SocketKeepalive ka = kaFactory.get();
1042             ka.start(MIN_KEEPALIVE_INTERVAL);
1043             TestSocketKeepaliveCallback.CallbackValue cv = callback.pollCallback();
1044             assertNotNull(cv);
1045             if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR) {
1046                 if (kalist.size() == 0 && cv.error == SocketKeepalive.ERROR_UNSUPPORTED) {
1047                     // Unsupported.
1048                     break;
1049                 } else if (cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
1050                     // Limit reached or temporary unavailable due to stopped slot is not yet
1051                     // released.
1052                     if (remainingRetries > 0) {
1053                         SystemClock.sleep(INTERVAL_KEEPALIVE_RETRY_MS);
1054                         remainingRetries--;
1055                         continue;
1056                     }
1057                     break;
1058                 }
1059             }
1060             if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_STARTED) {
1061                 kalist.add(ka);
1062             } else {
1063                 fail("Unexpected error when creating " + (kalist.size() + 1) + " "
1064                         + ka.getClass().getSimpleName() + ": " + cv);
1065             }
1066         }
1067 
1068         return kalist;
1069     }
1070 
createConcurrentNattSocketKeepalives( @onNull Network network, int requestCount, @NonNull TestSocketKeepaliveCallback callback)1071     private @NonNull ArrayList<SocketKeepalive> createConcurrentNattSocketKeepalives(
1072             @NonNull Network network, int requestCount,
1073             @NonNull TestSocketKeepaliveCallback callback)  throws Exception {
1074 
1075         final Executor executor = mContext.getMainExecutor();
1076 
1077         // Initialize a real NaT-T socket.
1078         final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
1079         final UdpEncapsulationSocket nattSocket = mIpSec.openUdpEncapsulationSocket();
1080         final InetAddress srcAddr = getFirstV4Address(network);
1081         final InetAddress dstAddr = getAddrByName(TEST_HOST, AF_INET);
1082         assertNotNull(srcAddr);
1083         assertNotNull(dstAddr);
1084 
1085         // Test concurrent Nat-T keepalives.
1086         final ArrayList<SocketKeepalive> result = createConcurrentKeepalivesOfType(requestCount,
1087                 callback, () -> mCm.createSocketKeepalive(network, nattSocket,
1088                         srcAddr, dstAddr, executor, callback));
1089 
1090         nattSocket.close();
1091         return result;
1092     }
1093 
createConcurrentTcpSocketKeepalives( @onNull Network network, int requestCount, @NonNull TestSocketKeepaliveCallback callback)1094     private @NonNull ArrayList<SocketKeepalive> createConcurrentTcpSocketKeepalives(
1095             @NonNull Network network, int requestCount,
1096             @NonNull TestSocketKeepaliveCallback callback) {
1097         final Executor executor = mContext.getMainExecutor();
1098 
1099         // Create concurrent TCP keepalives.
1100         return createConcurrentKeepalivesOfType(requestCount, callback, () -> {
1101             // Assert that TCP connections can be established. The file descriptor of tcp
1102             // sockets will be duplicated and kept valid in service side if the keepalives are
1103             // successfully started.
1104             try (Socket tcpSocket = getConnectedSocket(network, TEST_HOST, HTTP_PORT,
1105                     0 /* Unused */, AF_INET)) {
1106                 return mCm.createSocketKeepalive(network, tcpSocket, executor, callback);
1107             } catch (Exception e) {
1108                 fail("Unexpected error when creating TCP socket: " + e);
1109             }
1110             return null;
1111         });
1112     }
1113 
1114     /**
1115      * Creates concurrent keepalives until the specified counts of each type of keepalives are
1116      * reached or the expected error callbacks are received for each type of keepalives.
1117      *
1118      * @return the total number of keepalives created.
1119      */
1120     private int createConcurrentSocketKeepalives(
1121             @NonNull Network network, int nattCount, int tcpCount) throws Exception {
1122         final ArrayList<SocketKeepalive> kalist = new ArrayList<>();
1123         final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
1124 
1125         kalist.addAll(createConcurrentNattSocketKeepalives(network, nattCount, callback));
1126         kalist.addAll(createConcurrentTcpSocketKeepalives(network, tcpCount, callback));
1127 
1128         final int ret = kalist.size();
1129 
1130         // Clean up.
1131         for (final SocketKeepalive ka : kalist) {
1132             ka.stop();
1133             callback.expectStopped();
1134         }
1135         kalist.clear();
1136 
1137         return ret;
1138     }
1139 
1140     /**
1141      * Verifies that the concurrent keepalive slots meet the minimum requirement, and don't
1142      * get leaked after iterations.
1143      */
1144     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
1145     public void testSocketKeepaliveLimitWifi() throws Exception {
1146         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
1147             Log.i(TAG, "testSocketKeepaliveLimitWifi cannot execute unless device"
1148                     + " supports WiFi");
1149             return;
1150         }
1151 
1152         final Network network = ensureWifiConnected();
1153         final int supported = getSupportedKeepalivesForNet(network);
1154         if (supported == 0) {
1155             return;
1156         }
1157 
1158         adoptShellPermissionIdentity();
1159 
1160         // Verifies that the supported keepalive slots meet MIN_SUPPORTED_KEEPALIVE_COUNT.
1161         assertGreaterOrEqual(supported, MIN_SUPPORTED_WIFI_KEEPALIVE_COUNT);
1162 
1163         // Verifies that Nat-T keepalives can be established.
1164         assertEquals(supported, createConcurrentSocketKeepalives(network, supported + 1, 0));
1165         // Verifies that keepalives don't get leaked in second round.
1166         assertEquals(supported, createConcurrentSocketKeepalives(network, supported, 0));
1167 
1168         // If kernel < 4.8 then it doesn't support TCP keepalive, but it might still support
1169         // NAT-T keepalive. Test below cases only if TCP keepalive is supported by kernel.
1170         if (isTcpKeepaliveSupportedByKernel()) {
1171             assertEquals(supported, createConcurrentSocketKeepalives(network, 0, supported + 1));
1172 
1173             // Verifies that different types can be established at the same time.
1174             assertEquals(supported, createConcurrentSocketKeepalives(network,
1175                     supported / 2, supported - supported / 2));
1176 
1177             // Verifies that keepalives don't get leaked in second round.
1178             assertEquals(supported, createConcurrentSocketKeepalives(network, 0, supported));
1179             assertEquals(supported, createConcurrentSocketKeepalives(network,
1180                     supported / 2, supported - supported / 2));
1181         }
1182 
1183         dropShellPermissionIdentity();
1184     }
1185 
1186     /**
1187      * Verifies that the concurrent keepalive slots meet the minimum telephony requirement, and
1188      * don't get leaked after iterations.
1189      */
1190     @AppModeFull(reason = "Cannot request network in instant app mode")
1191     public void testSocketKeepaliveLimitTelephony() throws Exception {
1192         if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
1193             Log.i(TAG, "testSocketKeepaliveLimitTelephony cannot execute unless device"
1194                     + " supports telephony");
1195             return;
1196         }
1197 
1198         final int firstSdk = Build.VERSION.FIRST_SDK_INT;
1199         if (firstSdk < Build.VERSION_CODES.Q) {
1200             Log.i(TAG, "testSocketKeepaliveLimitTelephony: skip test for devices launching"
1201                     + " before Q: " + firstSdk);
1202             return;
1203         }
1204 
1205         final Network network = mCtsNetUtils.connectToCell();
1206         final int supported = getSupportedKeepalivesForNet(network);
1207 
1208         adoptShellPermissionIdentity();
1209 
1210         // Verifies that the supported keepalive slots meet minimum requirement.
1211         assertGreaterOrEqual(supported, MIN_SUPPORTED_CELLULAR_KEEPALIVE_COUNT);
1212 
1213         // Verifies that Nat-T keepalives can be established.
1214         assertEquals(supported, createConcurrentSocketKeepalives(network, supported + 1, 0));
1215         // Verifies that keepalives don't get leaked in second round.
1216         assertEquals(supported, createConcurrentSocketKeepalives(network, supported, 0));
1217 
1218         dropShellPermissionIdentity();
1219     }
1220 
1221     private int getIntResourceForName(@NonNull String resName) {
1222         final Resources r = mContext.getResources();
1223         final int resId = r.getIdentifier(resName, "integer", "android");
1224         return r.getInteger(resId);
1225     }
1226 
1227     /**
1228      * Verifies that the keepalive slots are limited as customized for unprivileged requests.
1229      */
1230     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
1231     public void testSocketKeepaliveUnprivileged() throws Exception {
1232         if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
1233             Log.i(TAG, "testSocketKeepaliveUnprivileged cannot execute unless device"
1234                     + " supports WiFi");
1235             return;
1236         }
1237 
1238         final Network network = ensureWifiConnected();
1239         final int supported = getSupportedKeepalivesForNet(network);
1240         if (supported == 0) {
1241             return;
1242         }
1243 
1244         // Resource ID might be shifted on devices that compiled with different symbols.
1245         // Thus, resolve ID at runtime is needed.
1246         final int allowedUnprivilegedPerUid =
1247                 getIntResourceForName(KEEPALIVE_ALLOWED_UNPRIVILEGED_RES_NAME);
1248         final int reservedPrivilegedSlots =
1249                 getIntResourceForName(KEEPALIVE_RESERVED_PER_SLOT_RES_NAME);
1250         // Verifies that unprivileged request per uid cannot exceed the limit customized in the
1251         // resource. Currently, unprivileged keepalive slots are limited to Nat-T only, this test
1252         // does not apply to TCP.
1253         assertGreaterOrEqual(supported, reservedPrivilegedSlots);
1254         assertGreaterOrEqual(supported, allowedUnprivilegedPerUid);
1255         final int expectedUnprivileged =
1256                 Math.min(allowedUnprivilegedPerUid, supported - reservedPrivilegedSlots);
1257         assertEquals(expectedUnprivileged,
1258                 createConcurrentSocketKeepalives(network, supported + 1, 0));
1259     }
1260 
1261     private static void assertGreaterOrEqual(long greater, long lesser) {
1262         assertTrue("" + greater + " expected to be greater than or equal to " + lesser,
1263                 greater >= lesser);
1264     }
1265 }
1266