Merge "Add test for WifiInfo#getFrequency()"
diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml
index c7cab7b..dbff179 100644
--- a/tests/cts/hostside/AndroidTest.xml
+++ b/tests/cts/hostside/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="component" value="networking" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<target_preparer class="com.android.cts.net.NetPolicyTestsPreparer" />
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
index 7bf7bd4..55bd406 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
@@ -150,6 +150,11 @@
}
public void testAppIdleNetworkAccess_whenCharging() throws Exception {
+ if (!isBatterySaverSupported()) {
+ Log.i(TAG, "Skipping " + getClass() + "." + getName()
+ + "() because device does not support Battery saver mode");
+ return;
+ }
if (!isSupported()) return;
// Check that app is paroled when charging
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
index 28175b8..931376b 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
@@ -16,6 +16,7 @@
package com.android.cts.net.hostside;
+import android.text.TextUtils;
import android.util.Log;
/**
@@ -52,12 +53,19 @@
@Override
protected boolean isSupported() throws Exception {
- boolean supported = isDozeModeEnabled();
- if (!supported) {
- Log.i(TAG, "Skipping " + getClass() + "." + getName()
- + "() because device does not support Doze Mode");
+ String unSupported = "";
+ if (!isDozeModeEnabled()) {
+ unSupported += "Doze mode,";
}
- return supported;
+ if (!isBatterySaverSupported()) {
+ unSupported += "Battery saver mode,";
+ }
+ if (!TextUtils.isEmpty(unSupported)) {
+ Log.i(TAG, "Skipping " + getClass() + "." + getName()
+ + "() because device does not support " + unSupported);
+ return false;
+ }
+ return true;
}
/**
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 6dfa2f3..40d7e34 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -34,6 +34,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
@@ -43,12 +44,15 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.test.InstrumentationTestCase;
import android.text.TextUtils;
import android.util.Log;
+import com.android.compatibility.common.util.BatteryUtils;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -311,6 +315,10 @@
return mSupported;
}
+ protected boolean isBatterySaverSupported() {
+ return BatteryUtils.isBatterySaverSupported();
+ }
+
/**
* Asserts that an app always have access while on foreground or running a foreground service.
*
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java
index 74875cd..b1a2186 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/MixedModesTest.java
@@ -71,6 +71,11 @@
* Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on metered networks.
*/
public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
+ if (!isBatterySaverSupported()) {
+ Log.i(TAG, "Skipping " + getClass() + "." + getName()
+ + "() because device does not support Battery saver mode");
+ return;
+ }
if (!isSupported()) return;
Log.i(TAG, "testDataAndBatterySaverModes_meteredNetwork() tests");
@@ -141,6 +146,11 @@
* networks.
*/
public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
+ if (!isBatterySaverSupported()) {
+ Log.i(TAG, "Skipping " + getClass() + "." + getName()
+ + "() because device does not support Battery saver mode");
+ return;
+ }
if (!isSupported()) return;
if (!setUnmeteredNetwork()) {
@@ -206,6 +216,11 @@
* are enabled.
*/
public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
+ if (!isBatterySaverSupported()) {
+ Log.i(TAG, "Skipping " + getClass() + "." + getName()
+ + "() because device does not support Battery saver mode");
+ return;
+ }
if (!isSupported()) {
return;
}
@@ -285,6 +300,11 @@
}
public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
+ if (!isBatterySaverSupported()) {
+ Log.i(TAG, "Skipping " + getClass() + "." + getName()
+ + "() because device does not support Battery saver mode");
+ return;
+ }
if (!isSupported()) {
return;
}
@@ -361,6 +381,11 @@
}
public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
+ if (!isBatterySaverSupported()) {
+ Log.i(TAG, "Skipping " + getClass() + "." + getName()
+ + "() because device does not support Battery saver mode");
+ return;
+ }
if (!isSupported()) {
return;
}
diff --git a/tests/cts/net/AndroidTest.xml b/tests/cts/net/AndroidTest.xml
index 1807a6b..3ff019e 100644
--- a/tests/cts/net/AndroidTest.xml
+++ b/tests/cts/net/AndroidTest.xml
@@ -18,6 +18,7 @@
<option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+ <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 8c9bf6e..ca1f771 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -35,6 +35,7 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import android.annotation.NonNull;
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.app.UiAutomation;
@@ -43,6 +44,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.IpSecManager;
@@ -59,6 +61,7 @@
import android.net.cts.util.CtsNetUtils;
import android.net.util.KeepaliveUtils;
import android.net.wifi.WifiManager;
+import android.os.Build;
import android.os.Looper;
import android.os.MessageQueue;
import android.os.SystemClock;
@@ -73,9 +76,6 @@
import androidx.test.InstrumentationRegistry;
-import com.android.internal.R;
-import com.android.internal.telephony.PhoneConstants;
-
import libcore.io.Streams;
import java.io.FileDescriptor;
@@ -99,6 +99,7 @@
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -113,6 +114,8 @@
private static final int CONNECT_TIMEOUT_MS = 2000;
private static final int KEEPALIVE_CALLBACK_TIMEOUT_MS = 2000;
private static final int KEEPALIVE_SOCKET_TIMEOUT_MS = 5000;
+ private static final int INTERVAL_KEEPALIVE_RETRY_MS = 500;
+ private static final int MAX_KEEPALIVE_RETRY_COUNT = 3;
private static final int MIN_KEEPALIVE_INTERVAL = 10;
private static final int NETWORK_CHANGE_METEREDNESS_TIMEOUT = 5000;
private static final int NUM_TRIES_MULTIPATH_PREF_CHECK = 20;
@@ -120,6 +123,17 @@
// device could have only one interface: data, wifi.
private static final int MIN_NUM_NETWORK_TYPES = 1;
+ // Minimum supported keepalive counts for wifi and cellular.
+ public static final int MIN_SUPPORTED_CELLULAR_KEEPALIVE_COUNT = 1;
+ public static final int MIN_SUPPORTED_WIFI_KEEPALIVE_COUNT = 3;
+
+ private static final String NETWORK_METERED_MULTIPATH_PREFERENCE_RES_NAME =
+ "config_networkMeteredMultipathPreference";
+ private static final String KEEPALIVE_ALLOWED_UNPRIVILEGED_RES_NAME =
+ "config_allowedUnprivilegedKeepalivePerUid";
+ private static final String KEEPALIVE_RESERVED_PER_SLOT_RES_NAME =
+ "config_reservedPrivilegedKeepaliveSlots";
+
private Context mContext;
private Instrumentation mInstrumentation;
private ConnectivityManager mCm;
@@ -367,9 +381,6 @@
final String invalidateFeature = "invalidateFeature";
final String mmsFeature = "enableMMS";
- final int failureCode = -1;
- final int wifiOnlyStartFailureCode = PhoneConstants.APN_REQUEST_FAILED;
- final int wifiOnlyStopFailureCode = -1;
assertStartUsingNetworkFeatureUnsupported(TYPE_MOBILE, invalidateFeature);
assertStopUsingNetworkFeatureUnsupported(TYPE_MOBILE, invalidateFeature);
@@ -663,7 +674,7 @@
final String rawMeteredPref = Settings.Global.getString(resolver,
NETWORK_METERED_MULTIPATH_PREFERENCE);
return TextUtils.isEmpty(rawMeteredPref)
- ? mContext.getResources().getInteger(R.integer.config_networkMeteredMultipathPreference)
+ ? getIntResourceForName(NETWORK_METERED_MULTIPATH_PREFERENCE_RES_NAME)
: Integer.parseInt(rawMeteredPref);
}
@@ -839,8 +850,7 @@
return s;
}
- private int getSupportedKeepalivesFromRes() throws Exception {
- final Network network = ensureWifiConnected();
+ private int getSupportedKeepalivesForNet(@NonNull Network network) throws Exception {
final NetworkCapabilities nc = mCm.getNetworkCapabilities(network);
// Get number of supported concurrent keepalives for testing network.
@@ -914,34 +924,46 @@
* keepalives is set to 0.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
- public void testKeepaliveUnsupported() throws Exception {
- if (getSupportedKeepalivesFromRes() != 0) return;
+ public void testKeepaliveWifiUnsupported() throws Exception {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testKeepaliveUnsupported cannot execute unless device"
+ + " supports WiFi");
+ return;
+ }
+
+ final Network network = ensureWifiConnected();
+ if (getSupportedKeepalivesForNet(network) != 0) return;
adoptShellPermissionIdentity();
- assertEquals(0, createConcurrentSocketKeepalives(1, 0));
- assertEquals(0, createConcurrentSocketKeepalives(0, 1));
+ assertEquals(0, createConcurrentSocketKeepalives(network, 1, 0));
+ assertEquals(0, createConcurrentSocketKeepalives(network, 0, 1));
dropShellPermissionIdentity();
}
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testCreateTcpKeepalive() throws Exception {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testCreateTcpKeepalive cannot execute unless device supports WiFi");
+ return;
+ }
+
adoptShellPermissionIdentity();
- if (getSupportedKeepalivesFromRes() == 0) return;
+ final Network network = ensureWifiConnected();
+ if (getSupportedKeepalivesForNet(network) == 0) return;
// If kernel < 4.8 then it doesn't support TCP keepalive, but it might still support
// NAT-T keepalive. If keepalive limits from resource overlay is not zero, TCP keepalive
// needs to be supported except if the kernel doesn't support it.
if (!isTcpKeepaliveSupportedByKernel()) {
// Sanity check to ensure the callback result is expected.
- assertEquals(0, createConcurrentSocketKeepalives(0, 1));
+ assertEquals(0, createConcurrentSocketKeepalives(network, 0, 1));
Log.i(TAG, "testCreateTcpKeepalive is skipped for kernel "
+ VintfRuntimeInfo.getKernelVersion());
return;
}
- final Network network = ensureWifiConnected();
final byte[] requestBytes = CtsNetUtils.HTTP_REQUEST.getBytes("UTF-8");
// So far only ipv4 tcp keepalive offload is supported.
// TODO: add test case for ipv6 tcp keepalive offload when it is supported.
@@ -1007,80 +1029,102 @@
}
}
- /**
- * Creates concurrent keepalives until the specified counts of each type of keepalives are
- * reached or the expected error callbacks are received for each type of keepalives.
- *
- * @return the total number of keepalives created.
- */
- private int createConcurrentSocketKeepalives(int nattCount, int tcpCount) throws Exception {
- final Network network = ensureWifiConnected();
-
+ private ArrayList<SocketKeepalive> createConcurrentKeepalivesOfType(
+ int requestCount, @NonNull TestSocketKeepaliveCallback callback,
+ Supplier<SocketKeepalive> kaFactory) {
final ArrayList<SocketKeepalive> kalist = new ArrayList<>();
- final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
- final Executor executor = mContext.getMainExecutor();
- // Create concurrent TCP keepalives.
- for (int i = 0; i < tcpCount; i++) {
- // Assert that TCP connections can be established on wifi. The file descriptor of tcp
- // sockets will be duplicated and kept valid in service side if the keepalives are
- // successfully started.
- try (Socket tcpSocket = getConnectedSocket(network, TEST_HOST, HTTP_PORT,
- 0 /* Unused */, AF_INET)) {
- final SocketKeepalive ka = mCm.createSocketKeepalive(network, tcpSocket, executor,
- callback);
- ka.start(MIN_KEEPALIVE_INTERVAL);
- TestSocketKeepaliveCallback.CallbackValue cv = callback.pollCallback();
- assertNotNull(cv);
- if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR) {
- if (i == 0 && cv.error == SocketKeepalive.ERROR_UNSUPPORTED) {
- // Unsupported.
- break;
- } else if (i != 0 && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
- // Limit reached.
- break;
- }
- }
- if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_STARTED) {
- kalist.add(ka);
- } else {
- fail("Unexpected error when creating " + (i + 1) + " TCP keepalives: " + cv);
- }
- }
- }
+ int remainingRetries = MAX_KEEPALIVE_RETRY_COUNT;
- // Assert that a Nat-T socket can be created.
- final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
- final UdpEncapsulationSocket nattSocket = mIpSec.openUdpEncapsulationSocket();
-
- final InetAddress srcAddr = getFirstV4Address(network);
- final InetAddress dstAddr = getAddrByName(TEST_HOST, AF_INET);
- assertNotNull(srcAddr);
- assertNotNull(dstAddr);
-
- // Test concurrent Nat-T keepalives.
- for (int i = 0; i < nattCount; i++) {
- final SocketKeepalive ka = mCm.createSocketKeepalive(network, nattSocket,
- srcAddr, dstAddr, executor, callback);
+ // Test concurrent keepalives with the given supplier.
+ while (kalist.size() < requestCount) {
+ final SocketKeepalive ka = kaFactory.get();
ka.start(MIN_KEEPALIVE_INTERVAL);
TestSocketKeepaliveCallback.CallbackValue cv = callback.pollCallback();
assertNotNull(cv);
if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_ERROR) {
- if (i == 0 && cv.error == SocketKeepalive.ERROR_UNSUPPORTED) {
+ if (kalist.size() == 0 && cv.error == SocketKeepalive.ERROR_UNSUPPORTED) {
// Unsupported.
break;
- } else if (i != 0 && cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
- // Limit reached.
+ } else if (cv.error == SocketKeepalive.ERROR_INSUFFICIENT_RESOURCES) {
+ // Limit reached or temporary unavailable due to stopped slot is not yet
+ // released.
+ if (remainingRetries > 0) {
+ SystemClock.sleep(INTERVAL_KEEPALIVE_RETRY_MS);
+ remainingRetries--;
+ continue;
+ }
break;
}
}
if (cv.callbackType == TestSocketKeepaliveCallback.CallbackType.ON_STARTED) {
kalist.add(ka);
} else {
- fail("Unexpected error when creating " + (i + 1) + " Nat-T keepalives: " + cv);
+ fail("Unexpected error when creating " + (kalist.size() + 1) + " "
+ + ka.getClass().getSimpleName() + ": " + cv);
}
}
+ return kalist;
+ }
+
+ private @NonNull ArrayList<SocketKeepalive> createConcurrentNattSocketKeepalives(
+ @NonNull Network network, int requestCount,
+ @NonNull TestSocketKeepaliveCallback callback) throws Exception {
+
+ final Executor executor = mContext.getMainExecutor();
+
+ // Initialize a real NaT-T socket.
+ final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ final UdpEncapsulationSocket nattSocket = mIpSec.openUdpEncapsulationSocket();
+ final InetAddress srcAddr = getFirstV4Address(network);
+ final InetAddress dstAddr = getAddrByName(TEST_HOST, AF_INET);
+ assertNotNull(srcAddr);
+ assertNotNull(dstAddr);
+
+ // Test concurrent Nat-T keepalives.
+ final ArrayList<SocketKeepalive> result = createConcurrentKeepalivesOfType(requestCount,
+ callback, () -> mCm.createSocketKeepalive(network, nattSocket,
+ srcAddr, dstAddr, executor, callback));
+
+ nattSocket.close();
+ return result;
+ }
+
+ private @NonNull ArrayList<SocketKeepalive> createConcurrentTcpSocketKeepalives(
+ @NonNull Network network, int requestCount,
+ @NonNull TestSocketKeepaliveCallback callback) {
+ final Executor executor = mContext.getMainExecutor();
+
+ // Create concurrent TCP keepalives.
+ return createConcurrentKeepalivesOfType(requestCount, callback, () -> {
+ // Assert that TCP connections can be established. The file descriptor of tcp
+ // sockets will be duplicated and kept valid in service side if the keepalives are
+ // successfully started.
+ try (Socket tcpSocket = getConnectedSocket(network, TEST_HOST, HTTP_PORT,
+ 0 /* Unused */, AF_INET)) {
+ return mCm.createSocketKeepalive(network, tcpSocket, executor, callback);
+ } catch (Exception e) {
+ fail("Unexpected error when creating TCP socket: " + e);
+ }
+ return null;
+ });
+ }
+
+ /**
+ * Creates concurrent keepalives until the specified counts of each type of keepalives are
+ * reached or the expected error callbacks are received for each type of keepalives.
+ *
+ * @return the total number of keepalives created.
+ */
+ private int createConcurrentSocketKeepalives(
+ @NonNull Network network, int nattCount, int tcpCount) throws Exception {
+ final ArrayList<SocketKeepalive> kalist = new ArrayList<>();
+ final TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+
+ kalist.addAll(createConcurrentNattSocketKeepalives(network, nattCount, callback));
+ kalist.addAll(createConcurrentTcpSocketKeepalives(network, tcpCount, callback));
+
final int ret = kalist.size();
// Clean up.
@@ -1089,7 +1133,6 @@
callback.expectStopped();
}
kalist.clear();
- nattSocket.close();
return ret;
}
@@ -1099,8 +1142,15 @@
* get leaked after iterations.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
- public void testSocketKeepaliveLimit() throws Exception {
- final int supported = getSupportedKeepalivesFromRes();
+ public void testSocketKeepaliveLimitWifi() throws Exception {
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testSocketKeepaliveLimitWifi cannot execute unless device"
+ + " supports WiFi");
+ return;
+ }
+
+ final Network network = ensureWifiConnected();
+ final int supported = getSupportedKeepalivesForNet(network);
if (supported == 0) {
return;
}
@@ -1108,25 +1158,25 @@
adoptShellPermissionIdentity();
// Verifies that the supported keepalive slots meet MIN_SUPPORTED_KEEPALIVE_COUNT.
- assertGreaterOrEqual(supported, KeepaliveUtils.MIN_SUPPORTED_KEEPALIVE_COUNT);
+ assertGreaterOrEqual(supported, MIN_SUPPORTED_WIFI_KEEPALIVE_COUNT);
// Verifies that Nat-T keepalives can be established.
- assertEquals(supported, createConcurrentSocketKeepalives(supported + 1, 0));
+ assertEquals(supported, createConcurrentSocketKeepalives(network, supported + 1, 0));
// Verifies that keepalives don't get leaked in second round.
- assertEquals(supported, createConcurrentSocketKeepalives(supported + 1, 0));
+ assertEquals(supported, createConcurrentSocketKeepalives(network, supported, 0));
// If kernel < 4.8 then it doesn't support TCP keepalive, but it might still support
// NAT-T keepalive. Test below cases only if TCP keepalive is supported by kernel.
if (isTcpKeepaliveSupportedByKernel()) {
- assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
+ assertEquals(supported, createConcurrentSocketKeepalives(network, 0, supported + 1));
// Verifies that different types can be established at the same time.
- assertEquals(supported, createConcurrentSocketKeepalives(
+ assertEquals(supported, createConcurrentSocketKeepalives(network,
supported / 2, supported - supported / 2));
// Verifies that keepalives don't get leaked in second round.
- assertEquals(supported, createConcurrentSocketKeepalives(0, supported + 1));
- assertEquals(supported, createConcurrentSocketKeepalives(
+ assertEquals(supported, createConcurrentSocketKeepalives(network, 0, supported));
+ assertEquals(supported, createConcurrentSocketKeepalives(network,
supported / 2, supported - supported / 2));
}
@@ -1134,19 +1184,69 @@
}
/**
+ * Verifies that the concurrent keepalive slots meet the minimum telephony requirement, and
+ * don't get leaked after iterations.
+ */
+ @AppModeFull(reason = "Cannot request network in instant app mode")
+ public void testSocketKeepaliveLimitTelephony() throws Exception {
+ if (!mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
+ Log.i(TAG, "testSocketKeepaliveLimitTelephony cannot execute unless device"
+ + " supports telephony");
+ return;
+ }
+
+ final int firstSdk = Build.VERSION.FIRST_SDK_INT;
+ if (firstSdk < Build.VERSION_CODES.Q) {
+ Log.i(TAG, "testSocketKeepaliveLimitTelephony: skip test for devices launching"
+ + " before Q: " + firstSdk);
+ return;
+ }
+
+ final Network network = mCtsNetUtils.connectToCell();
+ final int supported = getSupportedKeepalivesForNet(network);
+
+ adoptShellPermissionIdentity();
+
+ // Verifies that the supported keepalive slots meet minimum requirement.
+ assertGreaterOrEqual(supported, MIN_SUPPORTED_CELLULAR_KEEPALIVE_COUNT);
+
+ // Verifies that Nat-T keepalives can be established.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, supported + 1, 0));
+ // Verifies that keepalives don't get leaked in second round.
+ assertEquals(supported, createConcurrentSocketKeepalives(network, supported, 0));
+
+ dropShellPermissionIdentity();
+ }
+
+ private int getIntResourceForName(@NonNull String resName) {
+ final Resources r = mContext.getResources();
+ final int resId = r.getIdentifier(resName, "integer", "android");
+ return r.getInteger(resId);
+ }
+
+ /**
* Verifies that the keepalive slots are limited as customized for unprivileged requests.
*/
@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
public void testSocketKeepaliveUnprivileged() throws Exception {
- final int supported = getSupportedKeepalivesFromRes();
+ if (!mPackageManager.hasSystemFeature(FEATURE_WIFI)) {
+ Log.i(TAG, "testSocketKeepaliveUnprivileged cannot execute unless device"
+ + " supports WiFi");
+ return;
+ }
+
+ final Network network = ensureWifiConnected();
+ final int supported = getSupportedKeepalivesForNet(network);
if (supported == 0) {
return;
}
- final int allowedUnprivilegedPerUid = mContext.getResources().getInteger(
- R.integer.config_allowedUnprivilegedKeepalivePerUid);
- final int reservedPrivilegedSlots = mContext.getResources().getInteger(
- R.integer.config_reservedPrivilegedKeepaliveSlots);
+ // Resource ID might be shifted on devices that compiled with different symbols.
+ // Thus, resolve ID at runtime is needed.
+ final int allowedUnprivilegedPerUid =
+ getIntResourceForName(KEEPALIVE_ALLOWED_UNPRIVILEGED_RES_NAME);
+ final int reservedPrivilegedSlots =
+ getIntResourceForName(KEEPALIVE_RESERVED_PER_SLOT_RES_NAME);
// Verifies that unprivileged request per uid cannot exceed the limit customized in the
// resource. Currently, unprivileged keepalive slots are limited to Nat-T only, this test
// does not apply to TCP.
@@ -1154,7 +1254,8 @@
assertGreaterOrEqual(supported, allowedUnprivilegedPerUid);
final int expectedUnprivileged =
Math.min(allowedUnprivilegedPerUid, supported - reservedPrivilegedSlots);
- assertEquals(expectedUnprivileged, createConcurrentSocketKeepalives(supported + 1, 0));
+ assertEquals(expectedUnprivileged,
+ createConcurrentSocketKeepalives(network, supported + 1, 0));
}
private static void assertGreaterOrEqual(long greater, long lesser) {
diff --git a/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java b/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
index 01ac3fd..cbe54f8 100644
--- a/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
+++ b/tests/cts/net/src/android/net/cts/SSLCertificateSocketFactoryTest.java
@@ -16,135 +16,333 @@
package android.net.cts;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.Socket;
-
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLPeerUnverifiedException;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.net.SSLCertificateSocketFactory;
import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
import libcore.javax.net.ssl.SSLConfigurationAsserts;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
-public class SSLCertificateSocketFactoryTest extends AndroidTestCase {
- private SSLCertificateSocketFactory mFactory;
- private int mTimeout;
+@RunWith(JUnit4.class)
+public class SSLCertificateSocketFactoryTest {
+ // TEST_HOST should point to a web server with a valid TLS certificate.
+ private static final String TEST_HOST = "www.google.com";
+ private static final int HTTPS_PORT = 443;
+ private HostnameVerifier mDefaultVerifier;
+ private SSLCertificateSocketFactory mSocketFactory;
+ private InetAddress mLocalAddress;
+ // InetAddress obtained by resolving TEST_HOST.
+ private InetAddress mTestHostAddress;
+ // SocketAddress combining mTestHostAddress and HTTPS_PORT.
+ private List<SocketAddress> mTestSocketAddresses;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mTimeout = 1000;
- mFactory = (SSLCertificateSocketFactory) SSLCertificateSocketFactory.getDefault(mTimeout);
+ @Before
+ public void setUp() {
+ // Expected state before each test method is that
+ // HttpsURLConnection.getDefaultHostnameVerifier() will return the system default.
+ mDefaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
+ mSocketFactory = (SSLCertificateSocketFactory)
+ SSLCertificateSocketFactory.getDefault(1000 /* handshakeTimeoutMillis */);
+ assertNotNull(mSocketFactory);
+ InetAddress[] addresses;
+ try {
+ addresses = InetAddress.getAllByName(TEST_HOST);
+ mTestHostAddress = addresses[0];
+ } catch (UnknownHostException uhe) {
+ throw new AssertionError(
+ "Unable to test SSLCertificateSocketFactory: cannot resolve " + TEST_HOST, uhe);
+ }
+
+ mTestSocketAddresses = Arrays.stream(addresses)
+ .map(addr -> new InetSocketAddress(addr, HTTPS_PORT))
+ .collect(Collectors.toList());
+
+ // Find the local IP address which will be used to connect to TEST_HOST.
+ try {
+ Socket testSocket = new Socket(TEST_HOST, HTTPS_PORT);
+ mLocalAddress = testSocket.getLocalAddress();
+ testSocket.close();
+ } catch (IOException ioe) {
+ throw new AssertionError(""
+ + "Unable to test SSLCertificateSocketFactory: cannot connect to "
+ + TEST_HOST, ioe);
+ }
}
+ // Restore the system default hostname verifier after each test.
+ @After
+ public void restoreDefaultHostnameVerifier() {
+ HttpsURLConnection.setDefaultHostnameVerifier(mDefaultVerifier);
+ }
+
+ @Test
public void testDefaultConfiguration() throws Exception {
- SSLConfigurationAsserts.assertSSLSocketFactoryDefaultConfiguration(mFactory);
+ SSLConfigurationAsserts.assertSSLSocketFactoryDefaultConfiguration(mSocketFactory);
}
- public void testAccessProperties() throws Exception {
- mFactory.getSupportedCipherSuites();
- mFactory.getDefaultCipherSuites();
- SocketFactory sf = SSLCertificateSocketFactory.getDefault(mTimeout);
- assertNotNull(sf);
+ @Test
+ public void testAccessProperties() {
+ mSocketFactory.getSupportedCipherSuites();
+ mSocketFactory.getDefaultCipherSuites();
}
- public void testCreateSocket() throws Exception {
- new SSLCertificateSocketFactory(100);
- int port = 443;
- String host = "www.google.com";
- InetAddress inetAddress = null;
- inetAddress = InetAddress.getLocalHost();
+ /**
+ * Tests the {@code createSocket()} cases which are expected to fail with {@code IOException}.
+ */
+ @Test
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
+ public void createSocket_io_error_expected() {
+ // Connect to the localhost HTTPS port. Should result in connection refused IOException
+ // because no service should be listening on that port.
+ InetAddress localhostAddress = InetAddress.getLoopbackAddress();
try {
- mFactory.createSocket(inetAddress, port);
- fail("should throw exception!");
+ mSocketFactory.createSocket(localhostAddress, HTTPS_PORT);
+ fail();
} catch (IOException e) {
// expected
}
+ // Same, but also binding to a local address.
try {
- InetAddress inetAddress1 = InetAddress.getLocalHost();
- InetAddress inetAddress2 = InetAddress.getLocalHost();
- mFactory.createSocket(inetAddress1, port, inetAddress2, port);
- fail("should throw exception!");
+ mSocketFactory.createSocket(localhostAddress, HTTPS_PORT, localhostAddress, 0);
+ fail();
} catch (IOException e) {
// expected
}
+ // Same, wrapping an existing plain socket which is in an unconnected state.
try {
Socket socket = new Socket();
- mFactory.createSocket(socket, host, port, true);
- fail("should throw exception!");
+ mSocketFactory.createSocket(socket, "localhost", HTTPS_PORT, true);
+ fail();
} catch (IOException e) {
// expected
}
- Socket socket = null;
- socket = mFactory.createSocket(host, port);
+ }
+
+ /**
+ * Tests hostname verification for
+ * {@link SSLCertificateSocketFactory#createSocket(String, int)}.
+ *
+ * <p>This method should return a socket which is fully connected (i.e. TLS handshake complete)
+ * and whose peer TLS certificate has been verified to have the correct hostname.
+ *
+ * <p>{@link SSLCertificateSocketFactory} is documented to verify hostnames using
+ * the {@link HostnameVerifier} returned by
+ * {@link HttpsURLConnection#getDefaultHostnameVerifier}, so this test connects twice,
+ * once with the system default {@link HostnameVerifier} which is expected to succeed,
+ * and once after installing a {@link NegativeHostnameVerifier} which will cause
+ * {@link SSLCertificateSocketFactory#verifyHostname} to throw a
+ * {@link SSLPeerUnverifiedException}.
+ *
+ * <p>These tests only test the hostname verification logic in SSLCertificateSocketFactory,
+ * other TLS failure modes and the default HostnameVerifier are tested elsewhere, see
+ * {@link com.squareup.okhttp.internal.tls.HostnameVerifierTest} and
+ * https://android.googlesource.com/platform/external/boringssl/+/refs/heads/master/src/ssl/test
+ *
+ * <p>Tests the following behaviour:-
+ * <ul>
+ * <li>TEST_SERVER is available and has a valid TLS certificate
+ * <li>{@code createSocket()} verifies the remote hostname is correct using
+ * {@link HttpsURLConnection#getDefaultHostnameVerifier}
+ * <li>{@link SSLPeerUnverifiedException} is thrown when the remote hostname is invalid
+ * </ul>
+ *
+ * <p>See also http://b/2807618.
+ */
+ @Test
+ public void createSocket_simple_with_hostname_verification() throws Exception {
+ Socket socket = mSocketFactory.createSocket(TEST_HOST, HTTPS_PORT);
+ assertConnectedSocket(socket);
+ socket.close();
+
+ HttpsURLConnection.setDefaultHostnameVerifier(new NegativeHostnameVerifier());
+ try {
+ mSocketFactory.createSocket(TEST_HOST, HTTPS_PORT);
+ fail();
+ } catch (SSLPeerUnverifiedException expected) {
+ // expected
+ }
+ }
+
+ /**
+ * Tests hostname verification for
+ * {@link SSLCertificateSocketFactory#createSocket(Socket, String, int, boolean)}.
+ *
+ * <p>This method should return a socket which is fully connected (i.e. TLS handshake complete)
+ * and whose peer TLS certificate has been verified to have the correct hostname.
+ *
+ * <p>The TLS socket returned is wrapped around the plain socket passed into
+ * {@code createSocket()}.
+ *
+ * <p>See {@link #createSocket_simple_with_hostname_verification()} for test methodology.
+ */
+ @Test
+ public void createSocket_wrapped_with_hostname_verification() throws Exception {
+ Socket underlying = new Socket(TEST_HOST, HTTPS_PORT);
+ Socket socket = mSocketFactory.createSocket(underlying, TEST_HOST, HTTPS_PORT, true);
+ assertConnectedSocket(socket);
+ socket.close();
+
+ HttpsURLConnection.setDefaultHostnameVerifier(new NegativeHostnameVerifier());
+ try {
+ underlying = new Socket(TEST_HOST, HTTPS_PORT);
+ mSocketFactory.createSocket(underlying, TEST_HOST, HTTPS_PORT, true);
+ fail();
+ } catch (SSLPeerUnverifiedException expected) {
+ // expected
+ }
+ }
+
+ /**
+ * Tests hostname verification for
+ * {@link SSLCertificateSocketFactory#createSocket(String, int, InetAddress, int)}.
+ *
+ * <p>This method should return a socket which is fully connected (i.e. TLS handshake complete)
+ * and whose peer TLS certificate has been verified to have the correct hostname.
+ *
+ * <p>The TLS socket returned is also bound to the local address determined in {@link #setUp} to
+ * be used for connections to TEST_HOST, and a wildcard port.
+ *
+ * <p>See {@link #createSocket_simple_with_hostname_verification()} for test methodology.
+ */
+ @Test
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
+ public void createSocket_bound_with_hostname_verification() throws Exception {
+ Socket socket = mSocketFactory.createSocket(TEST_HOST, HTTPS_PORT, mLocalAddress, 0);
+ assertConnectedSocket(socket);
+ socket.close();
+
+ HttpsURLConnection.setDefaultHostnameVerifier(new NegativeHostnameVerifier());
+ try {
+ mSocketFactory.createSocket(TEST_HOST, HTTPS_PORT, mLocalAddress, 0);
+ fail();
+ } catch (SSLPeerUnverifiedException expected) {
+ // expected
+ }
+ }
+
+ /**
+ * Tests hostname verification for
+ * {@link SSLCertificateSocketFactory#createSocket(InetAddress, int)}.
+ *
+ * <p>This method should return a socket which the documentation describes as "unconnected",
+ * which actually means that the socket is fully connected at the TCP layer but TLS handshaking
+ * and hostname verification have not yet taken place.
+ *
+ * <p>Behaviour is tested by installing a {@link NegativeHostnameVerifier} and by calling
+ * {@link #assertConnectedSocket} to ensure TLS handshaking but no hostname verification takes
+ * place. Next, {@link SSLCertificateSocketFactory#verifyHostname} is called to ensure
+ * that hostname verification is using the {@link HostnameVerifier} returned by
+ * {@link HttpsURLConnection#getDefaultHostnameVerifier} as documented.
+ *
+ * <p>Tests the following behaviour:-
+ * <ul>
+ * <li>TEST_SERVER is available and has a valid TLS certificate
+ * <li>{@code createSocket()} does not verify the remote hostname
+ * <li>Calling {@link SSLCertificateSocketFactory#verifyHostname} on the returned socket
+ * throws {@link SSLPeerUnverifiedException} if the remote hostname is invalid
+ * </ul>
+ */
+ @Test
+ public void createSocket_simple_no_hostname_verification() throws Exception{
+ HttpsURLConnection.setDefaultHostnameVerifier(new NegativeHostnameVerifier());
+ Socket socket = mSocketFactory.createSocket(mTestHostAddress, HTTPS_PORT);
+ // Need to provide the expected hostname here or the TLS handshake will
+ // be unable to supply SNI to the remote host.
+ mSocketFactory.setHostname(socket, TEST_HOST);
+ assertConnectedSocket(socket);
+ try {
+ SSLCertificateSocketFactory.verifyHostname(socket, TEST_HOST);
+ fail();
+ } catch (SSLPeerUnverifiedException expected) {
+ // expected
+ }
+ HttpsURLConnection.setDefaultHostnameVerifier(mDefaultVerifier);
+ SSLCertificateSocketFactory.verifyHostname(socket, TEST_HOST);
+ socket.close();
+ }
+
+ /**
+ * Tests hostname verification for
+ * {@link SSLCertificateSocketFactory#createSocket(InetAddress, int, InetAddress, int)}.
+ *
+ * <p>This method should return a socket which the documentation describes as "unconnected",
+ * which actually means that the socket is fully connected at the TCP layer but TLS handshaking
+ * and hostname verification have not yet taken place.
+ *
+ * <p>The TLS socket returned is also bound to the local address determined in {@link #setUp} to
+ * be used for connections to TEST_HOST, and a wildcard port.
+ *
+ * <p>See {@link #createSocket_simple_no_hostname_verification()} for test methodology.
+ */
+ @Test
+ @AppModeFull(reason = "Socket cannot bind in instant app mode")
+ public void createSocket_bound_no_hostname_verification() throws Exception{
+ HttpsURLConnection.setDefaultHostnameVerifier(new NegativeHostnameVerifier());
+ Socket socket =
+ mSocketFactory.createSocket(mTestHostAddress, HTTPS_PORT, mLocalAddress, 0);
+ // Need to provide the expected hostname here or the TLS handshake will
+ // be unable to supply SNI to the peer.
+ mSocketFactory.setHostname(socket, TEST_HOST);
+ assertConnectedSocket(socket);
+ try {
+ SSLCertificateSocketFactory.verifyHostname(socket, TEST_HOST);
+ fail();
+ } catch (SSLPeerUnverifiedException expected) {
+ // expected
+ }
+ HttpsURLConnection.setDefaultHostnameVerifier(mDefaultVerifier);
+ SSLCertificateSocketFactory.verifyHostname(socket, TEST_HOST);
+ socket.close();
+ }
+
+ /**
+ * Asserts a socket is fully connected to the expected peer.
+ *
+ * <p>For the variants of createSocket which verify the remote hostname,
+ * {@code socket} should already be fully connected.
+ *
+ * <p>For the non-verifying variants, retrieving the input stream will trigger a TLS handshake
+ * and so may throw an exception, for example if the peer's certificate is invalid.
+ *
+ * <p>Does no hostname verification.
+ */
+ private void assertConnectedSocket(Socket socket) throws Exception {
assertNotNull(socket);
- assertNotNull(socket.getOutputStream());
+ assertTrue(socket.isConnected());
assertNotNull(socket.getInputStream());
-
- // it throw exception when calling createSocket(String, int, InetAddress, int)
- // The socket level is invalid.
- }
-
- // a host and port that are expected to be available but have
- // a cert with a different CN, in this case CN=mail.google.com
- private static String TEST_CREATE_SOCKET_HOST = "www3.l.google.com";
- private static int TEST_CREATE_SOCKET_PORT = 443;
-
- /**
- * b/2807618 Make sure that hostname verifcation in cases were it
- * is documented to be included by various
- * SSLCertificateSocketFactory.createSocket messages.
- *
- * NOTE: Test will fail if external server is not available.
- */
- @AppModeFull(reason = "Socket cannot bind in instant app mode")
- public void test_createSocket_simple() throws Exception {
- try {
- mFactory.createSocket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT);
- fail();
- } catch (SSLPeerUnverifiedException expected) {
- // expected
- }
+ assertNotNull(socket.getOutputStream());
+ assertTrue(mTestSocketAddresses.contains(socket.getRemoteSocketAddress()));
}
/**
- * b/2807618 Make sure that hostname verifcation in cases were it
- * is documented to be included by various
- * SSLCertificateSocketFactory.createSocket messages.
- *
- * NOTE: Test will fail if external server is not available.
+ * A HostnameVerifier which always returns false to simulate a server returning a
+ * certificate which does not match the expected hostname.
*/
- @AppModeFull(reason = "Socket cannot bind in instant app mode")
- public void test_createSocket_wrapping() throws Exception {
- try {
- Socket underlying = new Socket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT);
- mFactory.createSocket(
- underlying, TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT, true);
- fail();
- } catch (SSLPeerUnverifiedException expected) {
- // expected
- }
- }
-
- /**
- * b/2807618 Make sure that hostname verifcation in cases were it
- * is documented to be included by various
- * SSLCertificateSocketFactory.createSocket messages.
- *
- * NOTE: Test will fail if external server is not available.
- */
- @AppModeFull(reason = "Socket cannot bind in instant app mode")
- public void test_createSocket_bind() throws Exception {
- try {
- mFactory.createSocket(TEST_CREATE_SOCKET_HOST, TEST_CREATE_SOCKET_PORT, null, 0);
- fail();
- } catch (SSLPeerUnverifiedException expected) {
- // expected
+ private static class NegativeHostnameVerifier implements HostnameVerifier {
+ @Override
+ public boolean verify(String hostname, SSLSession sslSession) {
+ return false;
}
}
}