Snap for 7486544 from 342ee18b5770ff1f40d6929f303b246e98a76b47 to sc-release
Change-Id: Ie60bebe1250164a9d43b2d65330479252433738a
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index a3fc621..758c612 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -936,7 +936,17 @@
private final Context mContext;
- private final TetheringManager mTetheringManager;
+ @GuardedBy("mTetheringEventCallbacks")
+ private TetheringManager mTetheringManager;
+
+ private TetheringManager getTetheringManager() {
+ synchronized (mTetheringEventCallbacks) {
+ if (mTetheringManager == null) {
+ mTetheringManager = mContext.getSystemService(TetheringManager.class);
+ }
+ return mTetheringManager;
+ }
+ }
/**
* Tests if a given integer represents a valid network type.
@@ -2395,7 +2405,6 @@
public ConnectivityManager(Context context, IConnectivityManager service) {
mContext = Objects.requireNonNull(context, "missing context");
mService = Objects.requireNonNull(service, "missing IConnectivityManager");
- mTetheringManager = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE);
sInstance = this;
}
@@ -2466,7 +2475,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetherableIfaces() {
- return mTetheringManager.getTetherableIfaces();
+ return getTetheringManager().getTetherableIfaces();
}
/**
@@ -2481,7 +2490,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetheredIfaces() {
- return mTetheringManager.getTetheredIfaces();
+ return getTetheringManager().getTetheredIfaces();
}
/**
@@ -2502,7 +2511,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetheringErroredIfaces() {
- return mTetheringManager.getTetheringErroredIfaces();
+ return getTetheringManager().getTetheringErroredIfaces();
}
/**
@@ -2546,7 +2555,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Deprecated
public int tether(String iface) {
- return mTetheringManager.tether(iface);
+ return getTetheringManager().tether(iface);
}
/**
@@ -2570,7 +2579,7 @@
@UnsupportedAppUsage
@Deprecated
public int untether(String iface) {
- return mTetheringManager.untether(iface);
+ return getTetheringManager().untether(iface);
}
/**
@@ -2596,7 +2605,7 @@
@RequiresPermission(anyOf = {android.Manifest.permission.TETHER_PRIVILEGED,
android.Manifest.permission.WRITE_SETTINGS})
public boolean isTetheringSupported() {
- return mTetheringManager.isTetheringSupported();
+ return getTetheringManager().isTetheringSupported();
}
/**
@@ -2689,7 +2698,7 @@
final TetheringRequest request = new TetheringRequest.Builder(type)
.setShouldShowEntitlementUi(showProvisioningUi).build();
- mTetheringManager.startTethering(request, executor, tetheringCallback);
+ getTetheringManager().startTethering(request, executor, tetheringCallback);
}
/**
@@ -2708,7 +2717,7 @@
@Deprecated
@RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED)
public void stopTethering(int type) {
- mTetheringManager.stopTethering(type);
+ getTetheringManager().stopTethering(type);
}
/**
@@ -2766,7 +2775,7 @@
synchronized (mTetheringEventCallbacks) {
mTetheringEventCallbacks.put(callback, tetherCallback);
- mTetheringManager.registerTetheringEventCallback(executor, tetherCallback);
+ getTetheringManager().registerTetheringEventCallback(executor, tetherCallback);
}
}
@@ -2788,7 +2797,7 @@
synchronized (mTetheringEventCallbacks) {
final TetheringEventCallback tetherCallback =
mTetheringEventCallbacks.remove(callback);
- mTetheringManager.unregisterTetheringEventCallback(tetherCallback);
+ getTetheringManager().unregisterTetheringEventCallback(tetherCallback);
}
}
@@ -2808,7 +2817,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetherableUsbRegexs() {
- return mTetheringManager.getTetherableUsbRegexs();
+ return getTetheringManager().getTetherableUsbRegexs();
}
/**
@@ -2826,7 +2835,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetherableWifiRegexs() {
- return mTetheringManager.getTetherableWifiRegexs();
+ return getTetheringManager().getTetherableWifiRegexs();
}
/**
@@ -2845,7 +2854,7 @@
@UnsupportedAppUsage
@Deprecated
public String[] getTetherableBluetoothRegexs() {
- return mTetheringManager.getTetherableBluetoothRegexs();
+ return getTetheringManager().getTetherableBluetoothRegexs();
}
/**
@@ -2869,7 +2878,7 @@
@UnsupportedAppUsage
@Deprecated
public int setUsbTethering(boolean enable) {
- return mTetheringManager.setUsbTethering(enable);
+ return getTetheringManager().setUsbTethering(enable);
}
/**
@@ -2985,7 +2994,7 @@
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@Deprecated
public int getLastTetherError(String iface) {
- int error = mTetheringManager.getLastTetherError(iface);
+ int error = getTetheringManager().getLastTetherError(iface);
if (error == TetheringManager.TETHER_ERROR_UNKNOWN_TYPE) {
// TETHER_ERROR_UNKNOWN_TYPE was introduced with TetheringManager and has never been
// returned by ConnectivityManager. Convert it to the legacy TETHER_ERROR_UNKNOWN_IFACE
@@ -3067,7 +3076,7 @@
}
};
- mTetheringManager.requestLatestTetheringEntitlementResult(type, wrappedListener,
+ getTetheringManager().requestLatestTetheringEntitlementResult(type, wrappedListener,
showEntitlementUi);
}
@@ -4713,6 +4722,22 @@
}
/**
+ * Temporarily allow bad wifi to override {@code config_networkAvoidBadWifi} configuration.
+ *
+ * @param timeMs The expired current time. The value should be set within a limited time from
+ * now.
+ *
+ * @hide
+ */
+ public void setTestAllowBadWifiUntil(long timeMs) {
+ try {
+ mService.setTestAllowBadWifiUntil(timeMs);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Requests that the system open the captive portal app on the specified network.
*
* <p>This is to be used on networks where a captive portal was detected, as per
@@ -4849,7 +4874,7 @@
public void factoryReset() {
try {
mService.factoryReset();
- mTetheringManager.stopAllTethering();
+ getTetheringManager().stopAllTethering();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index 03c3600..085de6b 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -20,12 +20,13 @@
import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE;
import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
+import static com.android.net.module.util.ConnectivitySettingsUtils.getPrivateDnsModeAsString;
+
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.content.ContentResolver;
import android.content.Context;
import android.net.ConnectivityManager.MultipathPreference;
import android.os.Process;
@@ -35,6 +36,7 @@
import android.util.ArraySet;
import android.util.Range;
+import com.android.net.module.util.ConnectivitySettingsUtils;
import com.android.net.module.util.ProxyUtils;
import java.lang.annotation.Retention;
@@ -345,20 +347,22 @@
/**
* One of the private DNS modes that indicates the private DNS mode is off.
*/
- public static final int PRIVATE_DNS_MODE_OFF = 1;
+ public static final int PRIVATE_DNS_MODE_OFF = ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OFF;
/**
* One of the private DNS modes that indicates the private DNS mode is automatic, which
* will try to use the current DNS as private DNS.
*/
- public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC = 2;
+ public static final int PRIVATE_DNS_MODE_OPPORTUNISTIC =
+ ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC;
/**
* One of the private DNS modes that indicates the private DNS mode is strict and the
* {@link #PRIVATE_DNS_SPECIFIER} is required, which will try to use the value of
* {@link #PRIVATE_DNS_SPECIFIER} as private DNS.
*/
- public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME = 3;
+ public static final int PRIVATE_DNS_MODE_PROVIDER_HOSTNAME =
+ ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -369,10 +373,6 @@
})
public @interface PrivateDnsMode {}
- private static final String PRIVATE_DNS_MODE_OFF_STRING = "off";
- private static final String PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING = "opportunistic";
- private static final String PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING = "hostname";
-
/**
* A list of uids that is allowed to use restricted networks.
*
@@ -562,7 +562,7 @@
public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context,
@IntRange(from = 0) int count) {
if (count < 0) {
- throw new IllegalArgumentException("Count must be 0~10.");
+ throw new IllegalArgumentException("Count must be more than 0.");
}
Settings.Global.putInt(
context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, count);
@@ -585,6 +585,7 @@
/**
* Set minimum duration (to {@link Settings}) between each switching network notifications.
+ * The duration will be rounded down to the next millisecond, and must be positive.
*
* @param context The {@link Context} to set the setting.
* @param duration The minimum duration between notifications when switching networks.
@@ -612,10 +613,11 @@
/**
* Set URL (to {@link Settings}) used for HTTP captive portal detection upon a new connection.
- * This URL should respond with a 204 response to a GET request to indicate no captive portal is
- * present. And this URL must be HTTP as redirect responses are used to find captive portal
- * sign-in pages. If the URL set to null or be incorrect, it will result in captive portal
- * detection failed and lost the connection.
+ * The URL is accessed to check for connectivity and presence of a captive portal on a network.
+ * The URL should respond with HTTP status 204 to a GET request, and the stack will use
+ * redirection status as a signal for captive portal detection.
+ * If the URL is set to null or is otherwise incorrect or inaccessible, the stack will fail to
+ * detect connectivity and portals. This will often result in loss of connectivity.
*
* @param context The {@link Context} to set the setting.
* @param url The URL used for HTTP captive portal detection upon a new connection.
@@ -730,32 +732,6 @@
context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
}
- private static String getPrivateDnsModeAsString(@PrivateDnsMode int mode) {
- switch (mode) {
- case PRIVATE_DNS_MODE_OFF:
- return PRIVATE_DNS_MODE_OFF_STRING;
- case PRIVATE_DNS_MODE_OPPORTUNISTIC:
- return PRIVATE_DNS_MODE_OPPORTUNISTIC_STRING;
- case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME:
- return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME_STRING;
- default:
- throw new IllegalArgumentException("Invalid private dns mode: " + mode);
- }
- }
-
- private static int getPrivateDnsModeAsInt(String mode) {
- switch (mode) {
- case "off":
- return PRIVATE_DNS_MODE_OFF;
- case "hostname":
- return PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
- case "opportunistic":
- return PRIVATE_DNS_MODE_OPPORTUNISTIC;
- default:
- throw new IllegalArgumentException("Invalid private dns mode: " + mode);
- }
- }
-
/**
* Get private DNS mode from settings.
*
@@ -764,13 +740,7 @@
*/
@PrivateDnsMode
public static int getPrivateDnsMode(@NonNull Context context) {
- final ContentResolver cr = context.getContentResolver();
- String mode = Settings.Global.getString(cr, PRIVATE_DNS_MODE);
- if (TextUtils.isEmpty(mode)) mode = Settings.Global.getString(cr, PRIVATE_DNS_DEFAULT_MODE);
- // If both PRIVATE_DNS_MODE and PRIVATE_DNS_DEFAULT_MODE are not set, choose
- // PRIVATE_DNS_MODE_OPPORTUNISTIC as default mode.
- if (TextUtils.isEmpty(mode)) return PRIVATE_DNS_MODE_OPPORTUNISTIC;
- return getPrivateDnsModeAsInt(mode);
+ return ConnectivitySettingsUtils.getPrivateDnsMode(context);
}
/**
@@ -780,13 +750,7 @@
* @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants.
*/
public static void setPrivateDnsMode(@NonNull Context context, @PrivateDnsMode int mode) {
- if (!(mode == PRIVATE_DNS_MODE_OFF
- || mode == PRIVATE_DNS_MODE_OPPORTUNISTIC
- || mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME)) {
- throw new IllegalArgumentException("Invalid private dns mode: " + mode);
- }
- Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE,
- getPrivateDnsModeAsString(mode));
+ ConnectivitySettingsUtils.setPrivateDnsMode(context, mode);
}
/**
@@ -797,7 +761,7 @@
*/
@Nullable
public static String getPrivateDnsHostname(@NonNull Context context) {
- return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER);
+ return ConnectivitySettingsUtils.getPrivateDnsHostname(context);
}
/**
@@ -806,9 +770,8 @@
* @param context The {@link Context} to set the setting.
* @param specifier The specific private dns provider name.
*/
- public static void setPrivateDnsHostname(@NonNull Context context,
- @Nullable String specifier) {
- Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER, specifier);
+ public static void setPrivateDnsHostname(@NonNull Context context, @Nullable String specifier) {
+ ConnectivitySettingsUtils.setPrivateDnsHostname(context, specifier);
}
/**
@@ -858,6 +821,7 @@
/**
* Set duration (to {@link Settings}) to keep a PendingIntent-based request.
+ * The duration will be rounded down to the next millisecond, and must be positive.
*
* @param context The {@link Context} to set the setting.
* @param duration The duration to keep a PendingIntent-based request.
diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl
index c434bbc..50ec781 100644
--- a/framework/src/android/net/IConnectivityManager.aidl
+++ b/framework/src/android/net/IConnectivityManager.aidl
@@ -226,4 +226,6 @@
void offerNetwork(int providerId, in NetworkScore score,
in NetworkCapabilities caps, in INetworkOfferCallback callback);
void unofferNetwork(in INetworkOfferCallback callback);
+
+ void setTestAllowBadWifiUntil(long timeMs);
}
diff --git a/framework/src/android/net/util/MultinetworkPolicyTracker.java b/framework/src/android/net/util/MultinetworkPolicyTracker.java
index 0b42a00..7e62d28 100644
--- a/framework/src/android/net/util/MultinetworkPolicyTracker.java
+++ b/framework/src/android/net/util/MultinetworkPolicyTracker.java
@@ -75,6 +75,7 @@
private volatile boolean mAvoidBadWifi = true;
private volatile int mMeteredMultipathPreference;
private int mActiveSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+ private volatile long mTestAllowBadWifiUntilMs = 0;
// Mainline module can't use internal HandlerExecutor, so add an identical executor here.
private static class HandlerExecutor implements Executor {
@@ -162,14 +163,31 @@
* Whether the device or carrier configuration disables avoiding bad wifi by default.
*/
public boolean configRestrictsAvoidBadWifi() {
+ final boolean allowBadWifi = mTestAllowBadWifiUntilMs > 0
+ && mTestAllowBadWifiUntilMs > System.currentTimeMillis();
+ // If the config returns true, then avoid bad wifi design can be controlled by the
+ // NETWORK_AVOID_BAD_WIFI setting.
+ if (allowBadWifi) return true;
+
// TODO: use R.integer.config_networkAvoidBadWifi directly
final int id = mResources.get().getIdentifier("config_networkAvoidBadWifi",
"integer", mResources.getResourcesContext().getPackageName());
return (getResourcesForActiveSubId().getInteger(id) == 0);
}
+ /**
+ * Temporarily allow bad wifi to override {@code config_networkAvoidBadWifi} configuration.
+ * The value works when the time set is more than {@link System.currentTimeMillis()}.
+ */
+ public void setTestAllowBadWifiUntil(long timeMs) {
+ Log.d(TAG, "setTestAllowBadWifiUntil: " + mTestAllowBadWifiUntilMs);
+ mTestAllowBadWifiUntilMs = timeMs;
+ updateAvoidBadWifi();
+ }
+
+ @VisibleForTesting
@NonNull
- private Resources getResourcesForActiveSubId() {
+ protected Resources getResourcesForActiveSubId() {
return SubscriptionManager.getResourcesForSubId(
mResources.getResourcesContext(), mActiveSubId);
}
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 899286b..1f91eca 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -652,6 +652,12 @@
private static final int EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED = 54;
/**
+ * Event to set temporary allow bad wifi within a limited time to override
+ * {@code config_networkAvoidBadWifi}.
+ */
+ private static final int EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL = 55;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -663,6 +669,11 @@
*/
private static final int PROVISIONING_NOTIFICATION_HIDE = 0;
+ /**
+ * The maximum alive time to allow bad wifi configuration for testing.
+ */
+ private static final long MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS = 5 * 60 * 1000L;
+
private static String eventName(int what) {
return sMagicDecoderRing.get(what, Integer.toString(what));
}
@@ -1276,6 +1287,20 @@
public boolean getCellular464XlatEnabled() {
return NetworkProperties.isCellular464XlatEnabled().orElse(true);
}
+
+ /**
+ * @see PendingIntent#intentFilterEquals
+ */
+ public boolean intentFilterEquals(PendingIntent a, PendingIntent b) {
+ return a.intentFilterEquals(b);
+ }
+
+ /**
+ * @see LocationPermissionChecker
+ */
+ public LocationPermissionChecker makeLocationPermissionChecker(Context context) {
+ return new LocationPermissionChecker(context);
+ }
}
public ConnectivityService(Context context) {
@@ -1343,7 +1368,7 @@
mNetd = netd;
mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- mLocationPermissionChecker = new LocationPermissionChecker(mContext);
+ mLocationPermissionChecker = mDeps.makeLocationPermissionChecker(mContext);
// To ensure uid state is synchronized with Network Policy, register for
// NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService
@@ -3934,7 +3959,7 @@
for (Map.Entry<NetworkRequest, NetworkRequestInfo> entry : mNetworkRequests.entrySet()) {
PendingIntent existingPendingIntent = entry.getValue().mPendingIntent;
if (existingPendingIntent != null &&
- existingPendingIntent.intentFilterEquals(pendingIntent)) {
+ mDeps.intentFilterEquals(existingPendingIntent, pendingIntent)) {
return entry.getValue();
}
}
@@ -4328,6 +4353,22 @@
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_AVOID_UNVALIDATED, network));
}
+ @Override
+ public void setTestAllowBadWifiUntil(long timeMs) {
+ enforceSettingsPermission();
+ if (!Build.isDebuggable()) {
+ throw new IllegalStateException("Does not support in non-debuggable build");
+ }
+
+ if (timeMs > System.currentTimeMillis() + MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS) {
+ throw new IllegalArgumentException("It should not exceed "
+ + MAX_TEST_ALLOW_BAD_WIFI_UNTIL_MS + "ms from now");
+ }
+
+ mHandler.sendMessage(
+ mHandler.obtainMessage(EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL, timeMs));
+ }
+
private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
if (DBG) log("handleSetAcceptUnvalidated network=" + network +
" accept=" + accept + " always=" + always);
@@ -4870,6 +4911,10 @@
case EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED:
handleMobileDataPreferredUidsChanged();
break;
+ case EVENT_SET_TEST_ALLOW_BAD_WIFI_UNTIL:
+ final long timeMs = ((Long) msg.obj).longValue();
+ mMultinetworkPolicyTracker.setTestAllowBadWifiUntil(timeMs);
+ break;
}
}
}
diff --git a/tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
similarity index 98%
rename from tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java
rename to tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
index 06e9405..294ed10 100644
--- a/tests/unit/java/android/net/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/common/java/android/net/ConnectivityDiagnosticsManagerTest.java
@@ -36,9 +36,11 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.content.Context;
+import android.os.Build;
import android.os.PersistableBundle;
import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
import org.junit.After;
import org.junit.Before;
@@ -50,6 +52,7 @@
import java.util.concurrent.Executor;
@RunWith(JUnit4.class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
public class ConnectivityDiagnosticsManagerTest {
private static final int NET_ID = 1;
private static final int DETECTION_METHOD = 2;
diff --git a/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt b/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt
new file mode 100644
index 0000000..ebaa787
--- /dev/null
+++ b/tests/common/java/android/net/ConnectivitySettingsManagerTest.kt
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net
+
+import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE
+import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID
+import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_IGNORE
+import android.net.ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT
+import android.net.ConnectivitySettingsManager.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
+import android.net.ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_MOBILE
+import android.net.ConnectivitySettingsManager.DATA_ACTIVITY_TIMEOUT_WIFI
+import android.net.ConnectivitySettingsManager.DNS_RESOLVER_MAX_SAMPLES
+import android.net.ConnectivitySettingsManager.DNS_RESOLVER_MIN_SAMPLES
+import android.net.ConnectivitySettingsManager.DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS
+import android.net.ConnectivitySettingsManager.DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT
+import android.net.ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON
+import android.net.ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT
+import android.net.ConnectivitySettingsManager.NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS
+import android.net.ConnectivitySettingsManager.PRIVATE_DNS_DEFAULT_MODE
+import android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF
+import android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC
+import android.net.ConnectivitySettingsManager.WIFI_ALWAYS_REQUESTED
+import android.net.ConnectivitySettingsManager.getCaptivePortalMode
+import android.net.ConnectivitySettingsManager.getConnectivityKeepPendingIntentDuration
+import android.net.ConnectivitySettingsManager.getDnsResolverSampleRanges
+import android.net.ConnectivitySettingsManager.getDnsResolverSampleValidityDuration
+import android.net.ConnectivitySettingsManager.getDnsResolverSuccessThresholdPercent
+import android.net.ConnectivitySettingsManager.getMobileDataActivityTimeout
+import android.net.ConnectivitySettingsManager.getMobileDataAlwaysOn
+import android.net.ConnectivitySettingsManager.getNetworkSwitchNotificationMaximumDailyCount
+import android.net.ConnectivitySettingsManager.getNetworkSwitchNotificationRateDuration
+import android.net.ConnectivitySettingsManager.getPrivateDnsDefaultMode
+import android.net.ConnectivitySettingsManager.getWifiAlwaysRequested
+import android.net.ConnectivitySettingsManager.getWifiDataActivityTimeout
+import android.net.ConnectivitySettingsManager.setCaptivePortalMode
+import android.net.ConnectivitySettingsManager.setConnectivityKeepPendingIntentDuration
+import android.net.ConnectivitySettingsManager.setDnsResolverSampleRanges
+import android.net.ConnectivitySettingsManager.setDnsResolverSampleValidityDuration
+import android.net.ConnectivitySettingsManager.setDnsResolverSuccessThresholdPercent
+import android.net.ConnectivitySettingsManager.setMobileDataActivityTimeout
+import android.net.ConnectivitySettingsManager.setMobileDataAlwaysOn
+import android.net.ConnectivitySettingsManager.setNetworkSwitchNotificationMaximumDailyCount
+import android.net.ConnectivitySettingsManager.setNetworkSwitchNotificationRateDuration
+import android.net.ConnectivitySettingsManager.setPrivateDnsDefaultMode
+import android.net.ConnectivitySettingsManager.setWifiAlwaysRequested
+import android.net.ConnectivitySettingsManager.setWifiDataActivityTimeout
+import android.os.Build
+import android.platform.test.annotations.AppModeFull
+import android.provider.Settings
+import android.util.Range
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.SmallTest
+import com.android.net.module.util.ConnectivitySettingsUtils.getPrivateDnsModeAsString
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.time.Duration
+import java.util.Objects
+import kotlin.test.assertFailsWith
+
+/**
+ * Tests for [ConnectivitySettingsManager].
+ *
+ * Build, install and run with:
+ * atest android.net.ConnectivitySettingsManagerTest
+ */
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.R)
+@SmallTest
+@AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
+class ConnectivitySettingsManagerTest {
+ private val instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val context = instrumentation.context
+ private val resolver = context.contentResolver
+
+ private val defaultDuration = Duration.ofSeconds(0L)
+ private val testTime1 = 5L
+ private val testTime2 = 10L
+ private val settingsTypeGlobal = "global"
+ private val settingsTypeSecure = "secure"
+
+ /*** Reset setting value or delete setting if the setting was not existed before testing. */
+ private fun resetSettings(names: Array<String>, type: String, values: Array<String?>) {
+ for (i in names.indices) {
+ if (Objects.equals(values[i], null)) {
+ instrumentation.uiAutomation.executeShellCommand(
+ "settings delete $type ${names[i]}")
+ } else {
+ if (settingsTypeSecure.equals(type)) {
+ Settings.Secure.putString(resolver, names[i], values[i])
+ } else {
+ Settings.Global.putString(resolver, names[i], values[i])
+ }
+ }
+ }
+ }
+
+ fun <T> testIntSetting(
+ names: Array<String>,
+ type: String,
+ value1: T,
+ value2: T,
+ getter: () -> T,
+ setter: (value: T) -> Unit,
+ testIntValues: IntArray
+ ) {
+ val originals: Array<String?> = Array(names.size) { i ->
+ if (settingsTypeSecure.equals(type)) {
+ Settings.Secure.getString(resolver, names[i])
+ } else {
+ Settings.Global.getString(resolver, names[i])
+ }
+ }
+
+ try {
+ for (i in names.indices) {
+ if (settingsTypeSecure.equals(type)) {
+ Settings.Secure.putString(resolver, names[i], testIntValues[i].toString())
+ } else {
+ Settings.Global.putString(resolver, names[i], testIntValues[i].toString())
+ }
+ }
+ assertEquals(value1, getter())
+
+ setter(value2)
+ assertEquals(value2, getter())
+ } finally {
+ resetSettings(names, type, originals)
+ }
+ }
+
+ @Test
+ fun testMobileDataActivityTimeout() {
+ testIntSetting(names = arrayOf(DATA_ACTIVITY_TIMEOUT_MOBILE), type = settingsTypeGlobal,
+ value1 = Duration.ofSeconds(testTime1), value2 = Duration.ofSeconds(testTime2),
+ getter = { getMobileDataActivityTimeout(context, defaultDuration) },
+ setter = { setMobileDataActivityTimeout(context, it) },
+ testIntValues = intArrayOf(testTime1.toInt()))
+ }
+
+ @Test
+ fun testWifiDataActivityTimeout() {
+ testIntSetting(names = arrayOf(DATA_ACTIVITY_TIMEOUT_WIFI), type = settingsTypeGlobal,
+ value1 = Duration.ofSeconds(testTime1), value2 = Duration.ofSeconds(testTime2),
+ getter = { getWifiDataActivityTimeout(context, defaultDuration) },
+ setter = { setWifiDataActivityTimeout(context, it) },
+ testIntValues = intArrayOf(testTime1.toInt()))
+ }
+
+ @Test
+ fun testDnsResolverSampleValidityDuration() {
+ testIntSetting(names = arrayOf(DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS),
+ type = settingsTypeGlobal, value1 = Duration.ofSeconds(testTime1),
+ value2 = Duration.ofSeconds(testTime2),
+ getter = { getDnsResolverSampleValidityDuration(context, defaultDuration) },
+ setter = { setDnsResolverSampleValidityDuration(context, it) },
+ testIntValues = intArrayOf(testTime1.toInt()))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setDnsResolverSampleValidityDuration(context, Duration.ofSeconds(-1L)) }
+ }
+
+ @Test
+ fun testDnsResolverSuccessThresholdPercent() {
+ testIntSetting(names = arrayOf(DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT),
+ type = settingsTypeGlobal, value1 = 5, value2 = 10,
+ getter = { getDnsResolverSuccessThresholdPercent(context, 0 /* def */) },
+ setter = { setDnsResolverSuccessThresholdPercent(context, it) },
+ testIntValues = intArrayOf(5))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setDnsResolverSuccessThresholdPercent(context, -1) }
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setDnsResolverSuccessThresholdPercent(context, 120) }
+ }
+
+ @Test
+ fun testDnsResolverSampleRanges() {
+ testIntSetting(names = arrayOf(DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_MAX_SAMPLES),
+ type = settingsTypeGlobal, value1 = Range(1, 63), value2 = Range(2, 62),
+ getter = { getDnsResolverSampleRanges(context) },
+ setter = { setDnsResolverSampleRanges(context, it) },
+ testIntValues = intArrayOf(1, 63))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setDnsResolverSampleRanges(context, Range(-1, 62)) }
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setDnsResolverSampleRanges(context, Range(2, 65)) }
+ }
+
+ @Test
+ fun testNetworkSwitchNotificationMaximumDailyCount() {
+ testIntSetting(names = arrayOf(NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT),
+ type = settingsTypeGlobal, value1 = 5, value2 = 15,
+ getter = { getNetworkSwitchNotificationMaximumDailyCount(context, 0 /* def */) },
+ setter = { setNetworkSwitchNotificationMaximumDailyCount(context, it) },
+ testIntValues = intArrayOf(5))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setNetworkSwitchNotificationMaximumDailyCount(context, -1) }
+ }
+
+ @Test
+ fun testNetworkSwitchNotificationRateDuration() {
+ testIntSetting(names = arrayOf(NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS),
+ type = settingsTypeGlobal, value1 = Duration.ofMillis(testTime1),
+ value2 = Duration.ofMillis(testTime2),
+ getter = { getNetworkSwitchNotificationRateDuration(context, defaultDuration) },
+ setter = { setNetworkSwitchNotificationRateDuration(context, it) },
+ testIntValues = intArrayOf(testTime1.toInt()))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setNetworkSwitchNotificationRateDuration(context, Duration.ofMillis(-1L)) }
+ }
+
+ @Test
+ fun testCaptivePortalMode() {
+ testIntSetting(names = arrayOf(CAPTIVE_PORTAL_MODE), type = settingsTypeGlobal,
+ value1 = CAPTIVE_PORTAL_MODE_AVOID, value2 = CAPTIVE_PORTAL_MODE_PROMPT,
+ getter = { getCaptivePortalMode(context, CAPTIVE_PORTAL_MODE_IGNORE) },
+ setter = { setCaptivePortalMode(context, it) },
+ testIntValues = intArrayOf(CAPTIVE_PORTAL_MODE_AVOID))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setCaptivePortalMode(context, 5 /* mode */) }
+ }
+
+ @Test
+ fun testPrivateDnsDefaultMode() {
+ val original = Settings.Global.getString(resolver, PRIVATE_DNS_DEFAULT_MODE)
+
+ try {
+ val mode = getPrivateDnsModeAsString(PRIVATE_DNS_MODE_OPPORTUNISTIC)
+ Settings.Global.putString(resolver, PRIVATE_DNS_DEFAULT_MODE, mode)
+ assertEquals(mode, getPrivateDnsDefaultMode(context))
+
+ setPrivateDnsDefaultMode(context, PRIVATE_DNS_MODE_OFF)
+ assertEquals(getPrivateDnsModeAsString(PRIVATE_DNS_MODE_OFF),
+ getPrivateDnsDefaultMode(context))
+ } finally {
+ resetSettings(names = arrayOf(PRIVATE_DNS_DEFAULT_MODE), type = settingsTypeGlobal,
+ values = arrayOf(original))
+ }
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setPrivateDnsDefaultMode(context, -1) }
+ }
+
+ @Test
+ fun testConnectivityKeepPendingIntentDuration() {
+ testIntSetting(names = arrayOf(CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS),
+ type = settingsTypeSecure, value1 = Duration.ofMillis(testTime1),
+ value2 = Duration.ofMillis(testTime2),
+ getter = { getConnectivityKeepPendingIntentDuration(context, defaultDuration) },
+ setter = { setConnectivityKeepPendingIntentDuration(context, it) },
+ testIntValues = intArrayOf(testTime1.toInt()))
+
+ assertFailsWith<IllegalArgumentException>("Expect fail but argument accepted.") {
+ setConnectivityKeepPendingIntentDuration(context, Duration.ofMillis(-1L)) }
+ }
+
+ @Test
+ fun testMobileDataAlwaysOn() {
+ testIntSetting(names = arrayOf(MOBILE_DATA_ALWAYS_ON), type = settingsTypeGlobal,
+ value1 = false, value2 = true,
+ getter = { getMobileDataAlwaysOn(context, true /* def */) },
+ setter = { setMobileDataAlwaysOn(context, it) },
+ testIntValues = intArrayOf(0))
+ }
+
+ @Test
+ fun testWifiAlwaysRequested() {
+ testIntSetting(names = arrayOf(WIFI_ALWAYS_REQUESTED), type = settingsTypeGlobal,
+ value1 = false, value2 = true,
+ getter = { getWifiAlwaysRequested(context, true /* def */) },
+ setter = { setWifiAlwaysRequested(context, it) },
+ testIntValues = intArrayOf(0))
+ }
+}
\ No newline at end of file
diff --git a/tests/common/java/android/net/InvalidPacketExceptionTest.kt b/tests/common/java/android/net/InvalidPacketExceptionTest.kt
new file mode 100644
index 0000000..320ac27
--- /dev/null
+++ b/tests/common/java/android/net/InvalidPacketExceptionTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net
+
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import org.junit.runner.RunWith
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
+class InvalidPacketExceptionTest {
+ @Test
+ fun testConstructor() {
+ assertEquals(123, InvalidPacketException(123).error)
+ assertEquals(0, InvalidPacketException(0).error)
+ assertEquals(-123, InvalidPacketException(-123).error)
+ }
+}
\ No newline at end of file
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 5b95eea..5352a60 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
@@ -975,4 +975,15 @@
*/
String getExpected();
}
+
+ protected void setRestrictedNetworkingMode(boolean enabled) throws Exception {
+ executeSilentShellCommand(
+ "settings put global restricted_networking_mode " + (enabled ? 1 : 0));
+ assertRestrictedNetworkingModeState(enabled);
+ }
+
+ protected void assertRestrictedNetworkingModeState(boolean enabled) throws Exception {
+ assertDelayedShellCommand("cmd netpolicy get restricted-mode",
+ "Restricted mode status: " + (enabled ? "enabled" : "disabled"));
+ }
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
new file mode 100644
index 0000000..ddc5fd4
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyManagerTest.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import static android.os.Process.SYSTEM_UID;
+
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.assertNetworkingBlockedStatusForUid;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isUidNetworkingBlocked;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isUidRestrictedOnMeteredNetworks;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class NetworkPolicyManagerTest extends AbstractRestrictBackgroundNetworkTestCase {
+ private static final boolean METERED = true;
+ private static final boolean NON_METERED = false;
+
+ @Rule
+ public final MeterednessConfigurationRule mMeterednessConfiguration =
+ new MeterednessConfigurationRule();
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ assumeTrue(canChangeActiveNetworkMeteredness());
+
+ registerBroadcastReceiver();
+
+ removeRestrictBackgroundWhitelist(mUid);
+ removeRestrictBackgroundBlacklist(mUid);
+ assertRestrictBackgroundChangedReceived(0);
+
+ // Initial state
+ setBatterySaverMode(false);
+ setRestrictBackground(false);
+ setRestrictedNetworkingMode(false);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+
+ setBatterySaverMode(false);
+ setRestrictBackground(false);
+ setRestrictedNetworkingMode(false);
+ unregisterNetworkCallback();
+ }
+
+ @Test
+ public void testIsUidNetworkingBlocked_withUidNotBlocked() throws Exception {
+ // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
+ // test the cases of non-metered network and uid not matched by any rule.
+ // If mUid is not blocked by data saver mode or power saver mode, no matter the network is
+ // metered or non-metered, mUid shouldn't be blocked.
+ assertFalse(isUidNetworkingBlocked(mUid, METERED)); // Match NTWK_ALLOWED_DEFAULT
+ assertFalse(isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
+ }
+
+ @Test
+ public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
+ // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
+ // test the case of uid is system uid.
+ // SYSTEM_UID will never be blocked.
+ assertFalse(isUidNetworkingBlocked(SYSTEM_UID, METERED)); // Match NTWK_ALLOWED_SYSTEM
+ assertFalse(isUidNetworkingBlocked(SYSTEM_UID, NON_METERED)); // Match NTWK_ALLOWED_SYSTEM
+ try {
+ setRestrictBackground(true);
+ setBatterySaverMode(true);
+ setRestrictedNetworkingMode(true);
+ assertNetworkingBlockedStatusForUid(SYSTEM_UID, METERED,
+ false /* expectedResult */); // Match NTWK_ALLOWED_SYSTEM
+ assertFalse(
+ isUidNetworkingBlocked(SYSTEM_UID, NON_METERED)); // Match NTWK_ALLOWED_SYSTEM
+ } finally {
+ setRestrictBackground(false);
+ setBatterySaverMode(false);
+ setRestrictedNetworkingMode(false);
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
+ }
+ }
+
+ @Test
+ public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
+ // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
+ // test the cases of non-metered network, uid is matched by restrict background blacklist,
+ // uid is matched by restrict background whitelist, app is in the foreground with restrict
+ // background enabled and the app is in the background with restrict background enabled.
+ try {
+ // Enable restrict background and mUid will be blocked because it's not in the
+ // foreground.
+ setRestrictBackground(true);
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ true /* expectedResult */); // Match NTWK_BLOCKED_BG_RESTRICT
+
+ // Although restrict background is enabled and mUid is in the background, but mUid will
+ // not be blocked if network is non-metered.
+ assertFalse(
+ isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
+
+ // Add mUid into the restrict background blacklist.
+ addRestrictBackgroundBlacklist(mUid);
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ true /* expectedResult */); // Match NTWK_BLOCKED_DENYLIST
+
+ // Although mUid is in the restrict background blacklist, but mUid won't be blocked if
+ // the network is non-metered.
+ assertFalse(
+ isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
+ removeRestrictBackgroundBlacklist(mUid);
+
+ // Add mUid into the restrict background whitelist.
+ addRestrictBackgroundWhitelist(mUid);
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ false /* expectedResult */); // Match NTWK_ALLOWED_ALLOWLIST
+ assertFalse(
+ isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
+ removeRestrictBackgroundWhitelist(mUid);
+
+ // Make TEST_APP2_PKG go to foreground and mUid will be allowed temporarily.
+ launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
+ assertForegroundState();
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ false /* expectedResult */); // Match NTWK_ALLOWED_TMP_ALLOWLIST
+
+ // Back to background.
+ finishActivity();
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ true /* expectedResult */); // Match NTWK_BLOCKED_BG_RESTRICT
+ } finally {
+ setRestrictBackground(false);
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
+ }
+ }
+
+ @Test
+ public void testIsUidNetworkingBlocked_withRestrictedNetworkingMode() throws Exception {
+ // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
+ // test the cases of restricted networking mode enabled.
+ try {
+ // All apps should be blocked if restricted networking mode is enabled except for those
+ // apps who have CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
+ // This test won't test if an app who has CONNECTIVITY_USE_RESTRICTED_NETWORKS will not
+ // be blocked because CONNECTIVITY_USE_RESTRICTED_NETWORKS is a signature/privileged
+ // permission that CTS cannot acquire. Also it's not good for this test to use those
+ // privileged apps which have CONNECTIVITY_USE_RESTRICTED_NETWORKS to test because there
+ // is no guarantee that those apps won't remove this permission someday, and if it
+ // happens, then this test will fail.
+ setRestrictedNetworkingMode(true);
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ true /* expectedResult */); // Match NTWK_BLOCKED_RESTRICTED_MODE
+ assertTrue(isUidNetworkingBlocked(mUid,
+ NON_METERED)); // Match NTWK_BLOCKED_RESTRICTED_MODE
+ } finally {
+ setRestrictedNetworkingMode(false);
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
+ }
+ }
+
+ @Test
+ public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
+ // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
+ // test the cases of power saver mode enabled, uid in the power saver mode whitelist and
+ // uid in the power saver mode whitelist with non-metered network.
+ try {
+ // mUid should be blocked if power saver mode is enabled.
+ setBatterySaverMode(true);
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ true /* expectedResult */); // Match NTWK_BLOCKED_POWER
+ assertTrue(isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_BLOCKED_POWER
+
+ // Add TEST_APP2_PKG into power saver mode whitelist, its uid rule is RULE_ALLOW_ALL and
+ // it shouldn't be blocked.
+ addPowerSaveModeWhitelist(TEST_APP2_PKG);
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
+ assertFalse(
+ isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
+ removePowerSaveModeWhitelist(TEST_APP2_PKG);
+ } finally {
+ setBatterySaverMode(false);
+ assertNetworkingBlockedStatusForUid(mUid, METERED,
+ false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
+ }
+ }
+
+ @Test
+ public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
+ try {
+ // isUidRestrictedOnMeteredNetworks() will only return true when restrict background is
+ // enabled and mUid is not in the restrict background whitelist and TEST_APP2_PKG is not
+ // in the foreground. For other cases, it will return false.
+ setRestrictBackground(true);
+ assertTrue(isUidRestrictedOnMeteredNetworks(mUid));
+
+ // Make TEST_APP2_PKG go to foreground and isUidRestrictedOnMeteredNetworks() will
+ // return false.
+ launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
+ assertForegroundState();
+ assertFalse(isUidRestrictedOnMeteredNetworks(mUid));
+ // Back to background.
+ finishActivity();
+
+ // Add mUid into restrict background whitelist and isUidRestrictedOnMeteredNetworks()
+ // will return false.
+ addRestrictBackgroundWhitelist(mUid);
+ assertFalse(isUidRestrictedOnMeteredNetworks(mUid));
+ removeRestrictBackgroundWhitelist(mUid);
+ } finally {
+ // Restrict background is disabled and isUidRestrictedOnMeteredNetworks() will return
+ // false.
+ setRestrictBackground(false);
+ assertFalse(isUidRestrictedOnMeteredNetworks(mUid));
+ }
+ }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 7da1a21..4f9ce7c 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -43,6 +43,7 @@
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.NetworkPolicyManager;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiManager.ActionListener;
@@ -58,6 +59,7 @@
import com.android.compatibility.common.util.AppStandbyUtils;
import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.ShellIdentityUtils;
import com.android.compatibility.common.util.ThrowingRunnable;
@@ -81,6 +83,7 @@
private static ConnectivityManager mCm;
private static WifiManager mWm;
private static CarrierConfigManager mCarrierConfigManager;
+ private static NetworkPolicyManager sNpm;
private static Boolean mBatterySaverSupported;
private static Boolean mDataSaverSupported;
@@ -408,6 +411,13 @@
return mCarrierConfigManager;
}
+ public static NetworkPolicyManager getNetworkPolicyManager() {
+ if (sNpm == null) {
+ sNpm = getContext().getSystemService(NetworkPolicyManager.class);
+ }
+ return sNpm;
+ }
+
public static Context getContext() {
return getInstrumentation().getContext();
}
@@ -415,4 +425,33 @@
public static Instrumentation getInstrumentation() {
return InstrumentationRegistry.getInstrumentation();
}
+
+ // When power saver mode or restrict background enabled or adding any white/black list into
+ // those modes, NetworkPolicy may need to take some time to update the rules of uids. So having
+ // this function and using PollingCheck to try to make sure the uid has updated and reduce the
+ // flaky rate.
+ public static void assertNetworkingBlockedStatusForUid(int uid, boolean metered,
+ boolean expectedResult) throws Exception {
+ PollingCheck.waitFor(() -> (expectedResult == isUidNetworkingBlocked(uid, metered)));
+ }
+
+ public static boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork) {
+ final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ return getNetworkPolicyManager().isUidNetworkingBlocked(uid, meteredNetwork);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ public static boolean isUidRestrictedOnMeteredNetworks(int uid) {
+ final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+ try {
+ uiAutomation.adoptShellPermissionIdentity();
+ return getNetworkPolicyManager().isUidRestrictedOnMeteredNetworks(uid);
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
index 29d3c6e..5f0f6d6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
@@ -28,28 +28,17 @@
@After
public void tearDown() throws Exception {
- setRestrictedMode(false);
+ setRestrictedNetworkingMode(false);
super.tearDown();
}
- private void setRestrictedMode(boolean enabled) throws Exception {
- executeSilentShellCommand(
- "settings put global restricted_networking_mode " + (enabled ? 1 : 0));
- assertRestrictedModeState(enabled);
- }
-
- private void assertRestrictedModeState(boolean enabled) throws Exception {
- assertDelayedShellCommand("cmd netpolicy get restricted-mode",
- "Restricted mode status: " + (enabled ? "enabled" : "disabled"));
- }
-
@Test
public void testNetworkAccess() throws Exception {
- setRestrictedMode(false);
+ setRestrictedNetworkingMode(false);
// go to foreground state and enable restricted mode
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- setRestrictedMode(true);
+ setRestrictedNetworkingMode(true);
assertForegroundNetworkAccess(false);
// go to background state
@@ -57,7 +46,7 @@
assertBackgroundNetworkAccess(false);
// disable restricted mode and assert network access in foreground and background states
- setRestrictedMode(false);
+ setRestrictedNetworkingMode(false);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
assertForegroundNetworkAccess(true);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index 8485263..62aa493 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -760,7 +760,7 @@
assertEquals(vpnNetwork, mCM.getActiveNetwork());
assertNotEqual(defaultNetwork, vpnNetwork);
maybeExpectVpnTransportInfo(vpnNetwork);
- assertTrue(mCM.getNetworkInfo(vpnNetwork).getType() == TYPE_VPN);
+ assertEquals(TYPE_VPN, mCM.getNetworkInfo(vpnNetwork).getType());
if (SdkLevel.isAtLeastS()) {
// Check that system default network callback has not seen any network changes, even
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
new file mode 100644
index 0000000..fdb8876
--- /dev/null
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideNetworkPolicyManagerTests.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net;
+
+public class HostsideNetworkPolicyManagerTests extends HostsideNetworkTestCase {
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ uninstallPackage(TEST_APP2_PKG, false);
+ installPackage(TEST_APP2_APK);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ uninstallPackage(TEST_APP2_PKG, true);
+ }
+
+ public void testIsUidNetworkingBlocked_withUidNotBlocked() throws Exception {
+ runDeviceTests(TEST_PKG,
+ TEST_PKG + ".NetworkPolicyManagerTest",
+ "testIsUidNetworkingBlocked_withUidNotBlocked");
+ }
+
+ public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
+ runDeviceTests(TEST_PKG,
+ TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidNetworkingBlocked_withSystemUid");
+ }
+
+ public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
+ runDeviceTests(TEST_PKG,
+ TEST_PKG + ".NetworkPolicyManagerTest",
+ "testIsUidNetworkingBlocked_withDataSaverMode");
+ }
+
+ public void testIsUidNetworkingBlocked_withRestrictedNetworkingMode() throws Exception {
+ runDeviceTests(TEST_PKG,
+ TEST_PKG + ".NetworkPolicyManagerTest",
+ "testIsUidNetworkingBlocked_withRestrictedNetworkingMode");
+ }
+
+ public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
+ runDeviceTests(TEST_PKG,
+ TEST_PKG + ".NetworkPolicyManagerTest",
+ "testIsUidNetworkingBlocked_withPowerSaverMode");
+ }
+
+ public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
+ runDeviceTests(TEST_PKG,
+ TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidRestrictedOnMeteredNetworks");
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
index a889c41..9f079c4 100644
--- a/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
+++ b/tests/cts/net/src/android/net/cts/CaptivePortalTest.kt
@@ -20,6 +20,7 @@
import android.Manifest.permission.NETWORK_SETTINGS
import android.Manifest.permission.READ_DEVICE_CONFIG
import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.content.pm.PackageManager.FEATURE_WATCH
import android.content.pm.PackageManager.FEATURE_WIFI
import android.net.ConnectivityManager
import android.net.ConnectivityManager.NetworkCallback
@@ -57,6 +58,7 @@
import junit.framework.AssertionFailedError
import org.junit.After
import org.junit.Assume.assumeTrue
+import org.junit.Assume.assumeFalse
import org.junit.Before
import org.junit.runner.RunWith
import java.util.concurrent.CompletableFuture
@@ -128,6 +130,7 @@
fun testCaptivePortalIsNotDefaultNetwork() {
assumeTrue(pm.hasSystemFeature(FEATURE_TELEPHONY))
assumeTrue(pm.hasSystemFeature(FEATURE_WIFI))
+ assumeFalse(pm.hasSystemFeature(FEATURE_WATCH))
utils.ensureWifiConnected()
val cellNetwork = utils.connectToCell()
@@ -148,8 +151,8 @@
server.addResponse(Request(TEST_PORTAL_URL_PATH), Status.OK,
content = "Test captive portal content")
server.addResponse(Request(TEST_HTTPS_URL_PATH), Status.INTERNAL_ERROR)
- server.addResponse(Request(TEST_HTTP_URL_PATH), Status.REDIRECT,
- locationHeader = makeUrl(TEST_PORTAL_URL_PATH))
+ val headers = mapOf("Location" to makeUrl(TEST_PORTAL_URL_PATH))
+ server.addResponse(Request(TEST_HTTP_URL_PATH), Status.REDIRECT, headers)
setHttpsUrlDeviceConfig(makeUrl(TEST_HTTPS_URL_PATH))
setHttpUrlDeviceConfig(makeUrl(TEST_HTTP_URL_PATH))
// URL expiration needs to be in the next 10 minutes
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index d649518..4c967b8 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.READ_DEVICE_CONFIG;
import static android.content.pm.PackageManager.FEATURE_BLUETOOTH;
import static android.content.pm.PackageManager.FEATURE_ETHERNET;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
@@ -28,6 +29,8 @@
import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.ConnectivityManager.EXTRA_NETWORK;
+import static android.net.ConnectivityManager.EXTRA_NETWORK_REQUEST;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
import static android.net.ConnectivityManager.TYPE_ETHERNET;
@@ -48,6 +51,8 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI;
@@ -60,6 +65,8 @@
import static android.net.cts.util.CtsTetheringUtils.StartTetheringCallback;
import static android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback;
import static android.net.cts.util.CtsTetheringUtils.isWifiTetheringSupported;
+import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL;
+import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL;
import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
import static android.system.OsConstants.AF_INET;
@@ -69,19 +76,20 @@
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN;
import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE;
import static com.android.testutils.MiscAsserts.assertThrows;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@@ -107,11 +115,15 @@
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
+import android.net.NetworkProvider;
import android.net.NetworkRequest;
+import android.net.NetworkScore;
import android.net.NetworkSpecifier;
import android.net.NetworkStateSnapshot;
import android.net.NetworkUtils;
@@ -122,6 +134,7 @@
import android.net.TestNetworkInterface;
import android.net.TestNetworkManager;
import android.net.TetheringManager;
+import android.net.Uri;
import android.net.cts.util.CtsNetUtils;
import android.net.util.KeepaliveUtils;
import android.net.wifi.WifiManager;
@@ -136,6 +149,7 @@
import android.os.UserHandle;
import android.os.VintfRuntimeInfo;
import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -160,6 +174,7 @@
import com.android.testutils.DevSdkIgnoreRuleKt;
import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.SkipPresubmit;
+import com.android.testutils.TestHttpServer;
import com.android.testutils.TestNetworkTracker;
import com.android.testutils.TestableNetworkCallback;
@@ -192,16 +207,24 @@
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import fi.iki.elonen.NanoHTTPD.Method;
+import fi.iki.elonen.NanoHTTPD.Response.IStatus;
+import fi.iki.elonen.NanoHTTPD.Response.Status;
+
@RunWith(AndroidJUnit4.class)
public class ConnectivityManagerTest {
@Rule
@@ -228,6 +251,9 @@
// Airplane Mode BroadcastReceiver Timeout
private static final long AIRPLANE_MODE_CHANGE_TIMEOUT_MS = 10_000L;
+ // Timeout for applying uids allowed on restricted networks
+ private static final long APPLYING_UIDS_ALLOWED_ON_RESTRICTED_NETWORKS_TIMEOUT_MS = 3_000L;
+
// 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;
@@ -245,6 +271,12 @@
private static final int AIRPLANE_MODE_OFF = 0;
private static final int AIRPLANE_MODE_ON = 1;
+ private static final String TEST_HTTPS_URL_PATH = "/https_path";
+ private static final String TEST_HTTP_URL_PATH = "/http_path";
+ private static final String LOCALHOST_HOSTNAME = "localhost";
+ // Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
+ private static final long WIFI_CONNECT_TIMEOUT_MS = 60_000L;
+
private Context mContext;
private Instrumentation mInstrumentation;
private ConnectivityManager mCm;
@@ -259,6 +291,8 @@
// Used for cleanup purposes.
private final List<Range<Integer>> mVpnRequiredUidRanges = new ArrayList<>();
+ private final TestHttpServer mHttpServer = new TestHttpServer(LOCALHOST_HOSTNAME);
+
@Before
public void setUp() throws Exception {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -667,15 +701,18 @@
private NetworkRequest makeWifiNetworkRequest() {
return new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET)
.build();
}
private NetworkRequest makeCellNetworkRequest() {
return new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NET_CAPABILITY_INTERNET)
.build();
}
+ @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
public void testIsPrivateDnsBroken() throws InterruptedException {
final String invalidPrivateDnsServer = "invalidhostname.example.com";
@@ -826,6 +863,119 @@
}
}
+ private void runIdenticalPendingIntentsRequestTest(boolean useListen) throws Exception {
+ assumeTrue(mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+ // Disconnect before registering callbacks, reconnect later to fire them
+ mCtsNetUtils.ensureWifiDisconnected(null);
+
+ final NetworkRequest firstRequest = makeWifiNetworkRequest();
+ final NetworkRequest secondRequest = new NetworkRequest(firstRequest);
+ // Will match wifi or test, since transports are ORed; but there should only be wifi
+ secondRequest.networkCapabilities.addTransportType(TRANSPORT_TEST);
+
+ PendingIntent firstIntent = null;
+ PendingIntent secondIntent = null;
+ BroadcastReceiver receiver = null;
+
+ // Avoid receiving broadcasts from other runs by appending a timestamp
+ final String broadcastAction = NETWORK_CALLBACK_ACTION + System.currentTimeMillis();
+ try {
+ // TODO: replace with PendingIntent.FLAG_MUTABLE when this code compiles against S+
+ // Intent is mutable to receive EXTRA_NETWORK_REQUEST from ConnectivityService
+ final int pendingIntentFlagMutable = 1 << 25;
+ final String extraBoolKey = "extra_bool";
+ firstIntent = PendingIntent.getBroadcast(mContext,
+ 0 /* requestCode */,
+ new Intent(broadcastAction).putExtra(extraBoolKey, false),
+ PendingIntent.FLAG_UPDATE_CURRENT | pendingIntentFlagMutable);
+
+ if (useListen) {
+ mCm.registerNetworkCallback(firstRequest, firstIntent);
+ } else {
+ mCm.requestNetwork(firstRequest, firstIntent);
+ }
+
+ // Second intent equals the first as per filterEquals (extras don't count), so first
+ // intent will be updated with the new extras
+ secondIntent = PendingIntent.getBroadcast(mContext,
+ 0 /* requestCode */,
+ new Intent(broadcastAction).putExtra(extraBoolKey, true),
+ PendingIntent.FLAG_UPDATE_CURRENT | pendingIntentFlagMutable);
+
+ // Because secondIntent.intentFilterEquals the first, the request should be replaced
+ if (useListen) {
+ mCm.registerNetworkCallback(secondRequest, secondIntent);
+ } else {
+ mCm.requestNetwork(secondRequest, secondIntent);
+ }
+
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(broadcastAction);
+
+ final CompletableFuture<Network> networkFuture = new CompletableFuture<>();
+ final AtomicInteger receivedCount = new AtomicInteger(0);
+ receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final NetworkRequest request = intent.getParcelableExtra(EXTRA_NETWORK_REQUEST);
+ assertPendingIntentRequestMatches(request, secondRequest, useListen);
+ receivedCount.incrementAndGet();
+ networkFuture.complete(intent.getParcelableExtra(EXTRA_NETWORK));
+ }
+ };
+ mContext.registerReceiver(receiver, filter);
+
+ final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
+ try {
+ assertEquals(wifiNetwork, networkFuture.get(
+ NETWORK_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (TimeoutException e) {
+ throw new AssertionError("PendingIntent not received for " + secondRequest, e);
+ }
+
+ // Sleep for a small amount of time to try to check that only one callback is ever
+ // received (so the first callback was really unregistered). This does not guarantee
+ // that the test will fail if it runs very slowly, but it should at least be very
+ // noticeably flaky.
+ Thread.sleep(NO_CALLBACK_TIMEOUT_MS);
+
+ // TODO: BUG (b/189868426): this should also apply to listens
+ if (!useListen) {
+ assertEquals("PendingIntent should only be received once", 1, receivedCount.get());
+ }
+ } finally {
+ if (firstIntent != null) mCm.unregisterNetworkCallback(firstIntent);
+ if (secondIntent != null) mCm.unregisterNetworkCallback(secondIntent);
+ if (receiver != null) mContext.unregisterReceiver(receiver);
+ mCtsNetUtils.ensureWifiConnected();
+ }
+ }
+
+ private void assertPendingIntentRequestMatches(NetworkRequest broadcasted, NetworkRequest filed,
+ boolean useListen) {
+ // TODO: BUG (b/191713869): on S the request extra is null on listens
+ if (isAtLeastS() && useListen && broadcasted == null) return;
+ assertArrayEquals(filed.networkCapabilities.getCapabilities(),
+ broadcasted.networkCapabilities.getCapabilities());
+ // TODO: BUG (b/189868426): this should also apply to listens
+ if (useListen) return;
+ assertArrayEquals(filed.networkCapabilities.getTransportTypes(),
+ broadcasted.networkCapabilities.getTransportTypes());
+ }
+
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
+ public void testRegisterNetworkRequest_identicalPendingIntents() throws Exception {
+ runIdenticalPendingIntentsRequestTest(false /* useListen */);
+ }
+
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
+ public void testRegisterNetworkCallback_identicalPendingIntents() throws Exception {
+ runIdenticalPendingIntentsRequestTest(true /* useListen */);
+ }
+
/**
* Exercises the requestNetwork with NetworkCallback API. This checks to
* see if we get a callback for an INTERNET request.
@@ -2134,6 +2284,24 @@
null /* listener */));
}
+ @Test
+ public void testSystemReady() {
+ assumeTrue(TestUtils.shouldTestSApis());
+ assertThrows(SecurityException.class, () -> mCm.systemReady());
+ }
+
+ @Test
+ public void testGetIpSecNetIdRange() {
+ assumeTrue(TestUtils.shouldTestSApis());
+ // The lower refers to ConnectivityManager.TUN_INTF_NETID_START.
+ final long lower = 64512;
+ // The upper refers to ConnectivityManager.TUN_INTF_NETID_START
+ // + ConnectivityManager.TUN_INTF_NETID_RANGE - 1
+ final long upper = 65535;
+ assertEquals(lower, (long) ConnectivityManager.getIpSecNetIdRange().getLower());
+ assertEquals(upper, (long) ConnectivityManager.getIpSecNetIdRange().getUpper());
+ }
+
private void verifySettings(int expectedAirplaneMode, int expectedPrivateDnsMode,
int expectedAvoidBadWifi) throws Exception {
assertEquals(expectedAirplaneMode, Settings.Global.getInt(
@@ -2162,7 +2330,7 @@
* For specified apps, validate networks are prioritized in order: unmetered, TEST transport,
* default network.
*/
- @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @AppModeFull(reason = "Instant apps cannot create test networks")
@Test
public void testSetOemNetworkPreferenceForTestPref() throws Exception {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
@@ -2222,6 +2390,7 @@
* Verify that per-app OEM network preference functions as expected for network pref TEST_ONLY.
* For specified apps, validate that only TEST transport type networks are used.
*/
+ @AppModeFull(reason = "Instant apps cannot create test networks")
@Test
public void testSetOemNetworkPreferenceForTestOnlyPref() throws Exception {
// Cannot use @IgnoreUpTo(Build.VERSION_CODES.R) because this test also requires API 31
@@ -2327,4 +2496,470 @@
}
oemPrefListener.expectOnComplete();
}
+
+ @Test
+ public void testSetAcceptPartialConnectivity_NoPermission_GetException() {
+ assumeTrue(TestUtils.shouldTestSApis());
+ assertThrows(SecurityException.class, () -> mCm.setAcceptPartialConnectivity(
+ mCm.getActiveNetwork(), false /* accept */ , false /* always */));
+ }
+
+ @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
+ @Test
+ public void testAcceptPartialConnectivity_validatedNetwork() throws Exception {
+ assumeTrue(TestUtils.shouldTestSApis());
+ assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"
+ + " unless device supports WiFi",
+ mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+ try {
+ // Wait for partial connectivity to be detected on the network
+ final Network network = preparePartialConnectivity();
+
+ runAsShell(NETWORK_SETTINGS, () -> {
+ // The always bit is verified in NetworkAgentTest
+ mCm.setAcceptPartialConnectivity(network, true /* accept */, false /* always */);
+ });
+
+ // Accept partial connectivity network should result in a validated network
+ expectNetworkHasCapability(network, NET_CAPABILITY_VALIDATED, WIFI_CONNECT_TIMEOUT_MS);
+ } finally {
+ resetValidationConfig();
+ // Reconnect wifi to reset the wifi status
+ reconnectWifi();
+ }
+ }
+
+ @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
+ @Test
+ public void testRejectPartialConnectivity_TearDownNetwork() throws Exception {
+ assumeTrue(TestUtils.shouldTestSApis());
+ assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"
+ + " unless device supports WiFi",
+ mPackageManager.hasSystemFeature(FEATURE_WIFI));
+
+ final TestNetworkCallback cb = new TestNetworkCallback();
+ try {
+ // Wait for partial connectivity to be detected on the network
+ final Network network = preparePartialConnectivity();
+
+ mCm.requestNetwork(makeWifiNetworkRequest(), cb);
+ runAsShell(NETWORK_SETTINGS, () -> {
+ // The always bit is verified in NetworkAgentTest
+ mCm.setAcceptPartialConnectivity(network, false /* accept */, false /* always */);
+ });
+ // Reject partial connectivity network should cause the network being torn down
+ assertEquals(network, cb.waitForLost());
+ } finally {
+ mCm.unregisterNetworkCallback(cb);
+ resetValidationConfig();
+ // Wifi will not automatically reconnect to the network. ensureWifiDisconnected cannot
+ // apply here. Thus, turn off wifi first and restart to restore.
+ runShellCommand("svc wifi disable");
+ mCtsNetUtils.ensureWifiConnected();
+ }
+ }
+
+ @Test
+ public void testSetAcceptUnvalidated_NoPermission_GetException() {
+ assumeTrue(TestUtils.shouldTestSApis());
+ assertThrows(SecurityException.class, () -> mCm.setAcceptUnvalidated(
+ mCm.getActiveNetwork(), false /* accept */ , false /* always */));
+ }
+
+ @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
+ @Test
+ public void testRejectUnvalidated_TearDownNetwork() throws Exception {
+ assumeTrue(TestUtils.shouldTestSApis());
+ final boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+ && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
+ assumeTrue("testAcceptPartialConnectivity_validatedNetwork cannot execute"
+ + " unless device supports WiFi and telephony", canRunTest);
+
+ final TestableNetworkCallback wifiCb = new TestableNetworkCallback();
+ try {
+ // Ensure at least one default network candidate connected.
+ mCtsNetUtils.connectToCell();
+
+ final Network wifiNetwork = prepareUnvalidatedNetwork();
+ // Default network should not be wifi ,but checking that wifi is not the default doesn't
+ // guarantee that it won't become the default in the future.
+ assertNotEquals(wifiNetwork, mCm.getActiveNetwork());
+
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), wifiCb);
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mCm.setAcceptUnvalidated(wifiNetwork, false /* accept */, false /* always */);
+ });
+ waitForLost(wifiCb);
+ } finally {
+ mCm.unregisterNetworkCallback(wifiCb);
+ resetValidationConfig();
+ /// Wifi will not automatically reconnect to the network. ensureWifiDisconnected cannot
+ // apply here. Thus, turn off wifi first and restart to restore.
+ runShellCommand("svc wifi disable");
+ mCtsNetUtils.ensureWifiConnected();
+ }
+ }
+
+ @AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
+ @Test
+ public void testSetAvoidUnvalidated() throws Exception {
+ assumeTrue(TestUtils.shouldTestSApis());
+ // TODO: Allow in debuggable ROM only. To be replaced by FabricatedOverlay
+ assumeTrue(Build.isDebuggable());
+ final boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+ && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
+ assumeTrue("testSetAvoidUnvalidated cannot execute"
+ + " unless device supports WiFi and telephony", canRunTest);
+
+ final TestableNetworkCallback wifiCb = new TestableNetworkCallback();
+ final TestableNetworkCallback defaultCb = new TestableNetworkCallback();
+ final int previousAvoidBadWifi =
+ ConnectivitySettingsManager.getNetworkAvoidBadWifi(mContext);
+
+ allowBadWifi();
+
+ final Network cellNetwork = mCtsNetUtils.connectToCell();
+ final Network wifiNetwork = prepareValidatedNetwork();
+
+ mCm.registerDefaultNetworkCallback(defaultCb);
+ mCm.registerNetworkCallback(makeWifiNetworkRequest(), wifiCb);
+
+ try {
+ // Verify wifi is the default network.
+ defaultCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> wifiNetwork.equals(entry.getNetwork()));
+ wifiCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> wifiNetwork.equals(entry.getNetwork()));
+ assertTrue(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+
+ // Configure response code for unvalidated network
+ configTestServer(Status.INTERNAL_ERROR, Status.INTERNAL_ERROR);
+ mCm.reportNetworkConnectivity(wifiNetwork, false);
+ // Default network should stay on unvalidated wifi because avoid bad wifi is disabled.
+ defaultCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+ NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> !((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .hasCapability(NET_CAPABILITY_VALIDATED));
+ wifiCb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
+ NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> !((CallbackEntry.CapabilitiesChanged) entry).getCaps()
+ .hasCapability(NET_CAPABILITY_VALIDATED));
+
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mCm.setAvoidUnvalidated(wifiNetwork);
+ });
+ // Default network should be updated to validated cellular network.
+ defaultCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> cellNetwork.equals(entry.getNetwork()));
+ // No update on wifi callback.
+ wifiCb.assertNoCallback();
+ } finally {
+ mCm.unregisterNetworkCallback(wifiCb);
+ mCm.unregisterNetworkCallback(defaultCb);
+ resetAvoidBadWifi(previousAvoidBadWifi);
+ resetValidationConfig();
+ // Reconnect wifi to reset the wifi status
+ reconnectWifi();
+ }
+ }
+
+ private void resetAvoidBadWifi(int settingValue) {
+ setTestAllowBadWifiResource(0 /* timeMs */);
+ ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext, settingValue);
+ }
+
+ private void allowBadWifi() {
+ setTestAllowBadWifiResource(
+ System.currentTimeMillis() + WIFI_CONNECT_TIMEOUT_MS /* timeMs */);
+ ConnectivitySettingsManager.setNetworkAvoidBadWifi(mContext,
+ ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI_IGNORE);
+ }
+
+ private void setTestAllowBadWifiResource(long timeMs) {
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mCm.setTestAllowBadWifiUntil(timeMs);
+ });
+ }
+
+ private Network expectNetworkHasCapability(Network network, int expectedNetCap, long timeout)
+ throws Exception {
+ final CompletableFuture<Network> future = new CompletableFuture();
+ final NetworkCallback cb = new NetworkCallback() {
+ @Override
+ public void onCapabilitiesChanged(Network n, NetworkCapabilities nc) {
+ if (n.equals(network) && nc.hasCapability(expectedNetCap)) {
+ future.complete(network);
+ }
+ }
+ };
+
+ try {
+ mCm.registerNetworkCallback(new NetworkRequest.Builder().build(), cb);
+ return future.get(timeout, TimeUnit.MILLISECONDS);
+ } finally {
+ mCm.unregisterNetworkCallback(cb);
+ }
+ }
+
+ private void resetValidationConfig() {
+ NetworkValidationTestUtil.clearValidationTestUrlsDeviceConfig();
+ mHttpServer.stop();
+ }
+
+ private void prepareHttpServer() throws Exception {
+ runAsShell(READ_DEVICE_CONFIG, () -> {
+ // Verify that the test URLs are not normally set on the device, but do not fail if the
+ // test URLs are set to what this test uses (URLs on localhost), in case the test was
+ // interrupted manually and rerun.
+ assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTPS_URL);
+ assertEmptyOrLocalhostUrl(TEST_CAPTIVE_PORTAL_HTTP_URL);
+ });
+
+ NetworkValidationTestUtil.clearValidationTestUrlsDeviceConfig();
+
+ mHttpServer.start();
+ }
+
+ private Network reconnectWifi() {
+ mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
+ return mCtsNetUtils.ensureWifiConnected();
+ }
+
+ private Network prepareValidatedNetwork() throws Exception {
+ prepareHttpServer();
+ configTestServer(Status.NO_CONTENT, Status.NO_CONTENT);
+ // Disconnect wifi first then start wifi network with configuration.
+ final Network wifiNetwork = reconnectWifi();
+
+ return expectNetworkHasCapability(wifiNetwork, NET_CAPABILITY_VALIDATED,
+ WIFI_CONNECT_TIMEOUT_MS);
+ }
+
+ private Network preparePartialConnectivity() throws Exception {
+ prepareHttpServer();
+ // Configure response code for partial connectivity
+ configTestServer(Status.INTERNAL_ERROR /* httpsStatusCode */,
+ Status.NO_CONTENT /* httpStatusCode */);
+ // Disconnect wifi first then start wifi network with configuration.
+ mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
+ final Network network = mCtsNetUtils.ensureWifiConnected();
+
+ return expectNetworkHasCapability(network, NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+ WIFI_CONNECT_TIMEOUT_MS);
+ }
+
+ private Network prepareUnvalidatedNetwork() throws Exception {
+ prepareHttpServer();
+ // Configure response code for unvalidated network
+ configTestServer(Status.INTERNAL_ERROR /* httpsStatusCode */,
+ Status.INTERNAL_ERROR /* httpStatusCode */);
+
+ // Disconnect wifi first then start wifi network with configuration.
+ mCtsNetUtils.ensureWifiDisconnected(null /* wifiNetworkToCheck */);
+ final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
+ return expectNetworkHasCapability(wifiNetwork, NET_CAPABILITY_INTERNET,
+ WIFI_CONNECT_TIMEOUT_MS);
+ }
+
+ private String makeUrl(String path) {
+ return "http://localhost:" + mHttpServer.getListeningPort() + path;
+ }
+
+ private void assertEmptyOrLocalhostUrl(String urlKey) {
+ final String url = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CONNECTIVITY, urlKey);
+ assertTrue(urlKey + " must not be set in production scenarios, current value= " + url,
+ TextUtils.isEmpty(url) || LOCALHOST_HOSTNAME.equals(Uri.parse(url).getHost()));
+ }
+
+ private void configTestServer(IStatus httpsStatusCode, IStatus httpStatusCode) {
+ mHttpServer.addResponse(new TestHttpServer.Request(
+ TEST_HTTPS_URL_PATH, Method.GET, "" /* queryParameters */),
+ httpsStatusCode, null /* locationHeader */, "" /* content */);
+ mHttpServer.addResponse(new TestHttpServer.Request(
+ TEST_HTTP_URL_PATH, Method.GET, "" /* queryParameters */),
+ httpStatusCode, null /* locationHeader */, "" /* content */);
+ NetworkValidationTestUtil.setHttpsUrlDeviceConfig(makeUrl(TEST_HTTPS_URL_PATH));
+ NetworkValidationTestUtil.setHttpUrlDeviceConfig(makeUrl(TEST_HTTP_URL_PATH));
+ NetworkValidationTestUtil.setUrlExpirationDeviceConfig(
+ System.currentTimeMillis() + WIFI_CONNECT_TIMEOUT_MS);
+ }
+
+ @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @Test
+ public void testMobileDataPreferredUids() throws Exception {
+ assumeTrue(TestUtils.shouldTestSApis());
+ final boolean canRunTest = mPackageManager.hasSystemFeature(FEATURE_WIFI)
+ && mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
+ assumeTrue("testMobileDataPreferredUidsWithCallback cannot execute"
+ + " unless device supports both WiFi and telephony", canRunTest);
+
+ final int uid = mPackageManager.getPackageUid(mContext.getPackageName(), 0 /* flag */);
+ final Set<Integer> mobileDataPreferredUids =
+ ConnectivitySettingsManager.getMobileDataPreferredUids(mContext);
+ // CtsNetTestCases uid should not list in MOBILE_DATA_PREFERRED_UIDS setting because it just
+ // installs to device. In case the uid is existed in setting mistakenly, try to remove the
+ // uid and set correct uids to setting.
+ mobileDataPreferredUids.remove(uid);
+ ConnectivitySettingsManager.setMobileDataPreferredUids(mContext, mobileDataPreferredUids);
+
+ // For testing mobile data preferred uids feature, it needs both wifi and cell network.
+ final Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
+ final Network cellNetwork = mCtsNetUtils.connectToCell();
+ final TestableNetworkCallback defaultTrackingCb = new TestableNetworkCallback();
+ final TestableNetworkCallback systemDefaultCb = new TestableNetworkCallback();
+ final Handler h = new Handler(Looper.getMainLooper());
+ runWithShellPermissionIdentity(() -> mCm.registerSystemDefaultNetworkCallback(
+ systemDefaultCb, h), NETWORK_SETTINGS);
+ mCm.registerDefaultNetworkCallback(defaultTrackingCb);
+
+ try {
+ // CtsNetTestCases uid is not listed in MOBILE_DATA_PREFERRED_UIDS setting, so the
+ // per-app default network should be same as system default network.
+ waitForAvailable(systemDefaultCb, wifiNetwork);
+ defaultTrackingCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> wifiNetwork.equals(entry.getNetwork()));
+ // Active network for CtsNetTestCases uid should be wifi now.
+ assertEquals(wifiNetwork, mCm.getActiveNetwork());
+
+ // Add CtsNetTestCases uid to MOBILE_DATA_PREFERRED_UIDS setting, then available per-app
+ // default network callback should be received with cell network.
+ final Set<Integer> newMobileDataPreferredUids = new ArraySet<>(mobileDataPreferredUids);
+ newMobileDataPreferredUids.add(uid);
+ ConnectivitySettingsManager.setMobileDataPreferredUids(
+ mContext, newMobileDataPreferredUids);
+ defaultTrackingCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> cellNetwork.equals(entry.getNetwork()));
+ // System default network doesn't change.
+ systemDefaultCb.assertNoCallback();
+ // Active network for CtsNetTestCases uid should change to cell, too.
+ assertEquals(cellNetwork, mCm.getActiveNetwork());
+
+ // Remove CtsNetTestCases uid from MOBILE_DATA_PREFERRED_UIDS setting, then available
+ // per-app default network callback should be received again with system default network
+ newMobileDataPreferredUids.remove(uid);
+ ConnectivitySettingsManager.setMobileDataPreferredUids(
+ mContext, newMobileDataPreferredUids);
+ defaultTrackingCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> wifiNetwork.equals(entry.getNetwork()));
+ // System default network still doesn't change.
+ systemDefaultCb.assertNoCallback();
+ // Active network for CtsNetTestCases uid should change back to wifi.
+ assertEquals(wifiNetwork, mCm.getActiveNetwork());
+ } finally {
+ mCm.unregisterNetworkCallback(systemDefaultCb);
+ mCm.unregisterNetworkCallback(defaultTrackingCb);
+
+ // Restore setting.
+ ConnectivitySettingsManager.setMobileDataPreferredUids(
+ mContext, mobileDataPreferredUids);
+ }
+ }
+
+ /** Wait for assigned time. */
+ private void waitForMs(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ fail("Thread was interrupted");
+ }
+ }
+
+ private void assertBindSocketToNetworkSuccess(final Network network) throws Exception {
+ final CompletableFuture<Boolean> future = new CompletableFuture<>();
+ final ExecutorService executor = Executors.newSingleThreadExecutor();
+ try {
+ executor.execute(() -> {
+ for (int i = 0; i < 30; i++) {
+ waitForMs(100);
+
+ try (Socket socket = new Socket()) {
+ network.bindSocket(socket);
+ future.complete(true);
+ return;
+ } catch (IOException e) { }
+ }
+ });
+ assertTrue(future.get(APPLYING_UIDS_ALLOWED_ON_RESTRICTED_NETWORKS_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS));
+ } finally {
+ executor.shutdown();
+ }
+ }
+
+ @AppModeFull(reason = "WRITE_SECURE_SETTINGS permission can't be granted to instant apps")
+ @Test
+ public void testUidsAllowedOnRestrictedNetworks() throws Exception {
+ assumeTrue(TestUtils.shouldTestSApis());
+
+ final int uid = mPackageManager.getPackageUid(mContext.getPackageName(), 0 /* flag */);
+ final Set<Integer> originalUidsAllowedOnRestrictedNetworks =
+ ConnectivitySettingsManager.getUidsAllowedOnRestrictedNetworks(mContext);
+ // CtsNetTestCases uid should not list in UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting
+ // because it has been just installed to device. In case the uid is existed in setting
+ // mistakenly, try to remove the uid and set correct uids to setting.
+ originalUidsAllowedOnRestrictedNetworks.remove(uid);
+ ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(mContext,
+ originalUidsAllowedOnRestrictedNetworks);
+
+ final Handler h = new Handler(Looper.getMainLooper());
+ final TestableNetworkCallback testNetworkCb = new TestableNetworkCallback();
+ mCm.registerBestMatchingNetworkCallback(new NetworkRequest.Builder().clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST).build(), testNetworkCb, h);
+
+ // Create test network agent with restricted network.
+ final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .build();
+ final NetworkScore score = new NetworkScore.Builder()
+ .setExiting(false)
+ .setTransportPrimary(false)
+ .setKeepConnectedReason(NetworkScore.KEEP_CONNECTED_FOR_HANDOVER)
+ .build();
+ final NetworkAgent agent = new NetworkAgent(mContext, Looper.getMainLooper(),
+ TAG, nc, new LinkProperties(), score, new NetworkAgentConfig.Builder().build(),
+ new NetworkProvider(mContext, Looper.getMainLooper(), TAG)) {};
+ runWithShellPermissionIdentity(() -> agent.register(),
+ android.Manifest.permission.MANAGE_TEST_NETWORKS);
+ agent.markConnected();
+
+ final Network network = agent.getNetwork();
+
+ try (Socket socket = new Socket()) {
+ testNetworkCb.eventuallyExpect(CallbackEntry.AVAILABLE, NETWORK_CALLBACK_TIMEOUT_MS,
+ entry -> network.equals(entry.getNetwork()));
+ // Verify that the network is restricted.
+ final NetworkCapabilities testNetworkNc = mCm.getNetworkCapabilities(network);
+ assertNotNull(testNetworkNc);
+ assertFalse(testNetworkNc.hasCapability(
+ NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED));
+ // CtsNetTestCases package doesn't hold CONNECTIVITY_USE_RESTRICTED_NETWORKS, so it
+ // does not allow to bind socket to restricted network.
+ assertThrows(IOException.class, () -> network.bindSocket(socket));
+
+ // Add CtsNetTestCases uid to UIDS_ALLOWED_ON_RESTRICTED_NETWORKS setting, then it can
+ // bind socket to restricted network normally.
+ final Set<Integer> newUidsAllowedOnRestrictedNetworks =
+ new ArraySet<>(originalUidsAllowedOnRestrictedNetworks);
+ newUidsAllowedOnRestrictedNetworks.add(uid);
+ ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(mContext,
+ newUidsAllowedOnRestrictedNetworks);
+ // Wait a while for sending allowed uids on the restricted network to netd.
+ // TODD: Have a significant signal to know the uids has been send to netd.
+ assertBindSocketToNetworkSuccess(network);
+ } finally {
+ mCm.unregisterNetworkCallback(testNetworkCb);
+ agent.unregister();
+
+ // Restore setting.
+ ConnectivitySettingsManager.setUidsAllowedOnRestrictedNetworks(mContext,
+ originalUidsAllowedOnRestrictedNetworks);
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index c505cef..ccc9416 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -29,9 +29,9 @@
import android.net.NattKeepalivePacketData
import android.net.Network
import android.net.NetworkAgent
+import android.net.NetworkAgentConfig
import android.net.NetworkAgent.INVALID_NETWORK
import android.net.NetworkAgent.VALID_NETWORK
-import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
@@ -46,9 +46,17 @@
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkInfo
import android.net.NetworkProvider
+import android.net.NetworkReleasedException
import android.net.NetworkRequest
import android.net.NetworkScore
import android.net.RouteInfo
+import android.net.QosCallback
+import android.net.QosCallbackException
+import android.net.QosCallback.QosCallbackRegistrationException
+import android.net.QosFilter
+import android.net.QosSession
+import android.net.QosSessionAttributes
+import android.net.QosSocketInfo
import android.net.SocketKeepalive
import android.net.Uri
import android.net.VpnManager
@@ -59,12 +67,17 @@
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkDestroyed
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRegisterQosCallback
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnUnregisterQosCallback
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnValidationStatus
+import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnError
+import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionAvailable
+import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionLost
import android.os.Build
import android.os.Handler
import android.os.HandlerThread
@@ -72,6 +85,7 @@
import android.os.Message
import android.os.SystemClock
import android.telephony.TelephonyManager
+import android.telephony.data.EpsBearerQosSessionAttributes
import android.util.DebugUtils.valueToString
import androidx.test.InstrumentationRegistry
import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
@@ -97,9 +111,13 @@
import org.mockito.Mockito.mock
import org.mockito.Mockito.timeout
import org.mockito.Mockito.verify
+import java.net.InetAddress
+import java.net.InetSocketAddress
+import java.net.Socket
import java.time.Duration
import java.util.Arrays
import java.util.UUID
+import java.util.concurrent.Executors
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
@@ -143,7 +161,7 @@
private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
private val REMOTE_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.2")
- private val mCM = realContext.getSystemService(ConnectivityManager::class.java)
+ private val mCM = realContext.getSystemService(ConnectivityManager::class.java)!!
private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
private val mFakeConnectivityService = FakeConnectivityService()
@@ -152,6 +170,7 @@
private val agentsToCleanUp = mutableListOf<NetworkAgent>()
private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
+ private var qosTestSocket: Socket? = null
@Before
fun setUp() {
@@ -163,6 +182,7 @@
fun tearDown() {
agentsToCleanUp.forEach { it.unregister() }
callbacksToCleanUp.forEach { mCM.unregisterNetworkCallback(it) }
+ qosTestSocket?.close()
mHandlerThread.quitSafely()
instrumentation.getUiAutomation().dropShellPermissionIdentity()
}
@@ -228,6 +248,11 @@
data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry()
object OnNetworkCreated : CallbackEntry()
object OnNetworkDestroyed : CallbackEntry()
+ data class OnRegisterQosCallback(
+ val callbackId: Int,
+ val filter: QosFilter
+ ) : CallbackEntry()
+ data class OnUnregisterQosCallback(val callbackId: Int) : CallbackEntry()
}
override fun onBandwidthUpdateRequested() {
@@ -276,6 +301,14 @@
}
}
+ override fun onQosCallbackRegistered(qosCallbackId: Int, filter: QosFilter) {
+ history.add(OnRegisterQosCallback(qosCallbackId, filter))
+ }
+
+ override fun onQosCallbackUnregistered(qosCallbackId: Int) {
+ history.add(OnUnregisterQosCallback(qosCallbackId))
+ }
+
override fun onValidationStatus(status: Int, uri: Uri?) {
history.add(OnValidationStatus(status, uri))
}
@@ -307,6 +340,12 @@
return foundCallback
}
+ inline fun <reified T : CallbackEntry> expectCallback(valid: (T) -> Boolean) {
+ val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+ assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+ assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback")
+ }
+
inline fun <reified T : CallbackEntry> eventuallyExpect() =
history.poll(DEFAULT_TIMEOUT_MS) { it is T }.also {
assertNotNull(it, "Callback ${T::class} not received")
@@ -390,7 +429,7 @@
initialConfig: NetworkAgentConfig? = null,
expectedInitSignalStrengthThresholds: IntArray? = intArrayOf()
): Pair<TestableNetworkAgent, TestableNetworkCallback> {
- val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
+ val callback = TestableNetworkCallback()
// Ensure this NetworkAgent is never unneeded by filing a request with its specifier.
requestNetwork(makeTestNetworkRequest(specifier = specifier), callback)
val agent = createNetworkAgent(context, specifier, initialConfig = initialConfig)
@@ -651,7 +690,7 @@
assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_VPN))
assertTrue(hasAllTransports(vpnNc, defaultNetworkTransports),
"VPN transports ${Arrays.toString(vpnNc.transportTypes)}" +
- " lacking transports from ${Arrays.toString(defaultNetworkTransports)}")
+ " lacking transports from ${Arrays.toString(defaultNetworkTransports)}")
// Check that when no underlying networks are announced the underlying transport disappears.
agent.setUnderlyingNetworks(listOf<Network>())
@@ -934,4 +973,251 @@
// tearDown() will unregister the requests and agents
}
+
+ private class TestableQosCallback : QosCallback() {
+ val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+ sealed class CallbackEntry {
+ data class OnQosSessionAvailable(val sess: QosSession, val attr: QosSessionAttributes)
+ : CallbackEntry()
+ data class OnQosSessionLost(val sess: QosSession)
+ : CallbackEntry()
+ data class OnError(val ex: QosCallbackException)
+ : CallbackEntry()
+ }
+
+ override fun onQosSessionAvailable(sess: QosSession, attr: QosSessionAttributes) {
+ history.add(OnQosSessionAvailable(sess, attr))
+ }
+
+ override fun onQosSessionLost(sess: QosSession) {
+ history.add(OnQosSessionLost(sess))
+ }
+
+ override fun onError(ex: QosCallbackException) {
+ history.add(OnError(ex))
+ }
+
+ inline fun <reified T : CallbackEntry> expectCallback(): T {
+ val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+ assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+ return foundCallback
+ }
+
+ inline fun <reified T : CallbackEntry> expectCallback(valid: (T) -> Boolean) {
+ val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+ assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+ assertTrue(valid(foundCallback), "Unexpected callback : $foundCallback")
+ }
+
+ fun assertNoCallback() {
+ assertNull(history.poll(NO_CALLBACK_TIMEOUT),
+ "Callback received")
+ }
+ }
+
+ private fun setupForQosCallbackTesting(): Pair<TestableNetworkAgent, Socket> {
+ val request = NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(TRANSPORT_TEST)
+ .build()
+
+ val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
+ requestNetwork(request, callback)
+ val (agent, _) = createConnectedNetworkAgent()
+
+ qosTestSocket = assertNotNull(agent.network?.socketFactory?.createSocket()).also {
+ it.bind(InetSocketAddress(InetAddress.getLoopbackAddress(), 0))
+ }
+ return Pair(agent, qosTestSocket!!)
+ }
+
+ @Test
+ fun testQosCallbackRegisterWithUnregister() {
+ val (agent, socket) = setupForQosCallbackTesting()
+
+ val qosCallback = TestableQosCallback()
+ var callbackId = -1
+ Executors.newSingleThreadExecutor().let { executor ->
+ try {
+ val info = QosSocketInfo(agent.network!!, socket)
+ mCM.registerQosCallback(info, executor, qosCallback)
+ callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ assertFailsWith<QosCallbackRegistrationException>(
+ "The same callback cannot be " +
+ "registered more than once without first being unregistered") {
+ mCM.registerQosCallback(info, executor, qosCallback)
+ }
+ } finally {
+ socket.close()
+ mCM.unregisterQosCallback(qosCallback)
+ agent.expectCallback<OnUnregisterQosCallback> { it.callbackId == callbackId }
+ executor.shutdown()
+ }
+ }
+ }
+
+ @Test
+ fun testQosCallbackOnQosSession() {
+ val (agent, socket) = setupForQosCallbackTesting()
+ val qosCallback = TestableQosCallback()
+ Executors.newSingleThreadExecutor().let { executor ->
+ try {
+ val info = QosSocketInfo(agent.network!!, socket)
+ mCM.registerQosCallback(info, executor, qosCallback)
+ val callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ val uniqueSessionId = 4294967397
+ val sessId = 101
+
+ val attributes = createEpsAttributes(5)
+ assertEquals(attributes.qosIdentifier, 5)
+ agent.sendQosSessionAvailable(callbackId, sessId, attributes)
+ qosCallback.expectCallback<OnQosSessionAvailable> {
+ it.sess.sessionId == sessId && it.sess.uniqueId == uniqueSessionId &&
+ it.sess.sessionType == QosSession.TYPE_EPS_BEARER
+ }
+
+ agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER)
+ qosCallback.expectCallback<OnQosSessionLost> {
+ it.sess.sessionId == sessId && it.sess.uniqueId == uniqueSessionId &&
+ it.sess.sessionType == QosSession.TYPE_EPS_BEARER
+ }
+
+ // Make sure that we don't get more qos callbacks
+ mCM.unregisterQosCallback(qosCallback)
+ agent.expectCallback<OnUnregisterQosCallback>()
+
+ agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER)
+ qosCallback.assertNoCallback()
+ } finally {
+ socket.close()
+
+ // safety precaution
+ mCM.unregisterQosCallback(qosCallback)
+
+ executor.shutdown()
+ }
+ }
+ }
+
+ @Test
+ fun testQosCallbackOnError() {
+ val (agent, socket) = setupForQosCallbackTesting()
+ val qosCallback = TestableQosCallback()
+ Executors.newSingleThreadExecutor().let { executor ->
+ try {
+ val info = QosSocketInfo(agent.network!!, socket)
+ mCM.registerQosCallback(info, executor, qosCallback)
+ val callbackId = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ val sessId = 101
+ val attributes = createEpsAttributes()
+
+ // Double check that this is wired up and ready to go
+ agent.sendQosSessionAvailable(callbackId, sessId, attributes)
+ qosCallback.expectCallback<OnQosSessionAvailable>()
+
+ // Check that onError is coming through correctly
+ agent.sendQosCallbackError(callbackId,
+ QosCallbackException.EX_TYPE_FILTER_NOT_SUPPORTED)
+ qosCallback.expectCallback<OnError> {
+ it.ex.cause is UnsupportedOperationException
+ }
+
+ // Ensure that when an error occurs the callback was also unregistered
+ agent.sendQosSessionLost(callbackId, sessId, QosSession.TYPE_EPS_BEARER)
+ qosCallback.assertNoCallback()
+ } finally {
+ socket.close()
+
+ // Make sure that the callback is fully unregistered
+ mCM.unregisterQosCallback(qosCallback)
+
+ executor.shutdown()
+ }
+ }
+ }
+
+ @Test
+ fun testQosCallbackIdsAreMappedCorrectly() {
+ val (agent, socket) = setupForQosCallbackTesting()
+ val qosCallback1 = TestableQosCallback()
+ val qosCallback2 = TestableQosCallback()
+ Executors.newSingleThreadExecutor().let { executor ->
+ try {
+ val info = QosSocketInfo(agent.network!!, socket)
+ mCM.registerQosCallback(info, executor, qosCallback1)
+ val callbackId1 = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ mCM.registerQosCallback(info, executor, qosCallback2)
+ val callbackId2 = agent.expectCallback<OnRegisterQosCallback>().callbackId
+
+ val sessId1 = 101
+ val attributes1 = createEpsAttributes(1)
+
+ // Check #1
+ agent.sendQosSessionAvailable(callbackId1, sessId1, attributes1)
+ qosCallback1.expectCallback<OnQosSessionAvailable>()
+ qosCallback2.assertNoCallback()
+
+ // Check #2
+ val sessId2 = 102
+ val attributes2 = createEpsAttributes(2)
+ agent.sendQosSessionAvailable(callbackId2, sessId2, attributes2)
+ qosCallback1.assertNoCallback()
+ qosCallback2.expectCallback<OnQosSessionAvailable> { sessId2 == it.sess.sessionId }
+ } finally {
+ socket.close()
+
+ // Make sure that the callback is fully unregistered
+ mCM.unregisterQosCallback(qosCallback1)
+ mCM.unregisterQosCallback(qosCallback2)
+
+ executor.shutdown()
+ }
+ }
+ }
+
+ @Test
+ fun testQosCallbackWhenNetworkReleased() {
+ val (agent, socket) = setupForQosCallbackTesting()
+ Executors.newSingleThreadExecutor().let { executor ->
+ try {
+ val qosCallback1 = TestableQosCallback()
+ val qosCallback2 = TestableQosCallback()
+ try {
+ val info = QosSocketInfo(agent.network!!, socket)
+ mCM.registerQosCallback(info, executor, qosCallback1)
+ mCM.registerQosCallback(info, executor, qosCallback2)
+ agent.unregister()
+
+ qosCallback1.expectCallback<OnError> {
+ it.ex.cause is NetworkReleasedException
+ }
+
+ qosCallback2.expectCallback<OnError> {
+ it.ex.cause is NetworkReleasedException
+ }
+ } finally {
+ socket.close()
+ mCM.unregisterQosCallback(qosCallback1)
+ mCM.unregisterQosCallback(qosCallback2)
+ }
+ } finally {
+ socket.close()
+ executor.shutdown()
+ }
+ }
+ }
+
+ private fun createEpsAttributes(qci: Int = 1): EpsBearerQosSessionAttributes {
+ val remoteAddresses = ArrayList<InetSocketAddress>()
+ remoteAddresses.add(InetSocketAddress("2001:db8::123", 80))
+ return EpsBearerQosSessionAttributes(
+ qci, 2, 3, 4, 5,
+ remoteAddresses
+ )
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt b/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
index f6fc75b..dde14ac 100644
--- a/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkValidationTestUtil.kt
@@ -29,7 +29,7 @@
/**
* Clear the test network validation URLs.
*/
- fun clearValidationTestUrlsDeviceConfig() {
+ @JvmStatic fun clearValidationTestUrlsDeviceConfig() {
setHttpsUrlDeviceConfig(null)
setHttpUrlDeviceConfig(null)
setUrlExpirationDeviceConfig(null)
@@ -40,7 +40,7 @@
*
* @see NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL
*/
- fun setHttpsUrlDeviceConfig(url: String?) =
+ @JvmStatic fun setHttpsUrlDeviceConfig(url: String?) =
setConfig(NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL, url)
/**
@@ -48,7 +48,7 @@
*
* @see NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL
*/
- fun setHttpUrlDeviceConfig(url: String?) =
+ @JvmStatic fun setHttpUrlDeviceConfig(url: String?) =
setConfig(NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL, url)
/**
@@ -56,7 +56,7 @@
*
* @see NetworkStackUtils.TEST_URL_EXPIRATION_TIME
*/
- fun setUrlExpirationDeviceConfig(timestamp: Long?) =
+ @JvmStatic fun setUrlExpirationDeviceConfig(timestamp: Long?) =
setConfig(NetworkStackUtils.TEST_URL_EXPIRATION_TIME, timestamp?.toString())
private fun setConfig(configKey: String, value: String?) {
diff --git a/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
new file mode 100644
index 0000000..7d5e9ff
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/PacProxyManagerTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+
+import static com.android.testutils.TestPermissionUtil.runAsShell;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.PacProxyManager;
+import android.net.Proxy;
+import android.net.ProxyInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.util.Log;
+import android.util.Range;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+import com.android.testutils.DevSdkIgnoreRunner;
+import com.android.testutils.TestHttpServer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.ServerSocket;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import fi.iki.elonen.NanoHTTPD.Response.Status;
+
+@IgnoreUpTo(Build.VERSION_CODES.R)
+@RunWith(DevSdkIgnoreRunner.class)
+public final class PacProxyManagerTest {
+ private static final String TAG = PacProxyManagerTest.class.getSimpleName();
+ private static final int PROXY_CHANGED_BROADCAST_TIMEOUT_MS = 5000;
+ private static final int CALLBACK_TIMEOUT_MS = 3 * 1000;
+
+ private Context mContext;
+ private TestHttpServer mServer;
+ private ConnectivityManager mCm;
+ private PacProxyManager mPacProxyManager;
+ private ServerSocket mServerSocket;
+ private Instrumentation mInstrumentation;
+
+ private static final String PAC_FILE = "function FindProxyForURL(url, host)"
+ + "{"
+ + " return \"PROXY 192.168.0.1:9091\";"
+ + "}";
+
+ @Before
+ public void setUp() throws Exception {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getContext();
+
+ mCm = mContext.getSystemService(ConnectivityManager.class);
+ mPacProxyManager = (PacProxyManager) mContext.getSystemService(PacProxyManager.class);
+ mServer = new TestHttpServer();
+ mServer.start();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mServer != null) {
+ mServer.stop();
+ mServer = null;
+ }
+ }
+
+ private class TestPacProxyInstalledListener implements
+ PacProxyManager.PacProxyInstalledListener {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+
+ public void onPacProxyInstalled(Network network, ProxyInfo proxy) {
+ Log.e(TAG, "onPacProxyInstalled is called.");
+ mLatch.countDown();
+ }
+
+ public boolean waitForCallback() throws Exception {
+ final boolean result = mLatch.await(CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ return result;
+ }
+ }
+
+ private class ProxyBroadcastReceiver extends BroadcastReceiver {
+ private final CountDownLatch mLatch = new CountDownLatch(1);
+ private final ProxyInfo mProxy;
+
+ ProxyBroadcastReceiver(ProxyInfo proxy) {
+ mProxy = proxy;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final ProxyInfo proxy = (ProxyInfo) intent.getExtra(Proxy.EXTRA_PROXY_INFO,
+ ProxyInfo.buildPacProxy(Uri.EMPTY));
+ // ProxyTracker sends sticky broadcast which will receive the last broadcast while
+ // register the intent receiver. That is, if system never receives the intent then
+ // it won't receive an intent when register the receiver. How many intents will be
+ // received in the test is unpredictable so here counts down the latch when the PAC
+ // file in the intent is the same as the one at registration.
+ if (mProxy.getPacFileUrl().equals(proxy.getPacFileUrl())) {
+ // Host/Port represent a local proxy server that redirects to the PAC-configured
+ // server. Host should be "localhost" and the port should be a value which is
+ // between 0 and 65535.
+ assertEquals(proxy.getHost(), "localhost");
+ assertInRange(proxy.getPort(), 0 /* lower */, 65535 /* upper */);
+ mLatch.countDown();
+ }
+ }
+
+ public boolean waitForProxyChanged() throws Exception {
+ final boolean result = mLatch.await(PROXY_CHANGED_BROADCAST_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ return result;
+ }
+ }
+
+ @Test
+ public void testSetCurrentProxyScriptUrl() throws Exception {
+ // Register a PacProxyInstalledListener
+ final TestPacProxyInstalledListener listener = new TestPacProxyInstalledListener();
+ final Executor executor = (Runnable r) -> r.run();
+
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mPacProxyManager.addPacProxyInstalledListener(executor, listener);
+ });
+
+ final Map<String, String> headers = new HashMap<String, String>();
+ headers.put("Content-Type", "application/x-ns-proxy-autoconfig");
+ final Uri pacProxyUrl = Uri.parse("http://localhost:"
+ + mServer.getListeningPort() + "/proxy.pac");
+ mServer.addResponse(pacProxyUrl, Status.OK, headers, PAC_FILE);
+
+ final ProxyInfo proxy = ProxyInfo.buildPacProxy(pacProxyUrl);
+ final ProxyBroadcastReceiver receiver = new ProxyBroadcastReceiver(proxy);
+ mContext.registerReceiver(receiver, new IntentFilter(Proxy.PROXY_CHANGE_ACTION));
+
+ // Call setCurrentProxyScriptUrl with the URL of the pac file.
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mPacProxyManager.setCurrentProxyScriptUrl(proxy);
+ });
+
+ // Make sure the listener was called and testing the intent is received.
+ try {
+ assertTrue("Didn't receive PROXY_CHANGE_ACTION broadcast.",
+ receiver.waitForProxyChanged());
+ assertTrue("Did not receive onPacProxyInstalled callback.",
+ listener.waitForCallback());
+ } finally {
+ runAsShell(NETWORK_SETTINGS, () -> {
+ mPacProxyManager.removePacProxyInstalledListener(listener);
+ });
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ private void assertInRange(int value, int lower, int upper) {
+ final Range range = new Range(lower, upper);
+ assertTrue(value + "is not within range [" + lower + ", " + upper + "]",
+ range.contains(value));
+ }
+}
diff --git a/tests/cts/net/src/android/net/cts/ProxyTest.java b/tests/cts/net/src/android/net/cts/ProxyTest.java
deleted file mode 100644
index 467d12f..0000000
--- a/tests/cts/net/src/android/net/cts/ProxyTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.cts;
-
-
-import android.net.Proxy;
-import android.test.AndroidTestCase;
-
-public class ProxyTest extends AndroidTestCase {
-
- public void testConstructor() {
- new Proxy();
- }
-
- public void testAccessProperties() {
- final int minValidPort = 0;
- final int maxValidPort = 65535;
- int defaultPort = Proxy.getDefaultPort();
- if(null == Proxy.getDefaultHost()) {
- assertEquals(-1, defaultPort);
- } else {
- assertTrue(defaultPort >= minValidPort && defaultPort <= maxValidPort);
- }
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/ProxyTest.kt b/tests/cts/net/src/android/net/cts/ProxyTest.kt
new file mode 100644
index 0000000..a661b26
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/ProxyTest.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.cts
+
+import android.net.ConnectivityManager
+import android.net.Proxy
+import android.net.ProxyInfo
+import android.net.Uri
+import android.os.Build
+import android.text.TextUtils
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ProxyTest {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
+ @Test
+ fun testConstructor() {
+ Proxy()
+ }
+
+ @Test
+ fun testAccessProperties() {
+ val minValidPort = 0
+ val maxValidPort = 65535
+ val defaultPort = Proxy.getDefaultPort()
+ if (null == Proxy.getDefaultHost()) {
+ assertEquals(-1, defaultPort.toLong())
+ } else {
+ Assert.assertTrue(defaultPort in minValidPort..maxValidPort)
+ }
+ }
+
+ private fun verifyProxySystemProperties(info: ProxyInfo) {
+ assertEquals(info.host, System.getProperty("http.proxyHost"))
+ assertEquals(info.host, System.getProperty("https.proxyHost"))
+
+ assertEquals(info.port.toString(), System.getProperty("http.proxyPort"))
+ assertEquals(info.port.toString(), System.getProperty("https.proxyPort"))
+
+ val strExcludes = if (info.exclusionList.isEmpty()) null
+ else TextUtils.join("|", info.exclusionList)
+ assertEquals(strExcludes, System.getProperty("https.nonProxyHosts"))
+ assertEquals(strExcludes, System.getProperty("http.nonProxyHosts"))
+ }
+
+ private fun getDefaultProxy(): ProxyInfo? {
+ return InstrumentationRegistry.getInstrumentation().context
+ .getSystemService(ConnectivityManager::class.java)
+ .getDefaultProxy()
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R) // setHttpProxyConfiguration was added in S
+ fun testSetHttpProxyConfiguration_DirectProxy() {
+ val info = ProxyInfo.buildDirectProxy(
+ "testproxy.android.com",
+ 12345 /* port */,
+ listOf("testexclude1.android.com", "testexclude2.android.com"))
+ val original = getDefaultProxy()
+ try {
+ Proxy.setHttpProxyConfiguration(info)
+ verifyProxySystemProperties(info)
+ } finally {
+ Proxy.setHttpProxyConfiguration(original)
+ }
+ }
+
+ @Test @IgnoreUpTo(Build.VERSION_CODES.R) // setHttpProxyConfiguration was added in S
+ fun testSetHttpProxyConfiguration_PacProxy() {
+ val pacInfo = ProxyInfo.buildPacProxy(Uri.parse("http://testpac.android.com/pac.pac"))
+ val original = getDefaultProxy()
+ try {
+ Proxy.setHttpProxyConfiguration(pacInfo)
+ verifyProxySystemProperties(pacInfo)
+ } finally {
+ Proxy.setHttpProxyConfiguration(original)
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/cts/net/util/Android.bp b/tests/cts/net/util/Android.bp
index b5f1208..fffd30f 100644
--- a/tests/cts/net/util/Android.bp
+++ b/tests/cts/net/util/Android.bp
@@ -27,5 +27,6 @@
"junit",
"net-tests-utils",
"modules-utils-build",
+ "net-utils-framework-common",
],
}
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
index b32218b..bce9880 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsNetUtils.java
@@ -56,12 +56,13 @@
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
-import android.provider.Settings;
import android.system.Os;
import android.system.OsConstants;
+import android.text.TextUtils;
import android.util.Log;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.net.module.util.ConnectivitySettingsUtils;
import junit.framework.AssertionFailedError;
@@ -80,7 +81,6 @@
public final class CtsNetUtils {
private static final String TAG = CtsNetUtils.class.getSimpleName();
- private static final int DURATION = 10000;
private static final int SOCKET_TIMEOUT_MS = 2000;
private static final int PRIVATE_DNS_PROBE_MS = 1_000;
@@ -104,7 +104,7 @@
private final ContentResolver mCR;
private final WifiManager mWifiManager;
private TestNetworkCallback mCellNetworkCallback;
- private String mOldPrivateDnsMode;
+ private int mOldPrivateDnsMode = 0;
private String mOldPrivateDnsSpecifier;
public CtsNetUtils(Context context) {
@@ -508,62 +508,69 @@
}
public void storePrivateDnsSetting() {
- // Store private DNS setting
- mOldPrivateDnsMode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
- mOldPrivateDnsSpecifier = Settings.Global.getString(mCR,
- Settings.Global.PRIVATE_DNS_SPECIFIER);
- // It's possible that there is no private DNS default value in Settings.
- // Give it a proper default mode which is opportunistic mode.
- if (mOldPrivateDnsMode == null) {
- mOldPrivateDnsSpecifier = "";
- mOldPrivateDnsMode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
- Settings.Global.putString(mCR,
- Settings.Global.PRIVATE_DNS_SPECIFIER, mOldPrivateDnsSpecifier);
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldPrivateDnsMode);
- }
+ mOldPrivateDnsMode = ConnectivitySettingsUtils.getPrivateDnsMode(mContext);
+ mOldPrivateDnsSpecifier = ConnectivitySettingsUtils.getPrivateDnsHostname(mContext);
}
public void restorePrivateDnsSetting() throws InterruptedException {
- if (mOldPrivateDnsMode == null) {
+ if (mOldPrivateDnsMode == 0) {
fail("restorePrivateDnsSetting without storing settings first");
}
- // restore private DNS setting
- if (PRIVATE_DNS_MODE_STRICT.equals(mOldPrivateDnsMode)) {
- setPrivateDnsStrictMode(mOldPrivateDnsSpecifier);
- // In case of invalid setting, still restore it but fail the test
- if (mOldPrivateDnsSpecifier == null) {
- fail("Invalid private DNS setting: no hostname specified in strict mode");
- }
- awaitPrivateDnsSetting("restorePrivateDnsSetting timeout",
- mCm.getActiveNetwork(),
- mOldPrivateDnsSpecifier, true);
- } else {
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE, mOldPrivateDnsMode);
+ if (mOldPrivateDnsMode != ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) {
+ ConnectivitySettingsUtils.setPrivateDnsMode(mContext, mOldPrivateDnsMode);
+ return;
}
+ // restore private DNS setting
+ // In case of invalid setting, set to opportunistic to avoid a bad state and fail
+ if (TextUtils.isEmpty(mOldPrivateDnsSpecifier)) {
+ ConnectivitySettingsUtils.setPrivateDnsMode(mContext,
+ ConnectivitySettingsUtils.PRIVATE_DNS_MODE_OPPORTUNISTIC);
+ fail("Invalid private DNS setting: no hostname specified in strict mode");
+ }
+ setPrivateDnsStrictMode(mOldPrivateDnsSpecifier);
+
+ // There might be a race before private DNS setting is applied and the next test is
+ // running. So waiting private DNS to be validated can reduce the flaky rate of test.
+ awaitPrivateDnsSetting("restorePrivateDnsSetting timeout",
+ mCm.getActiveNetwork(),
+ mOldPrivateDnsSpecifier, true /* requiresValidatedServer */);
}
public void setPrivateDnsStrictMode(String server) {
// To reduce flake rate, set PRIVATE_DNS_SPECIFIER before PRIVATE_DNS_MODE. This ensures
// that if the previous private DNS mode was not strict, the system only sees one
// EVENT_PRIVATE_DNS_SETTINGS_CHANGED event instead of two.
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_SPECIFIER, server);
- final String mode = Settings.Global.getString(mCR, Settings.Global.PRIVATE_DNS_MODE);
+ ConnectivitySettingsUtils.setPrivateDnsHostname(mContext, server);
+ final int mode = ConnectivitySettingsUtils.getPrivateDnsMode(mContext);
// If current private DNS mode is strict, we only need to set PRIVATE_DNS_SPECIFIER.
- if (!PRIVATE_DNS_MODE_STRICT.equals(mode)) {
- Settings.Global.putString(mCR, Settings.Global.PRIVATE_DNS_MODE,
- PRIVATE_DNS_MODE_STRICT);
+ if (mode != ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME) {
+ ConnectivitySettingsUtils.setPrivateDnsMode(mContext,
+ ConnectivitySettingsUtils.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME);
}
}
+ /**
+ * Waiting for the new private DNS setting to be validated.
+ * This method is helpful when the new private DNS setting is configured and ensure the new
+ * setting is applied and workable. It can also reduce the flaky rate when the next test is
+ * running.
+ *
+ * @param msg A message that will be printed when the validation of private DNS is timeout.
+ * @param network A network which will apply the new private DNS setting.
+ * @param server The hostname of private DNS.
+ * @param requiresValidatedServer A boolean to decide if it's needed to wait private DNS to be
+ * validated or not.
+ * @throws InterruptedException If the thread is interrupted.
+ */
public void awaitPrivateDnsSetting(@NonNull String msg, @NonNull Network network,
- @NonNull String server, boolean requiresValidatedServers) throws InterruptedException {
- CountDownLatch latch = new CountDownLatch(1);
- NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
+ @NonNull String server, boolean requiresValidatedServer) throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
NetworkCallback callback = new NetworkCallback() {
@Override
public void onLinkPropertiesChanged(Network n, LinkProperties lp) {
- if (requiresValidatedServers && lp.getValidatedPrivateDnsServers().isEmpty()) {
+ if (requiresValidatedServer && lp.getValidatedPrivateDnsServers().isEmpty()) {
return;
}
if (network.equals(n) && server.equals(lp.getPrivateDnsServerName())) {
@@ -583,7 +590,7 @@
// private DNS probe. There is no way to know when the probe has completed: because the
// network is likely already validated, there is no callback that we can listen to, so
// just sleep.
- if (requiresValidatedServers) {
+ if (requiresValidatedServer) {
Thread.sleep(PRIVATE_DNS_PROBE_MS);
}
}
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp
index beae0cf..ba54273 100644
--- a/tests/unit/Android.bp
+++ b/tests/unit/Android.bp
@@ -60,7 +60,6 @@
"java/**/*.kt",
],
test_suites: ["device-tests"],
- certificate: "platform",
jarjar_rules: "jarjar-rules.txt",
static_libs: [
"androidx.test.rules",
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index be7239d..f6ea964 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -18,10 +18,15 @@
import static android.Manifest.permission.CHANGE_NETWORK_STATE;
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE;
+import static android.Manifest.permission.CREATE_USERS;
import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.GET_INTENT_SENDER_INTENT;
import static android.Manifest.permission.LOCAL_MAC_ADDRESS;
import static android.Manifest.permission.NETWORK_FACTORY;
import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.Manifest.permission.NETWORK_STACK;
+import static android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.content.Intent.ACTION_PACKAGE_ADDED;
import static android.content.Intent.ACTION_PACKAGE_REMOVED;
@@ -134,6 +139,7 @@
import static com.android.testutils.MiscAsserts.assertRunsInAtMost;
import static com.android.testutils.MiscAsserts.assertSameElements;
import static com.android.testutils.MiscAsserts.assertThrows;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -259,6 +265,7 @@
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.os.BadParcelableException;
+import android.os.BatteryStatsManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -297,6 +304,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.connectivity.resources.R;
+import com.android.internal.app.IBatteryStats;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.internal.util.ArrayUtils;
@@ -305,6 +313,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.CollectionUtils;
+import com.android.net.module.util.LocationPermissionChecker;
import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
import com.android.server.ConnectivityService.NetworkRequestInfo;
import com.android.server.connectivity.MockableSystemProperties;
@@ -488,6 +497,11 @@
@Mock Resources mResources;
@Mock ProxyTracker mProxyTracker;
+ // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
+ // underlying binder calls.
+ final BatteryStatsManager mBatteryStatsManager =
+ new BatteryStatsManager(mock(IBatteryStats.class));
+
private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -579,6 +593,7 @@
if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager;
if (Context.SYSTEM_CONFIG_SERVICE.equals(name)) return mSystemConfigManager;
if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager;
+ if (Context.BATTERY_STATS_SERVICE.equals(name)) return mBatteryStatsManager;
return super.getSystemService(name);
}
@@ -659,6 +674,15 @@
public void setPermission(String permission, Integer granted) {
mMockedPermissions.put(permission, granted);
}
+
+ @Override
+ public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
+ @NonNull IntentFilter filter, @Nullable String broadcastPermission,
+ @Nullable Handler scheduler) {
+ // TODO: ensure MultinetworkPolicyTracker's BroadcastReceiver is tested; just returning
+ // null should not pass the test
+ return null;
+ }
}
private void waitForIdle() {
@@ -1208,7 +1232,24 @@
return mDeviceIdleInternal;
}
},
- mNetworkManagementService, mMockNetd, userId, mVpnProfileStore);
+ mNetworkManagementService, mMockNetd, userId, mVpnProfileStore,
+ new SystemServices(mServiceContext) {
+ @Override
+ public String settingsSecureGetStringForUser(String key, int userId) {
+ switch (key) {
+ // Settings keys not marked as @Readable are not readable from
+ // non-privileged apps, unless marked as testOnly=true
+ // (atest refuses to install testOnly=true apps), even if mocked
+ // in the content provider, because
+ // Settings.Secure.NameValueCache#getStringForUser checks the key
+ // before querying the mock settings provider.
+ case Settings.Secure.ALWAYS_ON_VPN_APP:
+ return null;
+ default:
+ return super.settingsSecureGetStringForUser(key, userId);
+ }
+ }
+ }, new Ikev2SessionCreator());
}
public void setUids(Set<UidRange> uids) {
@@ -1455,8 +1496,7 @@
return mService.getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd;
}
- private static class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
- volatile boolean mConfigRestrictsAvoidBadWifi;
+ private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker {
volatile int mConfigMeteredMultipathPreference;
WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) {
@@ -1464,8 +1504,8 @@
}
@Override
- public boolean configRestrictsAvoidBadWifi() {
- return mConfigRestrictsAvoidBadWifi;
+ protected Resources getResourcesForActiveSubId() {
+ return mResources;
}
@Override
@@ -1592,6 +1632,11 @@
mServiceContext = new MockContext(InstrumentationRegistry.getContext(),
new FakeSettingsProvider());
mServiceContext.setUseRegisteredHandlers(true);
+ mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED);
+ mServiceContext.setPermission(CONTROL_OEM_PAID_NETWORK_PREFERENCE, PERMISSION_GRANTED);
+ mServiceContext.setPermission(PACKET_KEEPALIVE_OFFLOAD, PERMISSION_GRANTED);
+ mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_GRANTED);
mAlarmManagerThread = new HandlerThread("TestAlarmManager");
mAlarmManagerThread.start();
@@ -1651,6 +1696,13 @@
return mPolicyTracker;
}).when(deps).makeMultinetworkPolicyTracker(any(), any(), any());
doReturn(true).when(deps).getCellular464XlatEnabled();
+ doAnswer(inv ->
+ new LocationPermissionChecker(inv.getArgument(0)) {
+ @Override
+ protected int getCurrentUser() {
+ return runAsShell(CREATE_USERS, () -> super.getCurrentUser());
+ }
+ }).when(deps).makeLocationPermissionChecker(any());
doReturn(60000).when(mResources).getInteger(R.integer.config_networkTransitionTimeout);
doReturn("").when(mResources).getString(R.string.config_networkCaptivePortalServerUrl);
@@ -1670,7 +1722,9 @@
.getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any());
doReturn(R.array.network_switch_type_name).when(mResources)
.getIdentifier(eq("network_switch_type_name"), eq("array"), any());
-
+ doReturn(R.integer.config_networkAvoidBadWifi).when(mResources)
+ .getIdentifier(eq("config_networkAvoidBadWifi"), eq("integer"), any());
+ doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
final ConnectivityResources connRes = mock(ConnectivityResources.class);
doReturn(mResources).when(connRes).get();
@@ -1680,6 +1734,12 @@
doReturn(mResources).when(mockResContext).getResources();
ConnectivityResources.setResourcesContextForTest(mockResContext);
+ doAnswer(inv -> {
+ final PendingIntent a = inv.getArgument(0);
+ final PendingIntent b = inv.getArgument(1);
+ return runAsShell(GET_INTENT_SENDER_INTENT, () -> a.intentFilterEquals(b));
+ }).when(deps).intentFilterEquals(any(), any());
+
return deps;
}
@@ -4704,30 +4764,29 @@
}
@Test
- public void testAvoidBadWifiSetting() throws Exception {
+ public void testSetAllowBadWifiUntil() throws Exception {
+ runAsShell(NETWORK_SETTINGS,
+ () -> mService.setTestAllowBadWifiUntil(System.currentTimeMillis() + 5_000L));
+ waitForIdle();
+ testAvoidBadWifiConfig_controlledBySettings();
+
+ runAsShell(NETWORK_SETTINGS,
+ () -> mService.setTestAllowBadWifiUntil(System.currentTimeMillis() - 5_000L));
+ waitForIdle();
+ testAvoidBadWifiConfig_ignoreSettings();
+ }
+
+ private void testAvoidBadWifiConfig_controlledBySettings() {
final ContentResolver cr = mServiceContext.getContentResolver();
final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
- mPolicyTracker.mConfigRestrictsAvoidBadWifi = false;
- String[] values = new String[] {null, "0", "1"};
- for (int i = 0; i < values.length; i++) {
- Settings.Global.putInt(cr, settingName, 1);
- mPolicyTracker.reevaluate();
- waitForIdle();
- String msg = String.format("config=false, setting=%s", values[i]);
- assertTrue(mService.avoidBadWifi());
- assertFalse(msg, mPolicyTracker.shouldNotifyWifiUnvalidated());
- }
-
- mPolicyTracker.mConfigRestrictsAvoidBadWifi = true;
-
- Settings.Global.putInt(cr, settingName, 0);
+ Settings.Global.putString(cr, settingName, "0");
mPolicyTracker.reevaluate();
waitForIdle();
assertFalse(mService.avoidBadWifi());
assertFalse(mPolicyTracker.shouldNotifyWifiUnvalidated());
- Settings.Global.putInt(cr, settingName, 1);
+ Settings.Global.putString(cr, settingName, "1");
mPolicyTracker.reevaluate();
waitForIdle();
assertTrue(mService.avoidBadWifi());
@@ -4740,13 +4799,40 @@
assertTrue(mPolicyTracker.shouldNotifyWifiUnvalidated());
}
+ private void testAvoidBadWifiConfig_ignoreSettings() {
+ final ContentResolver cr = mServiceContext.getContentResolver();
+ final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
+
+ String[] values = new String[] {null, "0", "1"};
+ for (int i = 0; i < values.length; i++) {
+ Settings.Global.putString(cr, settingName, values[i]);
+ mPolicyTracker.reevaluate();
+ waitForIdle();
+ String msg = String.format("config=false, setting=%s", values[i]);
+ assertTrue(mService.avoidBadWifi());
+ assertFalse(msg, mPolicyTracker.shouldNotifyWifiUnvalidated());
+ }
+ }
+
+ @Test
+ public void testAvoidBadWifiSetting() throws Exception {
+ final ContentResolver cr = mServiceContext.getContentResolver();
+ final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI;
+
+ doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
+ testAvoidBadWifiConfig_ignoreSettings();
+
+ doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
+ testAvoidBadWifiConfig_controlledBySettings();
+ }
+
@Ignore("Refactoring in progress b/178071397")
@Test
public void testAvoidBadWifi() throws Exception {
final ContentResolver cr = mServiceContext.getContentResolver();
// Pretend we're on a carrier that restricts switching away from bad wifi.
- mPolicyTracker.mConfigRestrictsAvoidBadWifi = true;
+ doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
// File a request for cell to ensure it doesn't go down.
final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
@@ -4797,13 +4883,13 @@
// Simulate switching to a carrier that does not restrict avoiding bad wifi, and expect
// that we switch back to cell.
- mPolicyTracker.mConfigRestrictsAvoidBadWifi = false;
+ doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
mPolicyTracker.reevaluate();
defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
assertEquals(mCm.getActiveNetwork(), cellNetwork);
// Switch back to a restrictive carrier.
- mPolicyTracker.mConfigRestrictsAvoidBadWifi = true;
+ doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
mPolicyTracker.reevaluate();
defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mCm.getActiveNetwork(), wifiNetwork);
@@ -9357,8 +9443,7 @@
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
PERMISSION_DENIED);
mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
- mServiceContext.setPermission(Manifest.permission.NETWORK_STACK,
- PERMISSION_DENIED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED);
mServiceContext.setPermission(Manifest.permission.NETWORK_SETUP_WIZARD,
PERMISSION_DENIED);
}
@@ -9799,7 +9884,7 @@
setupConnectionOwnerUid(vpnOwnerUid, vpnType);
// Test as VPN app
- mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED);
mServiceContext.setPermission(
NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_DENIED);
}
@@ -9839,8 +9924,7 @@
public void testGetConnectionOwnerUidVpnServiceNetworkStackDoesNotThrow() throws Exception {
final int myUid = Process.myUid();
setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE);
- mServiceContext.setPermission(
- android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED);
assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo()));
}
@@ -10008,8 +10092,7 @@
public void testCheckConnectivityDiagnosticsPermissionsNetworkStack() throws Exception {
final NetworkAgentInfo naiWithoutUid = fakeMobileNai(new NetworkCapabilities());
- mServiceContext.setPermission(
- android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED);
assertTrue(
"NetworkStack permission not applied",
mService.checkConnectivityDiagnosticsPermissions(
@@ -10025,7 +10108,7 @@
nc.setAdministratorUids(new int[] {wrongUid});
final NetworkAgentInfo naiWithUid = fakeWifiNai(nc);
- mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED);
assertFalse(
"Mismatched uid/package name should not pass the location permission check",
@@ -10035,7 +10118,7 @@
private void verifyConnectivityDiagnosticsPermissionsWithNetworkAgentInfo(
NetworkAgentInfo info, boolean expectPermission) {
- mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED);
assertEquals(
"Unexpected ConnDiags permission",
@@ -10103,7 +10186,7 @@
setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION);
- mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED);
assertTrue(
"NetworkCapabilities administrator uid permission not applied",
@@ -10120,7 +10203,7 @@
setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION);
- mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED);
// Use wrong pid and uid
assertFalse(
@@ -10146,8 +10229,7 @@
final NetworkRequest request = new NetworkRequest.Builder().build();
when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
- mServiceContext.setPermission(
- android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED);
mService.registerConnectivityDiagnosticsCallback(
mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
@@ -10166,8 +10248,7 @@
final NetworkRequest request = new NetworkRequest.Builder().build();
when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
- mServiceContext.setPermission(
- android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED);
mService.registerConnectivityDiagnosticsCallback(
mConnectivityDiagnosticsCallback, request, mContext.getPackageName());
diff --git a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
index c32c1d2..3566aef 100644
--- a/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/unit/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -16,12 +16,17 @@
package com.android.server.net;
+import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkIdentity.OEM_PAID;
import static android.net.NetworkIdentity.OEM_PRIVATE;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
import static android.net.NetworkStats.DEFAULT_NETWORK_YES;
@@ -106,6 +111,7 @@
import android.provider.Settings;
import android.telephony.TelephonyManager;
+import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -200,6 +206,26 @@
if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
return mBaseContext.getSystemService(name);
}
+
+ @Override
+ public void enforceCallingOrSelfPermission(String permission, @Nullable String message) {
+ if (checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) {
+ throw new SecurityException("Test does not have mocked permission " + permission);
+ }
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ switch (permission) {
+ case PERMISSION_MAINLINE_NETWORK_STACK:
+ case READ_NETWORK_USAGE_HISTORY:
+ case UPDATE_DEVICE_STATS:
+ return PERMISSION_GRANTED;
+ default:
+ return PERMISSION_DENIED;
+ }
+
+ }
}
private final Clock mClock = new SimpleClock(ZoneOffset.UTC) {