Merge "Expose BIP and VSIM relavent definitions"
diff --git a/core/jni/android_net_NetworkUtils.cpp b/core/jni/android_net_NetworkUtils.cpp
index a781a37..1cee895 100644
--- a/core/jni/android_net_NetworkUtils.cpp
+++ b/core/jni/android_net_NetworkUtils.cpp
@@ -102,11 +102,6 @@
return setNetworkForSocket(netId, AFileDescriptor_getFD(env, javaFd));
}
-static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId)
-{
- return (jboolean) !queryUserAccess(uid, netId);
-}
-
static bool checkLenAndCopy(JNIEnv* env, const jbyteArray& addr, int len, void* dst)
{
if (env->GetArrayLength(addr) != len) {
@@ -246,7 +241,6 @@
{ "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
{ "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
{ "bindSocketToNetwork", "(Ljava/io/FileDescriptor;I)I", (void*) android_net_utils_bindSocketToNetwork },
- { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
{ "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
{ "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
{ "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
diff --git a/framework/api/current.txt b/framework/api/current.txt
index ad44b27..0a9560a 100644
--- a/framework/api/current.txt
+++ b/framework/api/current.txt
@@ -68,6 +68,7 @@
method public boolean bindProcessToNetwork(@Nullable android.net.Network);
method @NonNull public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network getActiveNetwork();
+ method @Nullable @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public android.net.Network getActiveNetworkForUid(int);
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.Network[] getAllNetworks();
@@ -387,7 +388,9 @@
public class NetworkRequest implements android.os.Parcelable {
method public boolean canBeSatisfiedBy(@Nullable android.net.NetworkCapabilities);
method public int describeContents();
+ method @NonNull public int[] getCapabilities();
method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier();
+ method @NonNull public int[] getTransportTypes();
method public boolean hasCapability(int);
method public boolean hasTransport(int);
method public void writeToParcel(android.os.Parcel, int);
diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt
index ac69a3e..cd96a1b 100644
--- a/framework/api/module-lib-current.txt
+++ b/framework/api/module-lib-current.txt
@@ -20,6 +20,7 @@
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void setAvoidUnvalidated(@NonNull android.net.Network);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setGlobalProxy(@Nullable android.net.ProxyInfo);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setLegacyLockdownVpnEnabled(boolean);
+ method public static void setPrivateDnsMode(@NonNull android.content.Context, @NonNull String);
method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setProfileNetworkPreference(@NonNull android.os.UserHandle, int, @Nullable java.util.concurrent.Executor, @Nullable Runnable);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void setRequireVpnForUids(boolean, @NonNull java.util.Collection<android.util.Range<java.lang.Integer>>);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_TEST_NETWORKS, android.Manifest.permission.NETWORK_STACK}) public void simulateDataStall(int, long, @NonNull android.net.Network, @NonNull android.os.PersistableBundle);
@@ -27,10 +28,12 @@
method public void systemReady();
field public static final int BLOCKED_METERED_REASON_ADMIN_DISABLED = 262144; // 0x40000
field public static final int BLOCKED_METERED_REASON_DATA_SAVER = 65536; // 0x10000
+ field public static final int BLOCKED_METERED_REASON_MASK = -65536; // 0xffff0000
field public static final int BLOCKED_METERED_REASON_USER_RESTRICTED = 131072; // 0x20000
field public static final int BLOCKED_REASON_APP_STANDBY = 4; // 0x4
field public static final int BLOCKED_REASON_BATTERY_SAVER = 1; // 0x1
field public static final int BLOCKED_REASON_DOZE = 2; // 0x2
+ field public static final int BLOCKED_REASON_LOCKDOWN_VPN = 16; // 0x10
field public static final int BLOCKED_REASON_NONE = 0; // 0x0
field public static final int BLOCKED_REASON_RESTRICTED_MODE = 8; // 0x8
field public static final String PRIVATE_DNS_MODE_OFF = "off";
@@ -40,6 +43,56 @@
field public static final int PROFILE_NETWORK_PREFERENCE_ENTERPRISE = 1; // 0x1
}
+ public static class ConnectivityManager.NetworkCallback {
+ method public void onBlockedStatusChanged(@NonNull android.net.Network, int);
+ }
+
+ public class ConnectivitySettingsManager {
+ method public static void clearGlobalProxy(@NonNull android.content.Context);
+ method @Nullable public static String getCaptivePortalHttpUrl(@NonNull android.content.Context);
+ method public static int getCaptivePortalMode(@NonNull android.content.Context, int);
+ method @NonNull public static java.time.Duration getConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method @NonNull public static android.util.Range<java.lang.Integer> getDnsResolverSampleRanges(@NonNull android.content.Context);
+ method @NonNull public static java.time.Duration getDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static int getDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, int);
+ method @Nullable public static android.net.ProxyInfo getGlobalProxy(@NonNull android.content.Context);
+ method @NonNull public static java.time.Duration getMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static boolean getMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
+ method @Nullable public static String getMobileDataPreferredApps(@NonNull android.content.Context);
+ method public static int getNetworkAvoidBadWifi(@NonNull android.content.Context);
+ method @Nullable public static String getNetworkMeteredMultipathPreference(@NonNull android.content.Context);
+ method public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, int);
+ method @NonNull public static java.time.Duration getNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method @NonNull public static String getPrivateDnsDefaultMode(@NonNull android.content.Context);
+ method @Nullable public static String getPrivateDnsHostname(@NonNull android.content.Context);
+ method public static boolean getWifiAlwaysRequested(@NonNull android.content.Context, boolean);
+ method @NonNull public static java.time.Duration getWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setCaptivePortalHttpUrl(@NonNull android.content.Context, @Nullable String);
+ method public static void setCaptivePortalMode(@NonNull android.content.Context, int);
+ method public static void setConnectivityKeepPendingIntentDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setDnsResolverSampleRanges(@NonNull android.content.Context, @NonNull android.util.Range<java.lang.Integer>);
+ method public static void setDnsResolverSampleValidityDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setDnsResolverSuccessThresholdPercent(@NonNull android.content.Context, @IntRange(from=0, to=100) int);
+ method public static void setGlobalProxy(@NonNull android.content.Context, @NonNull android.net.ProxyInfo);
+ method public static void setMobileDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setMobileDataAlwaysOn(@NonNull android.content.Context, boolean);
+ method public static void setMobileDataPreferredApps(@NonNull android.content.Context, @Nullable String);
+ method public static void setNetworkAvoidBadWifi(@NonNull android.content.Context, int);
+ method public static void setNetworkMeteredMultipathPreference(@NonNull android.content.Context, @NonNull String);
+ method public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull android.content.Context, @IntRange(from=0) int);
+ method public static void setNetworkSwitchNotificationRateDuration(@NonNull android.content.Context, @NonNull java.time.Duration);
+ method public static void setPrivateDnsDefaultMode(@NonNull android.content.Context, @NonNull String);
+ method public static void setPrivateDnsHostname(@NonNull android.content.Context, @Nullable String);
+ method public static void setWifiAlwaysRequested(@NonNull android.content.Context, boolean);
+ method public static void setWifiDataActivityTimeout(@NonNull android.content.Context, @NonNull java.time.Duration);
+ field public static final int CAPTIVE_PORTAL_MODE_AVOID = 2; // 0x2
+ field public static final int CAPTIVE_PORTAL_MODE_IGNORE = 0; // 0x0
+ field public static final int CAPTIVE_PORTAL_MODE_PROMPT = 1; // 0x1
+ field public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2; // 0x2
+ field public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0; // 0x0
+ field public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1; // 0x1
+ }
+
public final class NetworkAgentConfig implements android.os.Parcelable {
method @Nullable public String getSubscriberId();
method public boolean isBypassableVpn();
@@ -67,6 +120,7 @@
}
public class NetworkRequest implements android.os.Parcelable {
+ method @NonNull public int[] getUnwantedCapabilities();
method public boolean hasUnwantedCapability(int);
}
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index b64657e..afd4fa0 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -217,7 +217,9 @@
method public void markConnected();
method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
method public void onAutomaticReconnectDisabled();
+ method public void onBandwidthUpdateRequested();
method public void onNetworkCreated();
+ method public void onNetworkDestroyed();
method public void onNetworkUnwanted();
method public void onQosCallbackRegistered(int, @NonNull android.net.QosFilter);
method public void onQosCallbackUnregistered(int);
@@ -235,6 +237,8 @@
method public final void sendQosSessionAvailable(int, int, @NonNull android.net.QosSessionAttributes);
method public final void sendQosSessionLost(int, int, int);
method public final void sendSocketKeepaliveEvent(int, int);
+ method @Deprecated public void setLegacySubtype(int, @NonNull String);
+ method public void setTeardownDelayMs(@IntRange(from=0, to=0x1388) int);
method public final void setUnderlyingNetworks(@Nullable java.util.List<android.net.Network>);
method public void unregister();
field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
@@ -255,7 +259,12 @@
public static final class NetworkAgentConfig.Builder {
ctor public NetworkAgentConfig.Builder();
method @NonNull public android.net.NetworkAgentConfig build();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification();
method @NonNull public android.net.NetworkAgentConfig.Builder setExplicitlySelected(boolean);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyExtraInfo(@NonNull String);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubType(int);
+ method @NonNull public android.net.NetworkAgentConfig.Builder setLegacySubTypeName(@NonNull String);
method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyType(int);
method @NonNull public android.net.NetworkAgentConfig.Builder setLegacyTypeName(@NonNull String);
method @NonNull public android.net.NetworkAgentConfig.Builder setPartialConnectivityAcceptable(boolean);
@@ -406,6 +415,7 @@
}
public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ field public static final int ERROR_NO_SUCH_SLOT = -33; // 0xffffffdf
field public static final int SUCCESS = 0; // 0x0
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index bc668f3..c6f4e0b 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -38,7 +38,9 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -872,6 +874,17 @@
public static final int BLOCKED_REASON_RESTRICTED_MODE = 1 << 3;
/**
+ * Flag to indicate that an app is blocked because it is subject to an always-on VPN but the VPN
+ * is not currently connected.
+ *
+ * @see DevicePolicyManager#setAlwaysOnVpnPackage(ComponentName, String, boolean)
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_REASON_LOCKDOWN_VPN = 1 << 4;
+
+ /**
* Flag to indicate that an app is subject to Data saver restrictions that would
* result in its metered network access being blocked.
*
@@ -908,12 +921,21 @@
BLOCKED_REASON_DOZE,
BLOCKED_REASON_APP_STANDBY,
BLOCKED_REASON_RESTRICTED_MODE,
+ BLOCKED_REASON_LOCKDOWN_VPN,
BLOCKED_METERED_REASON_DATA_SAVER,
BLOCKED_METERED_REASON_USER_RESTRICTED,
BLOCKED_METERED_REASON_ADMIN_DISABLED,
})
public @interface BlockedReason {}
+ /**
+ * Set of blocked reasons that are only applicable on metered networks.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final int BLOCKED_METERED_REASON_MASK = 0xffff0000;
+
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
private final IConnectivityManager mService;
@@ -1168,8 +1190,7 @@
*
* @return a {@link Network} object for the current default network for the
* given UID or {@code null} if no default network is currently active
- *
- * @hide
+ * TODO: b/183465229 Cleanup getActiveNetworkForUid once b/165835257 is fixed
*/
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
@Nullable
@@ -3442,12 +3463,30 @@
* @param blocked Whether access to the {@link Network} is blocked due to system policy.
* @hide
*/
- public void onAvailable(@NonNull Network network,
+ public final void onAvailable(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities,
- @NonNull LinkProperties linkProperties, boolean blocked) {
+ @NonNull LinkProperties linkProperties, @BlockedReason int blocked) {
// Internally only this method is called when a new network is available, and
// it calls the callback in the same way and order that older versions used
// to call so as not to change the behavior.
+ onAvailable(network, networkCapabilities, linkProperties, blocked != 0);
+ onBlockedStatusChanged(network, blocked);
+ }
+
+ /**
+ * Legacy variant of onAvailable that takes a boolean blocked reason.
+ *
+ * This method has never been public API, but it's not final, so there may be apps that
+ * implemented it and rely on it being called. Do our best not to break them.
+ * Note: such apps will also get a second call to onBlockedStatusChanged immediately after
+ * this method is called. There does not seem to be a way to avoid this.
+ * TODO: add a compat check to move apps off this method, and eventually stop calling it.
+ *
+ * @hide
+ */
+ public void onAvailable(@NonNull Network network,
+ @NonNull NetworkCapabilities networkCapabilities,
+ @NonNull LinkProperties linkProperties, boolean blocked) {
onAvailable(network);
if (!networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)) {
@@ -3455,7 +3494,7 @@
}
onCapabilitiesChanged(network, networkCapabilities);
onLinkPropertiesChanged(network, linkProperties);
- onBlockedStatusChanged(network, blocked);
+ // No call to onBlockedStatusChanged here. That is done by the caller.
}
/**
@@ -3619,6 +3658,27 @@
*/
public void onBlockedStatusChanged(@NonNull Network network, boolean blocked) {}
+ /**
+ * Called when access to the specified network is blocked or unblocked, or the reason for
+ * access being blocked changes.
+ *
+ * If a NetworkCallback object implements this method,
+ * {@link #onBlockedStatusChanged(Network, boolean)} will not be called.
+ *
+ * <p>Do NOT call {@link #getNetworkCapabilities(Network)} or
+ * {@link #getLinkProperties(Network)} or other synchronous ConnectivityManager methods in
+ * this callback as this is prone to race conditions : calling these methods while in a
+ * callback may return an outdated or even a null object.
+ *
+ * @param network The {@link Network} whose blocked status has changed.
+ * @param blocked The blocked status of this {@link Network}.
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public void onBlockedStatusChanged(@NonNull Network network, @BlockedReason int blocked) {
+ onBlockedStatusChanged(network, blocked != 0);
+ }
+
private NetworkRequest networkRequest;
private final int mFlags;
}
@@ -3733,7 +3793,7 @@
case CALLBACK_AVAILABLE: {
NetworkCapabilities cap = getObject(message, NetworkCapabilities.class);
LinkProperties lp = getObject(message, LinkProperties.class);
- callback.onAvailable(network, cap, lp, message.arg1 != 0);
+ callback.onAvailable(network, cap, lp, message.arg1);
break;
}
case CALLBACK_LOSING: {
@@ -3767,8 +3827,7 @@
break;
}
case CALLBACK_BLK_CHANGED: {
- boolean blocked = message.arg1 != 0;
- callback.onBlockedStatusChanged(network, blocked);
+ callback.onBlockedStatusChanged(network, message.arg1);
}
}
}
@@ -5371,4 +5430,23 @@
if (TextUtils.isEmpty(mode)) mode = PRIVATE_DNS_MODE_OPPORTUNISTIC;
return mode;
}
+
+ /**
+ * Set private DNS mode to settings.
+ *
+ * @param context The {@link Context} to set the private DNS mode.
+ * @param mode The private dns mode. This should be one of the PRIVATE_DNS_MODE_* constants.
+ *
+ * @hide
+ */
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static void setPrivateDnsMode(@NonNull Context context,
+ @NonNull @PrivateDnsMode String 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");
+ }
+ Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_MODE, mode);
+ }
}
diff --git a/framework/src/android/net/ConnectivitySettingsManager.java b/framework/src/android/net/ConnectivitySettingsManager.java
index bbd8393..9a00055 100644
--- a/framework/src/android/net/ConnectivitySettingsManager.java
+++ b/framework/src/android/net/ConnectivitySettingsManager.java
@@ -16,16 +16,38 @@
package android.net;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_PERFORMANCE;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
+import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
+
import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.net.ConnectivityManager.MultipathPreference;
+import android.net.ConnectivityManager.PrivateDnsMode;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Range;
+
+import com.android.net.module.util.ProxyUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
+import java.util.List;
/**
* A manager class for connectivity module settings.
*
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public class ConnectivitySettingsManager {
private ConnectivitySettingsManager() {}
@@ -45,12 +67,16 @@
* Network activity refers to transmitting or receiving data on the network interfaces.
*
* Tracking is disabled if set to zero or negative value.
+ *
+ * @hide
*/
public static final String DATA_ACTIVITY_TIMEOUT_MOBILE = "data_activity_timeout_mobile";
/**
* Timeout to tracking Wifi data activity. Same as {@code DATA_ACTIVITY_TIMEOUT_MOBILE}
* but for Wifi network.
+ *
+ * @hide
*/
public static final String DATA_ACTIVITY_TIMEOUT_WIFI = "data_activity_timeout_wifi";
@@ -58,12 +84,16 @@
/**
* Sample validity in seconds to configure for the system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS =
"dns_resolver_sample_validity_seconds";
/**
* Success threshold in percent for use with the system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT =
"dns_resolver_success_threshold_percent";
@@ -71,24 +101,35 @@
/**
* Minimum number of samples needed for statistics to be considered meaningful in the
* system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_MIN_SAMPLES = "dns_resolver_min_samples";
/**
* Maximum number taken into account for statistics purposes in the system DNS resolver.
+ *
+ * @hide
*/
public static final String DNS_RESOLVER_MAX_SAMPLES = "dns_resolver_max_samples";
+ private static final int DNS_RESOLVER_DEFAULT_MIN_SAMPLES = 8;
+ private static final int DNS_RESOLVER_DEFAULT_MAX_SAMPLES = 64;
+
/** Network switch notification settings */
/**
* The maximum number of notifications shown in 24 hours when switching networks.
+ *
+ * @hide
*/
public static final String NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT =
"network_switch_notification_daily_limit";
/**
* The minimum time in milliseconds between notifications when switching networks.
+ *
+ * @hide
*/
public static final String NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS =
"network_switch_notification_rate_limit_millis";
@@ -98,14 +139,18 @@
/**
* The URL used for HTTP captive portal detection upon a new connection.
* A 204 response code from the server is used for validation.
+ *
+ * @hide
*/
public static final String CAPTIVE_PORTAL_HTTP_URL = "captive_portal_http_url";
/**
* What to do when connecting a network that presents a captive portal.
- * Must be one of the CAPTIVE_PORTAL_MODE_* constants above.
+ * Must be one of the CAPTIVE_PORTAL_MODE_* constants below.
*
* The default for this setting is CAPTIVE_PORTAL_MODE_PROMPT.
+ *
+ * @hide
*/
public static final String CAPTIVE_PORTAL_MODE = "captive_portal_mode";
@@ -139,11 +184,15 @@
/**
* Host name for global http proxy. Set via ConnectivityManager.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host";
/**
* Integer host port for global http proxy. Set via ConnectivityManager.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port";
@@ -153,12 +202,16 @@
* Domains should be listed in a comma- separated list. Example of
* acceptable formats: ".domain1.com,my.domain2.com" Use
* ConnectivityManager to set/get.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST =
"global_http_proxy_exclusion_list";
/**
* The location PAC File for the proxy.
+ *
+ * @hide
*/
public static final String GLOBAL_HTTP_PROXY_PAC = "global_proxy_pac_url";
@@ -171,11 +224,15 @@
* a specific provider. It may be used to store the provider name even when the
* mode changes so that temporarily disabling and re-enabling the specific
* provider mode does not necessitate retyping the provider hostname.
+ *
+ * @hide
*/
public static final String PRIVATE_DNS_MODE = "private_dns_mode";
/**
* The specific Private DNS provider name.
+ *
+ * @hide
*/
public static final String PRIVATE_DNS_SPECIFIER = "private_dns_specifier";
@@ -185,6 +242,8 @@
* all of which require explicit user action to enable/configure. See also b/79719289.
*
* Value is a string, suitable for assignment to PRIVATE_DNS_MODE above.
+ *
+ * @hide
*/
public static final String PRIVATE_DNS_DEFAULT_MODE = "private_dns_default_mode";
@@ -194,6 +253,8 @@
* The number of milliseconds to hold on to a PendingIntent based request. This delay gives
* the receivers of the PendingIntent an opportunity to make a new network request before
* the Network satisfying the request is potentially removed.
+ *
+ * @hide
*/
public static final String CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS =
"connectivity_release_pending_intent_delay_ms";
@@ -205,6 +266,8 @@
* See ConnectivityService for more info.
*
* (0 = disabled, 1 = enabled)
+ *
+ * @hide
*/
public static final String MOBILE_DATA_ALWAYS_ON = "mobile_data_always_on";
@@ -217,6 +280,8 @@
* See ConnectivityService for more info.
*
* (0 = disabled, 1 = enabled)
+ *
+ * @hide
*/
public static final String WIFI_ALWAYS_REQUESTED = "wifi_always_requested";
@@ -228,14 +293,637 @@
* 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
* null: Ask the user whether to switch away from bad wifi.
* 1: Avoid bad wifi.
+ *
+ * @hide
*/
public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi";
/**
+ * Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
+ */
+ public static final int NETWORK_AVOID_BAD_WIFI_IGNORE = 0;
+
+ /**
+ * Ask the user whether to switch away from bad wifi.
+ */
+ public static final int NETWORK_AVOID_BAD_WIFI_PROMPT = 1;
+
+ /**
+ * Avoid bad wifi.
+ */
+ public static final int NETWORK_AVOID_BAD_WIFI_AVOID = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ NETWORK_AVOID_BAD_WIFI_IGNORE,
+ NETWORK_AVOID_BAD_WIFI_PROMPT,
+ NETWORK_AVOID_BAD_WIFI_AVOID,
+ })
+ public @interface NetworkAvoidBadWifi {}
+
+ /**
* User setting for ConnectivityManager.getMeteredMultipathPreference(). This value may be
* overridden by the system based on device or application state. If null, the value
* specified by config_networkMeteredMultipathPreference is used.
+ *
+ * @hide
*/
public static final String NETWORK_METERED_MULTIPATH_PREFERENCE =
"network_metered_multipath_preference";
+
+ /**
+ * A list of apps that should go on cellular networks in preference even when higher-priority
+ * networks are connected.
+ *
+ * @hide
+ */
+ public static final String MOBILE_DATA_PREFERRED_APPS = "mobile_data_preferred_apps";
+
+ /**
+ * Get mobile data activity timeout from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default timeout if no setting value.
+ * @return The {@link Duration} of timeout to track mobile data activity.
+ */
+ @NonNull
+ public static Duration getMobileDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration def) {
+ final int timeout = Settings.Global.getInt(
+ context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE, (int) def.getSeconds());
+ return Duration.ofSeconds(timeout);
+ }
+
+ /**
+ * Set mobile data activity timeout to {@link Settings}.
+ * Tracking is disabled if set to zero or negative value.
+ *
+ * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be
+ * ignored.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param timeout The mobile data activity timeout.
+ */
+ public static void setMobileDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration timeout) {
+ Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_MOBILE,
+ (int) timeout.getSeconds());
+ }
+
+ /**
+ * Get wifi data activity timeout from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default timeout if no setting value.
+ * @return The {@link Duration} of timeout to track wifi data activity.
+ */
+ @NonNull
+ public static Duration getWifiDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration def) {
+ final int timeout = Settings.Global.getInt(
+ context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI, (int) def.getSeconds());
+ return Duration.ofSeconds(timeout);
+ }
+
+ /**
+ * Set wifi data activity timeout to {@link Settings}.
+ * Tracking is disabled if set to zero or negative value.
+ *
+ * Note: Only use the number of seconds in this duration, lower second(nanoseconds) will be
+ * ignored.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param timeout The wifi data activity timeout.
+ */
+ public static void setWifiDataActivityTimeout(@NonNull Context context,
+ @NonNull Duration timeout) {
+ Settings.Global.putInt(context.getContentResolver(), DATA_ACTIVITY_TIMEOUT_WIFI,
+ (int) timeout.getSeconds());
+ }
+
+ /**
+ * Get dns resolver sample validity duration from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default duration if no setting value.
+ * @return The {@link Duration} of sample validity duration to configure for the system DNS
+ * resolver.
+ */
+ @NonNull
+ public static Duration getDnsResolverSampleValidityDuration(@NonNull Context context,
+ @NonNull Duration def) {
+ final int duration = Settings.Global.getInt(context.getContentResolver(),
+ DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, (int) def.getSeconds());
+ return Duration.ofSeconds(duration);
+ }
+
+ /**
+ * Set dns resolver sample validity duration to {@link Settings}. The duration must be a
+ * positive number of seconds.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param duration The sample validity duration.
+ */
+ public static void setDnsResolverSampleValidityDuration(@NonNull Context context,
+ @NonNull Duration duration) {
+ final int time = (int) duration.getSeconds();
+ if (time <= 0) {
+ throw new IllegalArgumentException("Invalid duration");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_SAMPLE_VALIDITY_SECONDS, time);
+ }
+
+ /**
+ * Get dns resolver success threshold percent from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return The success threshold in percent for use with the system DNS resolver.
+ */
+ public static int getDnsResolverSuccessThresholdPercent(@NonNull Context context, int def) {
+ return Settings.Global.getInt(
+ context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, def);
+ }
+
+ /**
+ * Set dns resolver success threshold percent to {@link Settings}. The threshold percent must
+ * be 0~100.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param percent The success threshold percent.
+ */
+ public static void setDnsResolverSuccessThresholdPercent(@NonNull Context context,
+ @IntRange(from = 0, to = 100) int percent) {
+ if (percent < 0 || percent > 100) {
+ throw new IllegalArgumentException("Percent must be 0~100");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_SUCCESS_THRESHOLD_PERCENT, percent);
+ }
+
+ /**
+ * Get dns resolver samples range from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The {@link Range<Integer>} of samples needed for statistics to be considered
+ * meaningful in the system DNS resolver.
+ */
+ @NonNull
+ public static Range<Integer> getDnsResolverSampleRanges(@NonNull Context context) {
+ final int minSamples = Settings.Global.getInt(context.getContentResolver(),
+ DNS_RESOLVER_MIN_SAMPLES, DNS_RESOLVER_DEFAULT_MIN_SAMPLES);
+ final int maxSamples = Settings.Global.getInt(context.getContentResolver(),
+ DNS_RESOLVER_MAX_SAMPLES, DNS_RESOLVER_DEFAULT_MAX_SAMPLES);
+ return new Range<>(minSamples, maxSamples);
+ }
+
+ /**
+ * Set dns resolver samples range to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param range The samples range. The minimum number should be more than 0 and the maximum
+ * number should be less that 64.
+ */
+ public static void setDnsResolverSampleRanges(@NonNull Context context,
+ @NonNull Range<Integer> range) {
+ if (range.getLower() < 0 || range.getUpper() > 64) {
+ throw new IllegalArgumentException("Argument must be 0~64");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_MIN_SAMPLES, range.getLower());
+ Settings.Global.putInt(
+ context.getContentResolver(), DNS_RESOLVER_MAX_SAMPLES, range.getUpper());
+ }
+
+ /**
+ * Get maximum count (from {@link Settings}) of switching network notifications shown in 24
+ * hours.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return The maximum count of notifications shown in 24 hours when switching networks.
+ */
+ public static int getNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context,
+ int def) {
+ return Settings.Global.getInt(
+ context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, def);
+ }
+
+ /**
+ * Set maximum count (to {@link Settings}) of switching network notifications shown in 24 hours.
+ * The count must be at least 0.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param count The maximum count of switching network notifications shown in 24 hours.
+ */
+ public static void setNetworkSwitchNotificationMaximumDailyCount(@NonNull Context context,
+ @IntRange(from = 0) int count) {
+ if (count < 0) {
+ throw new IllegalArgumentException("Count must be 0~10.");
+ }
+ Settings.Global.putInt(
+ context.getContentResolver(), NETWORK_SWITCH_NOTIFICATION_DAILY_LIMIT, count);
+ }
+
+ /**
+ * Get minimum duration (from {@link Settings}) between each switching network notifications.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default time if no setting value.
+ * @return The minimum duration between notifications when switching networks.
+ */
+ @NonNull
+ public static Duration getNetworkSwitchNotificationRateDuration(@NonNull Context context,
+ @NonNull Duration def) {
+ final int duration = Settings.Global.getInt(context.getContentResolver(),
+ NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, (int) def.toMillis());
+ return Duration.ofMillis(duration);
+ }
+
+ /**
+ * Set minimum duration (to {@link Settings}) between each switching network notifications.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param duration The minimum duration between notifications when switching networks.
+ */
+ public static void setNetworkSwitchNotificationRateDuration(@NonNull Context context,
+ @NonNull Duration duration) {
+ final int time = (int) duration.toMillis();
+ if (time < 0) {
+ throw new IllegalArgumentException("Invalid duration.");
+ }
+ Settings.Global.putInt(context.getContentResolver(),
+ NETWORK_SWITCH_NOTIFICATION_RATE_LIMIT_MILLIS, time);
+ }
+
+ /**
+ * Get URL (from {@link Settings}) used for HTTP captive portal detection upon a new connection.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The URL used for HTTP captive portal detection upon a new connection.
+ */
+ @Nullable
+ public static String getCaptivePortalHttpUrl(@NonNull Context context) {
+ return Settings.Global.getString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL);
+ }
+
+ /**
+ * 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.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param url The URL used for HTTP captive portal detection upon a new connection.
+ */
+ public static void setCaptivePortalHttpUrl(@NonNull Context context, @Nullable String url) {
+ Settings.Global.putString(context.getContentResolver(), CAPTIVE_PORTAL_HTTP_URL, url);
+ }
+
+ /**
+ * Get mode (from {@link Settings}) when connecting a network that presents a captive portal.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default mode if no setting value.
+ * @return The mode when connecting a network that presents a captive portal.
+ */
+ @CaptivePortalMode
+ public static int getCaptivePortalMode(@NonNull Context context,
+ @CaptivePortalMode int def) {
+ return Settings.Global.getInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, def);
+ }
+
+ /**
+ * Set mode (to {@link Settings}) when connecting a network that presents a captive portal.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param mode The mode when connecting a network that presents a captive portal.
+ */
+ public static void setCaptivePortalMode(@NonNull Context context, @CaptivePortalMode int mode) {
+ if (!(mode == CAPTIVE_PORTAL_MODE_IGNORE
+ || mode == CAPTIVE_PORTAL_MODE_PROMPT
+ || mode == CAPTIVE_PORTAL_MODE_AVOID)) {
+ throw new IllegalArgumentException("Invalid captive portal mode");
+ }
+ Settings.Global.putInt(context.getContentResolver(), CAPTIVE_PORTAL_MODE, mode);
+ }
+
+ /**
+ * Get the global HTTP proxy applied to the device, or null if none.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The {@link ProxyInfo} which build from global http proxy settings.
+ */
+ @Nullable
+ public static ProxyInfo getGlobalProxy(@NonNull Context context) {
+ final String host = Settings.Global.getString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST);
+ final int port = Settings.Global.getInt(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* def */);
+ final String exclusionList = Settings.Global.getString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+ final String pacFileUrl = Settings.Global.getString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC);
+
+ if (TextUtils.isEmpty(host) && TextUtils.isEmpty(pacFileUrl)) {
+ return null; // No global proxy.
+ }
+
+ if (TextUtils.isEmpty(pacFileUrl)) {
+ return ProxyInfo.buildDirectProxy(
+ host, port, ProxyUtils.exclusionStringAsList(exclusionList));
+ } else {
+ return ProxyInfo.buildPacProxy(Uri.parse(pacFileUrl));
+ }
+ }
+
+ /**
+ * Set global http proxy settings from given {@link ProxyInfo}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param proxyInfo The {@link ProxyInfo} for global http proxy settings which build from
+ * {@link ProxyInfo#buildPacProxy(Uri)} or
+ * {@link ProxyInfo#buildDirectProxy(String, int, List)}
+ */
+ public static void setGlobalProxy(@NonNull Context context, @NonNull ProxyInfo proxyInfo) {
+ final String host = proxyInfo.getHost();
+ final int port = proxyInfo.getPort();
+ final String exclusionList = proxyInfo.getExclusionListAsString();
+ final String pacFileUrl = proxyInfo.getPacFileUrl().toString();
+
+ if (TextUtils.isEmpty(pacFileUrl)) {
+ Settings.Global.putString(context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, host);
+ Settings.Global.putInt(context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, port);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclusionList);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
+ } else {
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */);
+ Settings.Global.putInt(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */);
+ }
+ }
+
+ /**
+ * Clear all global http proxy settings.
+ *
+ * @param context The {@link Context} to set the setting.
+ */
+ public static void clearGlobalProxy(@NonNull Context context) {
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_HOST, "" /* value */);
+ Settings.Global.putInt(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PORT, 0 /* value */);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_EXCLUSION_LIST, "" /* value */);
+ Settings.Global.putString(
+ context.getContentResolver(), GLOBAL_HTTP_PROXY_PAC, "" /* value */);
+ }
+
+ /**
+ * Get specific private dns provider name from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The specific private dns provider name, or null if no setting value.
+ */
+ @Nullable
+ public static String getPrivateDnsHostname(@NonNull Context context) {
+ return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_SPECIFIER);
+ }
+
+ /**
+ * Set specific private dns provider name to {@link Settings}.
+ *
+ * @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);
+ }
+
+ /**
+ * Get default private dns mode from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The default private dns mode.
+ */
+ @PrivateDnsMode
+ @NonNull
+ public static String getPrivateDnsDefaultMode(@NonNull Context context) {
+ return Settings.Global.getString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE);
+ }
+
+ /**
+ * Set default private dns mode to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param mode The default private dns mode. This should be one of the PRIVATE_DNS_MODE_*
+ * constants.
+ */
+ public static void setPrivateDnsDefaultMode(@NonNull Context context,
+ @NonNull @PrivateDnsMode String 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");
+ }
+ Settings.Global.putString(context.getContentResolver(), PRIVATE_DNS_DEFAULT_MODE, mode);
+ }
+
+ /**
+ * Get duration (from {@link Settings}) to keep a PendingIntent-based request.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default duration if no setting value.
+ * @return The duration to keep a PendingIntent-based request.
+ */
+ @NonNull
+ public static Duration getConnectivityKeepPendingIntentDuration(@NonNull Context context,
+ @NonNull Duration def) {
+ final int duration = Settings.Secure.getInt(context.getContentResolver(),
+ CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, (int) def.toMillis());
+ return Duration.ofMillis(duration);
+ }
+
+ /**
+ * Set duration (to {@link Settings}) to keep a PendingIntent-based request.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param duration The duration to keep a PendingIntent-based request.
+ */
+ public static void setConnectivityKeepPendingIntentDuration(@NonNull Context context,
+ @NonNull Duration duration) {
+ final int time = (int) duration.toMillis();
+ if (time < 0) {
+ throw new IllegalArgumentException("Invalid duration.");
+ }
+ Settings.Secure.putInt(
+ context.getContentResolver(), CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS, time);
+ }
+
+ /**
+ * Read from {@link Settings} whether the mobile data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return Whether the mobile data connection should remain active even when higher
+ * priority networks are active.
+ */
+ public static boolean getMobileDataAlwaysOn(@NonNull Context context, boolean def) {
+ final int enable = Settings.Global.getInt(
+ context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (def ? 1 : 0));
+ return (enable != 0) ? true : false;
+ }
+
+ /**
+ * Write into {@link Settings} whether the mobile data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param enable Whether the mobile data connection should remain active even when higher
+ * priority networks are active.
+ */
+ public static void setMobileDataAlwaysOn(@NonNull Context context, boolean enable) {
+ Settings.Global.putInt(
+ context.getContentResolver(), MOBILE_DATA_ALWAYS_ON, (enable ? 1 : 0));
+ }
+
+ /**
+ * Read from {@link Settings} whether the wifi data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @param def The default value if no setting value.
+ * @return Whether the wifi data connection should remain active even when higher
+ * priority networks are active.
+ */
+ public static boolean getWifiAlwaysRequested(@NonNull Context context, boolean def) {
+ final int enable = Settings.Global.getInt(
+ context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (def ? 1 : 0));
+ return (enable != 0) ? true : false;
+ }
+
+ /**
+ * Write into {@link Settings} whether the wifi data connection should remain active
+ * even when higher priority networks are active.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param enable Whether the wifi data connection should remain active even when higher
+ * priority networks are active
+ */
+ public static void setWifiAlwaysRequested(@NonNull Context context, boolean enable) {
+ Settings.Global.putInt(
+ context.getContentResolver(), WIFI_ALWAYS_REQUESTED, (enable ? 1 : 0));
+ }
+
+ /**
+ * Get avoid bad wifi setting from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The setting whether to automatically switch away from wifi networks that lose
+ * internet access.
+ */
+ @NetworkAvoidBadWifi
+ public static int getNetworkAvoidBadWifi(@NonNull Context context) {
+ final String setting =
+ Settings.Global.getString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI);
+ if ("0".equals(setting)) {
+ return NETWORK_AVOID_BAD_WIFI_IGNORE;
+ } else if ("1".equals(setting)) {
+ return NETWORK_AVOID_BAD_WIFI_AVOID;
+ } else {
+ return NETWORK_AVOID_BAD_WIFI_PROMPT;
+ }
+ }
+
+ /**
+ * Set avoid bad wifi setting to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param value Whether to automatically switch away from wifi networks that lose internet
+ * access.
+ */
+ public static void setNetworkAvoidBadWifi(@NonNull Context context,
+ @NetworkAvoidBadWifi int value) {
+ final String setting;
+ if (value == NETWORK_AVOID_BAD_WIFI_IGNORE) {
+ setting = "0";
+ } else if (value == NETWORK_AVOID_BAD_WIFI_AVOID) {
+ setting = "1";
+ } else if (value == NETWORK_AVOID_BAD_WIFI_PROMPT) {
+ setting = null;
+ } else {
+ throw new IllegalArgumentException("Invalid avoid bad wifi setting");
+ }
+ Settings.Global.putString(context.getContentResolver(), NETWORK_AVOID_BAD_WIFI, setting);
+ }
+
+ /**
+ * Get network metered multipath preference from {@link Settings}.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return The network metered multipath preference which should be one of
+ * ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value specified
+ * by config_networkMeteredMultipathPreference is used.
+ */
+ @Nullable
+ public static String getNetworkMeteredMultipathPreference(@NonNull Context context) {
+ return Settings.Global.getString(
+ context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE);
+ }
+
+ /**
+ * Set network metered multipath preference to {@link Settings}.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param preference The network metered multipath preference which should be one of
+ * ConnectivityManager#MULTIPATH_PREFERENCE_* value or null if the value
+ * specified by config_networkMeteredMultipathPreference is used.
+ */
+ public static void setNetworkMeteredMultipathPreference(@NonNull Context context,
+ @NonNull @MultipathPreference String preference) {
+ if (!(Integer.valueOf(preference) == MULTIPATH_PREFERENCE_HANDOVER
+ || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_RELIABILITY
+ || Integer.valueOf(preference) == MULTIPATH_PREFERENCE_PERFORMANCE)) {
+ throw new IllegalArgumentException("Invalid private dns mode");
+ }
+ Settings.Global.putString(
+ context.getContentResolver(), NETWORK_METERED_MULTIPATH_PREFERENCE, preference);
+ }
+
+ /**
+ * Get the list of apps(from {@link Settings}) that should go on cellular networks in preference
+ * even when higher-priority networks are connected.
+ *
+ * @param context The {@link Context} to query the setting.
+ * @return A list of apps that should go on cellular networks in preference even when
+ * higher-priority networks are connected or null if no setting value.
+ */
+ @Nullable
+ public static String getMobileDataPreferredApps(@NonNull Context context) {
+ return Settings.Secure.getString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS);
+ }
+
+ /**
+ * Set the list of apps(to {@link Settings}) that should go on cellular networks in preference
+ * even when higher-priority networks are connected.
+ *
+ * @param context The {@link Context} to set the setting.
+ * @param list A list of apps that should go on cellular networks in preference even when
+ * higher-priority networks are connected.
+ */
+ public static void setMobileDataPreferredApps(@NonNull Context context, @Nullable String list) {
+ Settings.Secure.putString(context.getContentResolver(), MOBILE_DATA_PREFERRED_APPS, list);
+ }
}
diff --git a/framework/src/android/net/INetworkAgent.aidl b/framework/src/android/net/INetworkAgent.aidl
index 078acbd..d941d4b 100644
--- a/framework/src/android/net/INetworkAgent.aidl
+++ b/framework/src/android/net/INetworkAgent.aidl
@@ -47,4 +47,5 @@
void onQosFilterCallbackRegistered(int qosCallbackId, in QosFilterParcelable filterParcel);
void onQosCallbackUnregistered(int qosCallbackId);
void onNetworkCreated();
+ void onNetworkDestroyed();
}
diff --git a/framework/src/android/net/INetworkAgentRegistry.aidl b/framework/src/android/net/INetworkAgentRegistry.aidl
index cbd6193..26cb1ed 100644
--- a/framework/src/android/net/INetworkAgentRegistry.aidl
+++ b/framework/src/android/net/INetworkAgentRegistry.aidl
@@ -41,4 +41,5 @@
void sendNrQosSessionAvailable(int callbackId, in QosSession session, in NrQosSessionAttributes attributes);
void sendQosSessionLost(int qosCallbackId, in QosSession session);
void sendQosCallbackError(int qosCallbackId, int exceptionType);
+ void sendTeardownDelayMs(int teardownDelayMs);
}
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index aef1a31..c57da53 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -185,6 +185,20 @@
public static final int EVENT_UNDERLYING_NETWORKS_CHANGED = BASE + 5;
/**
+ * Sent by the NetworkAgent to ConnectivityService to pass the current value of the teardown
+ * delay.
+ * arg1 = teardown delay in milliseconds
+ * @hide
+ */
+ public static final int EVENT_TEARDOWN_DELAY_CHANGED = BASE + 6;
+
+ /**
+ * The maximum value for the teardown delay, in milliseconds.
+ * @hide
+ */
+ public static final int MAX_TEARDOWN_DELAY_MS = 5000;
+
+ /**
* Sent by ConnectivityService to the NetworkAgent to inform the agent of the
* networks status - whether we could use the network or could not, due to
* either a bad network configuration (no internet link) or captive portal.
@@ -197,7 +211,6 @@
*/
public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;
-
/**
* Network validation suceeded.
* Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}.
@@ -370,10 +383,17 @@
*/
public static final int CMD_NETWORK_CREATED = BASE + 22;
+ /**
+ * Sent by ConnectivityService to {@link NetworkAgent} to inform the agent that its native
+ * network was destroyed.
+ *
+ * @hide
+ */
+ public static final int CMD_NETWORK_DESTROYED = BASE + 23;
+
private static NetworkInfo getLegacyNetworkInfo(final NetworkAgentConfig config) {
- // The subtype can be changed with (TODO) setLegacySubtype, but it starts
- // with 0 (TelephonyManager.NETWORK_TYPE_UNKNOWN) and an empty description.
- final NetworkInfo ni = new NetworkInfo(config.legacyType, 0, config.legacyTypeName, "");
+ final NetworkInfo ni = new NetworkInfo(config.legacyType, config.legacySubType,
+ config.legacyTypeName, config.legacySubTypeName);
ni.setIsAvailable(true);
ni.setDetailedState(NetworkInfo.DetailedState.CONNECTING, null /* reason */,
config.getLegacyExtraInfo());
@@ -574,6 +594,10 @@
onNetworkCreated();
break;
}
+ case CMD_NETWORK_DESTROYED: {
+ onNetworkDestroyed();
+ break;
+ }
}
}
}
@@ -719,6 +743,11 @@
public void onNetworkCreated() {
mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_CREATED));
}
+
+ @Override
+ public void onNetworkDestroyed() {
+ mHandler.sendMessage(mHandler.obtainMessage(CMD_NETWORK_DESTROYED));
+ }
}
/**
@@ -835,6 +864,29 @@
}
/**
+ * Sets the value of the teardown delay.
+ *
+ * The teardown delay is the time between when the network disconnects and when the native
+ * network corresponding to this {@code NetworkAgent} is destroyed. By default, the native
+ * network is destroyed immediately. If {@code teardownDelayMs} is non-zero, then when this
+ * network disconnects, the system will instead immediately mark the network as restricted
+ * and unavailable to unprivileged apps, but will defer destroying the native network until the
+ * teardown delay timer expires.
+ *
+ * The interfaces in use by this network will remain in use until the native network is
+ * destroyed and cannot be reused until {@link #onNetworkDestroyed()} is called.
+ *
+ * This method may be called at any time while the network is connected. It has no effect if
+ * the network is already disconnected and the teardown delay timer is running.
+ *
+ * @param teardownDelayMs the teardown delay to set, or 0 to disable teardown delay.
+ */
+ public void setTeardownDelayMs(
+ @IntRange(from = 0, to = MAX_TEARDOWN_DELAY_MS) int teardownDelayMs) {
+ queueOrSendMessage(reg -> reg.sendTeardownDelayMs(teardownDelayMs));
+ }
+
+ /**
* Change the legacy subtype of this network agent.
*
* This is only for backward compatibility and should not be used by non-legacy network agents,
@@ -846,6 +898,7 @@
* @hide
*/
@Deprecated
+ @SystemApi
public void setLegacySubtype(final int legacySubtype, @NonNull final String legacySubtypeName) {
mNetworkInfo.setSubtype(legacySubtype, legacySubtypeName);
queueOrSendNetworkInfo(mNetworkInfo);
@@ -979,6 +1032,7 @@
* shall try to overwrite this method and produce a bandwidth update if capable.
* @hide
*/
+ @SystemApi
public void onBandwidthUpdateRequested() {
pollLceData();
}
@@ -1031,6 +1085,12 @@
*/
public void onNetworkCreated() {}
+
+ /**
+ * Called when ConnectivityService has successfully destroy this NetworkAgent's native network.
+ */
+ public void onNetworkDestroyed() {}
+
/**
* Requests that the network hardware send the specified packet at the specified interval.
*
diff --git a/framework/src/android/net/NetworkAgentConfig.java b/framework/src/android/net/NetworkAgentConfig.java
index 0bd2371..3f058d8 100644
--- a/framework/src/android/net/NetworkAgentConfig.java
+++ b/framework/src/android/net/NetworkAgentConfig.java
@@ -175,6 +175,12 @@
}
/**
+ * The legacy Sub type of this network agent, or TYPE_NONE if unset.
+ * @hide
+ */
+ public int legacySubType = ConnectivityManager.TYPE_NONE;
+
+ /**
* Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
* Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
*
@@ -200,6 +206,13 @@
}
/**
+ * The name of the legacy Sub network type. It's a free-form string.
+ * @hide
+ */
+ @NonNull
+ public String legacySubTypeName = "";
+
+ /**
* The legacy extra info of the agent. The extra info should only be :
* <ul>
* <li>For cellular agents, the APN name.</li>
@@ -235,6 +248,8 @@
skip464xlat = nac.skip464xlat;
legacyType = nac.legacyType;
legacyTypeName = nac.legacyTypeName;
+ legacySubType = nac.legacySubType;
+ legacySubTypeName = nac.legacySubTypeName;
mLegacyExtraInfo = nac.mLegacyExtraInfo;
}
}
@@ -300,7 +315,6 @@
* and reduce idle traffic on networks that are known to be IPv6-only without a NAT64.
*
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder disableNat64Detection() {
@@ -313,7 +327,6 @@
* perform its own carrier-specific provisioning procedure.
*
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder disableProvisioningNotification() {
@@ -334,6 +347,18 @@
}
/**
+ * Sets the legacy sub-type for this network.
+ *
+ * @param legacySubType the type
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setLegacySubType(final int legacySubType) {
+ mConfig.legacySubType = legacySubType;
+ return this;
+ }
+
+ /**
* Sets the name of the legacy type of the agent. It's a free-form string used in logging.
* @param legacyTypeName the name
* @return this builder, to facilitate chaining.
@@ -345,10 +370,20 @@
}
/**
+ * Sets the name of the legacy Sub-type of the agent. It's a free-form string.
+ * @param legacySubTypeName the name
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setLegacySubTypeName(@NonNull String legacySubTypeName) {
+ mConfig.legacySubTypeName = legacySubTypeName;
+ return this;
+ }
+
+ /**
* Sets the legacy extra info of the agent.
* @param legacyExtraInfo the legacy extra info.
* @return this builder, to facilitate chaining.
- * @hide
*/
@NonNull
public Builder setLegacyExtraInfo(@NonNull String legacyExtraInfo) {
@@ -435,6 +470,8 @@
out.writeInt(skip464xlat ? 1 : 0);
out.writeInt(legacyType);
out.writeString(legacyTypeName);
+ out.writeInt(legacySubType);
+ out.writeString(legacySubTypeName);
out.writeString(mLegacyExtraInfo);
}
@@ -452,6 +489,8 @@
networkAgentConfig.skip464xlat = in.readInt() != 0;
networkAgentConfig.legacyType = in.readInt();
networkAgentConfig.legacyTypeName = in.readString();
+ networkAgentConfig.legacySubType = in.readInt();
+ networkAgentConfig.legacySubTypeName = in.readString();
networkAgentConfig.mLegacyExtraInfo = in.readString();
return networkAgentConfig;
}
diff --git a/framework/src/android/net/NetworkRequest.java b/framework/src/android/net/NetworkRequest.java
index bcbc04f7..38691ef 100644
--- a/framework/src/android/net/NetworkRequest.java
+++ b/framework/src/android/net/NetworkRequest.java
@@ -699,4 +699,43 @@
public int hashCode() {
return Objects.hash(requestId, legacyType, networkCapabilities, type);
}
+
+ /**
+ * Gets all the capabilities set on this {@code NetworkRequest} instance.
+ *
+ * @return an array of capability values for this instance.
+ */
+ @NonNull
+ public @NetCapability int[] getCapabilities() {
+ // No need to make a defensive copy here as NC#getCapabilities() already returns
+ // a new array.
+ return networkCapabilities.getCapabilities();
+ }
+
+ /**
+ * Gets all the unwanted capabilities set on this {@code NetworkRequest} instance.
+ *
+ * @return an array of unwanted capability values for this instance.
+ *
+ * @hide
+ */
+ @NonNull
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public @NetCapability int[] getUnwantedCapabilities() {
+ // No need to make a defensive copy here as NC#getUnwantedCapabilities() already returns
+ // a new array.
+ return networkCapabilities.getUnwantedCapabilities();
+ }
+
+ /**
+ * Gets all the transports set on this {@code NetworkRequest} instance.
+ *
+ * @return an array of transport type values for this instance.
+ */
+ @NonNull
+ public @Transport int[] getTransportTypes() {
+ // No need to make a defensive copy here as NC#getTransportTypes() already returns
+ // a new array.
+ return networkCapabilities.getTransportTypes();
+ }
}
diff --git a/framework/src/android/net/NetworkUtils.java b/framework/src/android/net/NetworkUtils.java
index c4bebc0..a92fda1 100644
--- a/framework/src/android/net/NetworkUtils.java
+++ b/framework/src/android/net/NetworkUtils.java
@@ -92,7 +92,10 @@
* Determine if {@code uid} can access network designated by {@code netId}.
* @return {@code true} if {@code uid} can access network, {@code false} otherwise.
*/
- public native static boolean queryUserAccess(int uid, int netId);
+ public static boolean queryUserAccess(int uid, int netId) {
+ // TODO (b/183485986): remove this method
+ return false;
+ }
/**
* DNS resolver series jni method.
diff --git a/framework/src/android/net/SocketKeepalive.java b/framework/src/android/net/SocketKeepalive.java
index d007a95..f6cae72 100644
--- a/framework/src/android/net/SocketKeepalive.java
+++ b/framework/src/android/net/SocketKeepalive.java
@@ -55,36 +55,68 @@
static final String TAG = "SocketKeepalive";
/**
- * No errors.
+ * Success. It indicates there is no error.
* @hide
*/
@SystemApi
public static final int SUCCESS = 0;
- /** @hide */
+ /**
+ * No keepalive. This should only be internally as it indicates There is no keepalive.
+ * It should not propagate to applications.
+ * @hide
+ */
public static final int NO_KEEPALIVE = -1;
- /** @hide */
+ /**
+ * Data received.
+ * @hide
+ */
public static final int DATA_RECEIVED = -2;
- /** @hide */
+ /**
+ * The binder died.
+ * @hide
+ */
public static final int BINDER_DIED = -10;
- /** The specified {@code Network} is not connected. */
+ /**
+ * The invalid network. It indicates the specified {@code Network} is not connected.
+ */
public static final int ERROR_INVALID_NETWORK = -20;
- /** The specified IP addresses are invalid. For example, the specified source IP address is
- * not configured on the specified {@code Network}. */
+
+ /**
+ * The invalid IP addresses. Indicates the specified IP addresses are invalid.
+ * For example, the specified source IP address is not configured on the
+ * specified {@code Network}.
+ */
public static final int ERROR_INVALID_IP_ADDRESS = -21;
- /** The requested port is invalid. */
+
+ /**
+ * The port is invalid.
+ */
public static final int ERROR_INVALID_PORT = -22;
- /** The packet length is invalid (e.g., too long). */
+
+ /**
+ * The length is invalid (e.g. too long).
+ */
public static final int ERROR_INVALID_LENGTH = -23;
- /** The packet transmission interval is invalid (e.g., too short). */
+
+ /**
+ * The interval is invalid (e.g. too short).
+ */
public static final int ERROR_INVALID_INTERVAL = -24;
- /** The target socket is invalid. */
+
+ /**
+ * The socket is invalid.
+ */
public static final int ERROR_INVALID_SOCKET = -25;
- /** The target socket is not idle. */
+
+ /**
+ * The socket is not idle.
+ */
public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
/**
* The stop reason is uninitialized. This should only be internally used as initial state
* of stop reason, instead of propagating to application.
@@ -92,15 +124,29 @@
*/
public static final int ERROR_STOP_REASON_UNINITIALIZED = -27;
- /** The device does not support this request. */
+ /**
+ * The request is unsupported.
+ */
public static final int ERROR_UNSUPPORTED = -30;
- /** @hide TODO: delete when telephony code has been updated. */
- public static final int ERROR_HARDWARE_UNSUPPORTED = ERROR_UNSUPPORTED;
- /** The hardware returned an error. */
+
+ /**
+ * There was a hardware error.
+ */
public static final int ERROR_HARDWARE_ERROR = -31;
- /** The limitation of resource is reached. */
+
+ /**
+ * Resources are insufficient (e.g. all hardware slots are in use).
+ */
public static final int ERROR_INSUFFICIENT_RESOURCES = -32;
+ /**
+ * There was no such slot. This should only be internally as it indicates
+ * a programming error in the system server. It should not propagate to
+ * applications.
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_NO_SUCH_SLOT = -33;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -111,7 +157,8 @@
ERROR_INVALID_LENGTH,
ERROR_INVALID_INTERVAL,
ERROR_INVALID_SOCKET,
- ERROR_SOCKET_NOT_IDLE
+ ERROR_SOCKET_NOT_IDLE,
+ ERROR_NO_SUCH_SLOT
})
public @interface ErrorCode {}
@@ -122,7 +169,6 @@
ERROR_INVALID_LENGTH,
ERROR_UNSUPPORTED,
ERROR_INSUFFICIENT_RESOURCES,
- ERROR_HARDWARE_UNSUPPORTED
})
public @interface KeepaliveEvent {}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b737189..70c9fe7 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -30,6 +30,8 @@
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
+import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
@@ -108,6 +110,7 @@
import android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
import android.net.ConnectivityDiagnosticsManager.DataStallReport;
import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.BlockedReason;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.RestrictBackgroundStatus;
import android.net.ConnectivityResources;
@@ -315,6 +318,9 @@
// The maximum number of network request allowed per uid before an exception is thrown.
private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
+ // The maximum number of network request allowed for system UIDs before an exception is thrown.
+ private static final int MAX_NETWORK_REQUESTS_PER_SYSTEM_UID = 250;
+
@VisibleForTesting
protected int mLingerDelayMs; // Can't be final, or test subclass constructors can't change it.
@VisibleForTesting
@@ -330,6 +336,7 @@
protected final PermissionMonitor mPermissionMonitor;
private final PerUidCounter mNetworkRequestCounter;
+ private final PerUidCounter mSystemNetworkRequestCounter;
private volatile boolean mLockdownEnabled;
@@ -1146,8 +1153,8 @@
/**
* @see NetworkUtils#queryUserAccess(int, int)
*/
- public boolean queryUserAccess(int uid, int netId) {
- return NetworkUtils.queryUserAccess(uid, netId);
+ public boolean queryUserAccess(int uid, Network network, ConnectivityService cs) {
+ return cs.queryUserAccess(uid, network);
}
/**
@@ -1198,6 +1205,7 @@
mContext = Objects.requireNonNull(context, "missing Context");
mResources = deps.getResources(mContext);
mNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_UID);
+ mSystemNetworkRequestCounter = new PerUidCounter(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID);
mMetricsLog = logger;
mNetworkRanker = new NetworkRanker();
@@ -1548,16 +1556,16 @@
mNetworkInfoBlockingLogs.log(action + " " + uid);
}
- private void maybeLogBlockedStatusChanged(NetworkRequestInfo nri, Network net,
- boolean blocked) {
+ private void maybeLogBlockedStatusChanged(NetworkRequestInfo nri, Network net, int blocked) {
if (nri == null || net == null || !LOGD_BLOCKED_NETWORKINFO) {
return;
}
- final String action = blocked ? "BLOCKED" : "UNBLOCKED";
+ final String action = (blocked != 0) ? "BLOCKED" : "UNBLOCKED";
final int requestId = nri.getActiveRequest() != null
? nri.getActiveRequest().requestId : nri.mRequests.get(0).requestId;
mNetworkInfoBlockingLogs.log(String.format(
- "%s %d(%d) on netId %d", action, nri.mAsUid, requestId, net.getNetId()));
+ "%s %d(%d) on netId %d: %s", action, nri.mAsUid, requestId, net.getNetId(),
+ blockedReasonsToString(blocked)));
}
/**
@@ -2338,15 +2346,15 @@
private final NetworkPolicyCallback mPolicyCallback = new NetworkPolicyCallback() {
@Override
- public void onUidBlockedReasonChanged(int uid, int blockedReasons) {
+ public void onUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
mHandler.sendMessage(mHandler.obtainMessage(EVENT_UID_BLOCKED_REASON_CHANGED,
uid, blockedReasons));
}
};
- void handleUidBlockedReasonChanged(int uid, int blockedReasons) {
+ private void handleUidBlockedReasonChanged(int uid, @BlockedReason int blockedReasons) {
maybeNotifyNetworkBlockedForNewState(uid, blockedReasons);
- mUidBlockedReasons.put(uid, blockedReasons);
+ setUidBlockedReasons(uid, blockedReasons);
}
private boolean checkAnyPermissionOf(String... permissions) {
@@ -3119,6 +3127,13 @@
}
break;
}
+ case NetworkAgent.EVENT_TEARDOWN_DELAY_CHANGED: {
+ if (msg.arg1 >= 0 && msg.arg1 <= NetworkAgent.MAX_TEARDOWN_DELAY_MS) {
+ nai.teardownDelayMs = msg.arg1;
+ } else {
+ logwtf(nai.toShortString() + " set invalid teardown delay " + msg.arg1);
+ }
+ }
}
}
@@ -3689,6 +3704,23 @@
mLegacyTypeTracker.remove(nai, wasDefault);
rematchAllNetworksAndRequests();
mLingerMonitor.noteDisconnect(nai);
+
+ // Immediate teardown.
+ if (nai.teardownDelayMs == 0) {
+ destroyNetwork(nai);
+ return;
+ }
+
+ // Delayed teardown.
+ try {
+ mNetd.networkSetPermissionForNetwork(nai.network.netId, INetd.PERMISSION_SYSTEM);
+ } catch (RemoteException e) {
+ Log.d(TAG, "Error marking network restricted during teardown: " + e);
+ }
+ mHandler.postDelayed(() -> destroyNetwork(nai), nai.teardownDelayMs);
+ }
+
+ private void destroyNetwork(NetworkAgentInfo nai) {
if (nai.created) {
// Tell netd to clean up the configuration for this network
// (routing rules, DNS, etc).
@@ -3701,6 +3733,7 @@
mDnsManager.removeNetwork(nai.network);
}
mNetIdManager.releaseNetId(nai.network.getNetId());
+ nai.onNetworkDestroyed();
}
private boolean createNativeNetwork(@NonNull NetworkAgentInfo networkAgent) {
@@ -4001,7 +4034,7 @@
}
}
}
- mNetworkRequestCounter.decrementCount(nri.mUid);
+ decrementRequestCount(nri);
mNetworkRequestInfoLogs.log("RELEASE " + nri);
if (null != nri.getActiveRequest()) {
@@ -4112,6 +4145,20 @@
}
}
+ private PerUidCounter getRequestCounter(NetworkRequestInfo nri) {
+ return checkAnyPermissionOf(
+ nri.mPid, nri.mUid, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+ ? mSystemNetworkRequestCounter : mNetworkRequestCounter;
+ }
+
+ private void incrementRequestCountOrThrow(NetworkRequestInfo nri) {
+ getRequestCounter(nri).incrementCountOrThrow(nri.mUid);
+ }
+
+ private void decrementRequestCount(NetworkRequestInfo nri) {
+ getRequestCounter(nri).decrementCount(nri.mUid);
+ }
+
@Override
public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
enforceNetworkStackSettingsOrSetup();
@@ -4839,6 +4886,42 @@
nai.networkMonitor().forceReevaluation(uid);
}
+ // TODO: call into netd.
+ private boolean queryUserAccess(int uid, Network network) {
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) return false;
+
+ // Any UID can use its default network.
+ if (nai == getDefaultNetworkForUid(uid)) return true;
+
+ // Privileged apps can use any network.
+ if (mPermissionMonitor.hasRestrictedNetworksPermission(uid)) {
+ return true;
+ }
+
+ // An unprivileged UID can use a VPN iff the VPN applies to it.
+ if (nai.isVPN()) {
+ return nai.networkCapabilities.appliesToUid(uid);
+ }
+
+ // An unprivileged UID can bypass the VPN that applies to it only if it can protect its
+ // sockets, i.e., if it is the owner.
+ final NetworkAgentInfo vpn = getVpnForUid(uid);
+ if (vpn != null && !vpn.networkAgentConfig.allowBypass
+ && uid != vpn.networkCapabilities.getOwnerUid()) {
+ return false;
+ }
+
+ // The UID's permission must be at least sufficient for the network. Since the restricted
+ // permission was already checked above, that just leaves background networks.
+ if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_FOREGROUND)) {
+ return mPermissionMonitor.hasUseBackgroundNetworksPermission(uid);
+ }
+
+ // Unrestricted network. Anyone gets to use it.
+ return true;
+ }
+
/**
* Returns information about the proxy a certain network is using. If given a null network, it
* it will return the proxy for the bound network for the caller app or the default proxy if
@@ -4859,7 +4942,7 @@
return null;
}
return getLinkPropertiesProxyInfo(activeNetwork);
- } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network.getNetId())) {
+ } else if (mDeps.queryUserAccess(mDeps.getCallingUid(), network, this)) {
// Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
// caller may not have.
return getLinkPropertiesProxyInfo(network);
@@ -5424,7 +5507,7 @@
mPid = getCallingPid();
mUid = mDeps.getCallingUid();
mAsUid = asUid;
- mNetworkRequestCounter.incrementCountOrThrow(mUid);
+ incrementRequestCountOrThrow(this);
/**
* Location sensitive data not included in pending intent. Only included in
* {@link NetworkCallback}.
@@ -5456,15 +5539,10 @@
mUid = mDeps.getCallingUid();
mAsUid = asUid;
mPendingIntent = null;
- mNetworkRequestCounter.incrementCountOrThrow(mUid);
+ incrementRequestCountOrThrow(this);
mCallbackFlags = callbackFlags;
mCallingAttributionTag = callingAttributionTag;
-
- try {
- mBinder.linkToDeath(this, 0);
- } catch (RemoteException e) {
- binderDied();
- }
+ linkDeathRecipient();
}
NetworkRequestInfo(@NonNull final NetworkRequestInfo nri,
@@ -5499,9 +5577,10 @@
mUid = nri.mUid;
mAsUid = nri.mAsUid;
mPendingIntent = nri.mPendingIntent;
- mNetworkRequestCounter.incrementCountOrThrow(mUid);
+ incrementRequestCountOrThrow(this);
mCallbackFlags = nri.mCallbackFlags;
mCallingAttributionTag = nri.mCallingAttributionTag;
+ linkDeathRecipient();
}
NetworkRequestInfo(int asUid, @NonNull final NetworkRequest r) {
@@ -5530,8 +5609,18 @@
return Collections.unmodifiableList(tempRequests);
}
+ void linkDeathRecipient() {
+ if (null != mBinder) {
+ try {
+ mBinder.linkToDeath(this, 0);
+ } catch (RemoteException e) {
+ binderDied();
+ }
+ }
+ }
+
void unlinkDeathRecipient() {
- if (mBinder != null) {
+ if (null != mBinder) {
mBinder.unlinkToDeath(this, 0);
}
}
@@ -6213,8 +6302,8 @@
// Request used to optionally keep vehicle internal network always active
private final NetworkRequest mDefaultVehicleRequest;
- // TODO replace with INetd.DUMMY_NET_ID when available.
- private static final int NO_SERVICE_NET_ID = 51;
+ // TODO replace with INetd.UNREACHABLE_NET_ID when available.
+ private static final int NO_SERVICE_NET_ID = 52;
// Sentinel NAI used to direct apps with default networks that should have no connectivity to a
// network with no service. This NAI should never be matched against, nor should any public API
// ever return the associated network. For this reason, this NAI is not in the list of available
@@ -7308,7 +7397,7 @@
break;
}
case ConnectivityManager.CALLBACK_BLK_CHANGED: {
- maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1 != 0);
+ maybeLogBlockedStatusChanged(nri, networkAgent.network, arg1);
msg.arg1 = arg1;
break;
}
@@ -8052,12 +8141,11 @@
return;
}
+ final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
final boolean metered = nai.networkCapabilities.isMetered();
- boolean blocked;
- blocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
- blocked |= NetworkPolicyManager.isUidBlocked(
- mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE), metered);
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE, blocked ? 1 : 0);
+ final boolean vpnBlocked = isUidBlockedByVpn(nri.mAsUid, mVpnBlockedUidRanges);
+ callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_AVAILABLE,
+ getBlockedState(blockedReasons, metered, vpnBlocked));
}
// Notify the requests on this NAI that the network is now lingered.
@@ -8066,6 +8154,21 @@
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime);
}
+ private static int getBlockedState(int reasons, boolean metered, boolean vpnBlocked) {
+ if (!metered) reasons &= ~BLOCKED_METERED_REASON_MASK;
+ return vpnBlocked
+ ? reasons | BLOCKED_REASON_LOCKDOWN_VPN
+ : reasons & ~BLOCKED_REASON_LOCKDOWN_VPN;
+ }
+
+ private void setUidBlockedReasons(int uid, @BlockedReason int blockedReasons) {
+ if (blockedReasons == BLOCKED_REASON_NONE) {
+ mUidBlockedReasons.delete(uid);
+ } else {
+ mUidBlockedReasons.put(uid, blockedReasons);
+ }
+ }
+
/**
* Notify of the blocked state apps with a registered callback matching a given NAI.
*
@@ -8073,7 +8176,10 @@
* any given nai, all requests need to be considered according to the uid who filed it.
*
* @param nai The target NetworkAgentInfo.
- * @param oldMetered True if the previous network capabilities is metered.
+ * @param oldMetered True if the previous network capabilities were metered.
+ * @param newMetered True if the current network capabilities are metered.
+ * @param oldBlockedUidRanges list of UID ranges previously blocked by lockdown VPN.
+ * @param newBlockedUidRanges list of UID ranges blocked by lockdown VPN.
*/
private void maybeNotifyNetworkBlocked(NetworkAgentInfo nai, boolean oldMetered,
boolean newMetered, List<UidRange> oldBlockedUidRanges,
@@ -8082,22 +8188,18 @@
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
- final boolean oldBlocked, newBlocked, oldVpnBlocked, newVpnBlocked;
- oldVpnBlocked = isUidBlockedByVpn(nri.mAsUid, oldBlockedUidRanges);
- newVpnBlocked = (oldBlockedUidRanges != newBlockedUidRanges)
+ final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
+ final boolean oldVpnBlocked = isUidBlockedByVpn(nri.mAsUid, oldBlockedUidRanges);
+ final boolean newVpnBlocked = (oldBlockedUidRanges != newBlockedUidRanges)
? isUidBlockedByVpn(nri.mAsUid, newBlockedUidRanges)
: oldVpnBlocked;
- final int blockedReasons = mUidBlockedReasons.get(nri.mAsUid, BLOCKED_REASON_NONE);
- oldBlocked = oldVpnBlocked || NetworkPolicyManager.isUidBlocked(
- blockedReasons, oldMetered);
- newBlocked = newVpnBlocked || NetworkPolicyManager.isUidBlocked(
- blockedReasons, newMetered);
-
- if (oldBlocked != newBlocked) {
+ final int oldBlockedState = getBlockedState(blockedReasons, oldMetered, oldVpnBlocked);
+ final int newBlockedState = getBlockedState(blockedReasons, newMetered, newVpnBlocked);
+ if (oldBlockedState != newBlockedState) {
callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
- encodeBool(newBlocked));
+ newBlockedState);
}
}
}
@@ -8107,25 +8209,23 @@
* @param uid The uid for which the rules changed.
* @param blockedReasons The reasons for why an uid is blocked.
*/
- private void maybeNotifyNetworkBlockedForNewState(int uid, int blockedReasons) {
+ private void maybeNotifyNetworkBlockedForNewState(int uid, @BlockedReason int blockedReasons) {
for (final NetworkAgentInfo nai : mNetworkAgentInfos) {
final boolean metered = nai.networkCapabilities.isMetered();
final boolean vpnBlocked = isUidBlockedByVpn(uid, mVpnBlockedUidRanges);
- final boolean oldBlocked, newBlocked;
- oldBlocked = vpnBlocked || NetworkPolicyManager.isUidBlocked(
- mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered);
- newBlocked = vpnBlocked || NetworkPolicyManager.isUidBlocked(
- blockedReasons, metered);
- if (oldBlocked == newBlocked) {
+ final int oldBlockedState = getBlockedState(
+ mUidBlockedReasons.get(uid, BLOCKED_REASON_NONE), metered, vpnBlocked);
+ final int newBlockedState = getBlockedState(blockedReasons, metered, vpnBlocked);
+ if (oldBlockedState == newBlockedState) {
continue;
}
- final int arg = encodeBool(newBlocked);
for (int i = 0; i < nai.numNetworkRequests(); i++) {
NetworkRequest nr = nai.requestAt(i);
NetworkRequestInfo nri = mNetworkRequests.get(nr);
if (nri != null && nri.mAsUid == uid) {
- callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED, arg);
+ callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_BLK_CHANGED,
+ newBlockedState);
}
}
}
@@ -8673,7 +8773,7 @@
// Decrement the reference count for this NetworkRequestInfo. The reference count is
// incremented when the NetworkRequestInfo is created as part of
// enforceRequestCountLimit().
- mNetworkRequestCounter.decrementCount(nri.mUid);
+ decrementRequestCount(nri);
return;
}
@@ -8739,7 +8839,7 @@
// Decrement the reference count for this NetworkRequestInfo. The reference count is
// incremented when the NetworkRequestInfo is created as part of
// enforceRequestCountLimit().
- mNetworkRequestCounter.decrementCount(nri.mUid);
+ decrementRequestCount(nri);
iCb.unlinkToDeath(cbInfo, 0);
}
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 7b20ded..058dac8 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -26,6 +26,7 @@
import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
import static android.net.SocketKeepalive.ERROR_INVALID_NETWORK;
import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.ERROR_NO_SUCH_SLOT;
import static android.net.SocketKeepalive.ERROR_STOP_REASON_UNINITIALIZED;
import static android.net.SocketKeepalive.ERROR_UNSUPPORTED;
import static android.net.SocketKeepalive.MAX_INTERVAL_SEC;
@@ -518,6 +519,8 @@
}
} else if (reason == ERROR_STOP_REASON_UNINITIALIZED) {
throw new IllegalStateException("Unexpected stop reason: " + reason);
+ } else if (reason == ERROR_NO_SUCH_SLOT) {
+ throw new IllegalStateException("No such slot: " + reason);
} else {
notifyErrorCallback(ki.mCallback, reason);
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 4963353..ee32fbf 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -201,6 +201,9 @@
// Set to true when partial connectivity was detected.
public boolean partialConnectivity;
+ // Delay between when the network is disconnected and when the native network is destroyed.
+ public int teardownDelayMs;
+
// Captive portal info of the network from RFC8908, if any.
// Obtained by ConnectivityService and merged into NetworkAgent-provided information.
public CaptivePortalData capportApiData;
@@ -588,6 +591,17 @@
}
}
+ /**
+ * Notify the NetworkAgent that the native network has been destroyed.
+ */
+ public void onNetworkDestroyed() {
+ try {
+ networkAgent.onNetworkDestroyed();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error sending network destroyed event", e);
+ }
+ }
+
// TODO: consider moving out of NetworkAgentInfo into its own class
private class NetworkAgentMessageHandler extends INetworkAgentRegistry.Stub {
private final Handler mHandler;
@@ -664,6 +678,12 @@
@QosCallbackException.ExceptionType final int exceptionType) {
mQosCallbackTracker.sendEventQosCallbackError(qosCallbackId, exceptionType);
}
+
+ @Override
+ public void sendTeardownDelayMs(int teardownDelayMs) {
+ mHandler.obtainMessage(NetworkAgent.EVENT_TEARDOWN_DELAY_CHANGED,
+ teardownDelayMs, 0, new Pair<>(NetworkAgentInfo.this, null)).sendToTarget();
+ }
}
/**
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index 488677a..3711679 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -271,6 +271,13 @@
return mApps.containsKey(uid);
}
+ /**
+ * Returns whether the given uid has permission to use restricted networks.
+ */
+ public synchronized boolean hasRestrictedNetworksPermission(int uid) {
+ return Boolean.TRUE.equals(mApps.get(uid));
+ }
+
private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) {
List<Integer> network = new ArrayList<>();
List<Integer> system = new ArrayList<>();
diff --git a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
index a4d8353..1e54093 100644
--- a/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
+++ b/tests/net/common/java/android/net/NetworkAgentConfigTest.kt
@@ -19,6 +19,7 @@
import android.os.Build
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel.isAtLeastS
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.assertParcelSane
@@ -44,7 +45,13 @@
setPartialConnectivityAcceptable(false)
setUnvalidatedConnectivityAcceptable(true)
}.build()
- assertParcelSane(config, 10)
+ if (isAtLeastS()) {
+ // From S, the config will have 12 items
+ assertParcelSane(config, 12)
+ } else {
+ // For R or below, the config will have 10 items
+ assertParcelSane(config, 10)
+ }
}
@Test @IgnoreUpTo(Build.VERSION_CODES.Q)
diff --git a/tests/net/integration/AndroidManifest.xml b/tests/net/integration/AndroidManifest.xml
index f5a4234..db18500 100644
--- a/tests/net/integration/AndroidManifest.xml
+++ b/tests/net/integration/AndroidManifest.xml
@@ -37,6 +37,7 @@
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<!-- Reading DeviceConfig flags -->
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
+ <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<application android:debuggable="true">
<uses-library android:name="android.test.runner"/>
diff --git a/tests/net/java/android/net/ConnectivityManagerTest.java b/tests/net/java/android/net/ConnectivityManagerTest.java
index 36f205b..6cbdd25 100644
--- a/tests/net/java/android/net/ConnectivityManagerTest.java
+++ b/tests/net/java/android/net/ConnectivityManagerTest.java
@@ -41,10 +41,10 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8938c1d..ad93420 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.CHANGE_NETWORK_STATE;
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.content.Intent.ACTION_USER_ADDED;
import static android.content.Intent.ACTION_USER_REMOVED;
@@ -31,6 +32,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
+import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
@@ -715,6 +717,9 @@
private int mProbesSucceeded;
private String mNmValidationRedirectUrl = null;
private boolean mNmProvNotificationRequested = false;
+ private Runnable mCreatedCallback;
+ private Runnable mUnwantedCallback;
+ private Runnable mDisconnectedCallback;
private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
// Contains the redirectUrl from networkStatus(). Before reading, wait for
@@ -769,6 +774,24 @@
mRedirectUrl = redirectUrl;
mNetworkStatusReceived.open();
}
+
+ @Override
+ public void onNetworkCreated() {
+ super.onNetworkCreated();
+ if (mCreatedCallback != null) mCreatedCallback.run();
+ }
+
+ @Override
+ public void onNetworkUnwanted() {
+ super.onNetworkUnwanted();
+ if (mUnwantedCallback != null) mUnwantedCallback.run();
+ }
+
+ @Override
+ public void onNetworkDestroyed() {
+ super.onNetworkDestroyed();
+ if (mDisconnectedCallback != null) mDisconnectedCallback.run();
+ }
};
assertEquals(na.getNetwork().netId, nmNetworkCaptor.getValue().netId);
@@ -970,6 +993,18 @@
p.timestampMillis = DATA_STALL_TIMESTAMP;
mNmCallbacks.notifyDataStallSuspected(p);
}
+
+ public void setCreatedCallback(Runnable r) {
+ mCreatedCallback = r;
+ }
+
+ public void setUnwantedCallback(Runnable r) {
+ mUnwantedCallback = r;
+ }
+
+ public void setDisconnectedCallback(Runnable r) {
+ mDisconnectedCallback = r;
+ }
}
/**
@@ -1373,10 +1408,21 @@
}
private void mockUidNetworkingBlocked() {
- doAnswer(i -> NetworkPolicyManager.isUidBlocked(mBlockedReasons, i.getArgument(1))
+ doAnswer(i -> isUidBlocked(mBlockedReasons, i.getArgument(1))
).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean());
}
+ private boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) {
+ final int blockedOnAllNetworksReason = (blockedReasons & ~BLOCKED_METERED_REASON_MASK);
+ if (blockedOnAllNetworksReason != BLOCKED_REASON_NONE) {
+ return true;
+ }
+ if (meteredNetwork) {
+ return blockedReasons != BLOCKED_REASON_NONE;
+ }
+ return false;
+ }
+
private void setBlockedReasonChanged(int blockedReasons) {
mBlockedReasons = blockedReasons;
mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons);
@@ -1571,7 +1617,7 @@
doReturn(mNetworkStack).when(deps).getNetworkStack();
doReturn(mSystemProperties).when(deps).getSystemProperties();
doReturn(mock(ProxyTracker.class)).when(deps).makeProxyTracker(any(), any());
- doReturn(true).when(deps).queryUserAccess(anyInt(), anyInt());
+ doReturn(true).when(deps).queryUserAccess(anyInt(), any(), any());
doAnswer(inv -> {
mPolicyTracker = new WrappedMultinetworkPolicyTracker(
inv.getArgument(0), inv.getArgument(1), inv.getArgument(2));
@@ -2438,8 +2484,7 @@
public void networkCallbacksSanitizationTest_Sanitize() throws Exception {
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
PERMISSION_DENIED);
- mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
- PERMISSION_DENIED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
doNetworkCallbacksSanitizationTest(true /* sanitized */);
}
@@ -2447,7 +2492,7 @@
public void networkCallbacksSanitizationTest_NoSanitize_NetworkStack() throws Exception {
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
PERMISSION_GRANTED);
- mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
doNetworkCallbacksSanitizationTest(false /* sanitized */);
}
@@ -2455,7 +2500,7 @@
public void networkCallbacksSanitizationTest_NoSanitize_Settings() throws Exception {
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
PERMISSION_DENIED);
- mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
doNetworkCallbacksSanitizationTest(false /* sanitized */);
}
@@ -2799,6 +2844,94 @@
}
@Test
+ public void testNetworkAgentCallbacks() throws Exception {
+ // Keeps track of the order of events that happen in this test.
+ final LinkedBlockingQueue<String> eventOrder = new LinkedBlockingQueue<>();
+
+ final NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ final TestNetworkCallback callback = new TestNetworkCallback();
+ final AtomicReference<Network> wifiNetwork = new AtomicReference<>();
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+
+ // Expectations for state when various callbacks fire. These expectations run on the handler
+ // thread and not on the test thread because they need to prevent the handler thread from
+ // advancing while they examine state.
+
+ // 1. When onCreated fires, netd has been told to create the network.
+ mWiFiNetworkAgent.setCreatedCallback(() -> {
+ eventOrder.offer("onNetworkCreated");
+ wifiNetwork.set(mWiFiNetworkAgent.getNetwork());
+ assertNotNull(wifiNetwork.get());
+ try {
+ verify(mMockNetd).networkCreatePhysical(wifiNetwork.get().getNetId(),
+ INetd.PERMISSION_NONE);
+ } catch (RemoteException impossible) {
+ fail();
+ }
+ });
+
+ // 2. onNetworkUnwanted isn't precisely ordered with respect to any particular events. Just
+ // check that it is fired at some point after disconnect.
+ mWiFiNetworkAgent.setUnwantedCallback(() -> eventOrder.offer("onNetworkUnwanted"));
+
+ // 3. While the teardown timer is running, connectivity APIs report the network is gone, but
+ // netd has not yet been told to destroy it.
+ final Runnable duringTeardown = () -> {
+ eventOrder.offer("timePasses");
+ assertNull(mCm.getLinkProperties(wifiNetwork.get()));
+ try {
+ verify(mMockNetd, never()).networkDestroy(wifiNetwork.get().getNetId());
+ } catch (RemoteException impossible) {
+ fail();
+ }
+ };
+
+ // 4. After onNetworkDisconnected is called, connectivity APIs report the network is gone,
+ // and netd has been told to destroy it.
+ mWiFiNetworkAgent.setDisconnectedCallback(() -> {
+ eventOrder.offer("onNetworkDisconnected");
+ assertNull(mCm.getLinkProperties(wifiNetwork.get()));
+ try {
+ verify(mMockNetd).networkDestroy(wifiNetwork.get().getNetId());
+ } catch (RemoteException impossible) {
+ fail();
+ }
+ });
+
+ // Connect a network, and file a request for it after it has come up, to ensure the nascent
+ // timer is cleared and the test does not have to wait for it. Filing the request after the
+ // network has come up is necessary because ConnectivityService does not appear to clear the
+ // nascent timer if the first request satisfied by the network was filed before the network
+ // connected.
+ // TODO: fix this bug, file the request before connecting, and remove the waitForIdle.
+ mWiFiNetworkAgent.connectWithoutInternet();
+ waitForIdle();
+ mCm.requestNetwork(request, callback);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+
+ // Set teardown delay and make sure CS has processed it.
+ mWiFiNetworkAgent.getNetworkAgent().setTeardownDelayMs(300);
+ waitForIdle();
+
+ // Post the duringTeardown lambda to the handler so it fires while teardown is in progress.
+ // The delay must be long enough it will run after the unregisterNetworkCallback has torn
+ // down the network and started the teardown timer, and short enough that the lambda is
+ // scheduled to run before the teardown timer.
+ final Handler h = new Handler(mCsHandlerThread.getLooper());
+ h.postDelayed(duringTeardown, 150);
+
+ // Disconnect the network and check that events happened in the right order.
+ mCm.unregisterNetworkCallback(callback);
+ assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals("onNetworkUnwanted", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals("timePasses", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals("onNetworkDisconnected", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ mCm.unregisterNetworkCallback(callback);
+ }
+
+ @Test
public void testExplicitlySelected() throws Exception {
NetworkRequest request = new NetworkRequest.Builder()
.clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
@@ -3439,8 +3572,7 @@
@Test
public void testCaptivePortalApi() throws Exception {
- mServiceContext.setPermission(
- android.Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -3474,8 +3606,7 @@
private TestNetworkCallback setupNetworkCallbackAndConnectToWifi() throws Exception {
// Grant NETWORK_SETTINGS permission to be able to receive LinkProperties change callbacks
// with sensitive (captive portal) data
- mServiceContext.setPermission(
- android.Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -3909,8 +4040,7 @@
@Test
public void testRegisterDefaultNetworkCallback() throws Exception {
// NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
- mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
- PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(defaultNetworkCallback);
@@ -4069,8 +4199,7 @@
() -> mCm.registerDefaultNetworkCallbackAsUid(APP1_UID, callback, handler));
callback.assertNoCallback();
- mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
- PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
mCm.registerSystemDefaultNetworkCallback(callback, handler);
callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent);
mCm.unregisterNetworkCallback(callback);
@@ -5391,10 +5520,11 @@
}
@Test
- public void testNetworkCallbackMaximum() {
+ public void testNetworkCallbackMaximum() throws Exception {
final int MAX_REQUESTS = 100;
final int CALLBACKS = 89;
final int INTENTS = 11;
+ final int SYSTEM_ONLY_MAX_REQUESTS = 250;
assertEquals(MAX_REQUESTS, CALLBACKS + INTENTS);
NetworkRequest networkRequest = new NetworkRequest.Builder().build();
@@ -5443,6 +5573,33 @@
new Intent("d"), FLAG_IMMUTABLE))
);
+ // The system gets another SYSTEM_ONLY_MAX_REQUESTS slots.
+ final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
+ withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> {
+ ArrayList<NetworkCallback> systemRegistered = new ArrayList<>();
+ for (int i = 0; i < SYSTEM_ONLY_MAX_REQUESTS - 1; i++) {
+ NetworkCallback cb = new NetworkCallback();
+ if (i % 2 == 0) {
+ mCm.registerDefaultNetworkCallbackAsUid(1000000 + i, cb, handler);
+ } else {
+ mCm.registerNetworkCallback(networkRequest, cb);
+ }
+ systemRegistered.add(cb);
+ }
+ waitForIdle();
+
+ assertThrows(TooManyRequestsException.class, () ->
+ mCm.registerDefaultNetworkCallbackAsUid(1001042, new NetworkCallback(),
+ handler));
+ assertThrows(TooManyRequestsException.class, () ->
+ mCm.registerNetworkCallback(networkRequest, new NetworkCallback()));
+
+ for (NetworkCallback callback : systemRegistered) {
+ mCm.unregisterNetworkCallback(callback);
+ }
+ waitForIdle(); // Wait for requests to be unregistered before giving up the permission.
+ });
+
for (Object o : registered) {
if (o instanceof NetworkCallback) {
mCm.unregisterNetworkCallback((NetworkCallback)o);
@@ -5469,6 +5626,30 @@
waitForIdle();
for (int i = 0; i < MAX_REQUESTS; i++) {
+ NetworkCallback networkCallback = new NetworkCallback();
+ mCm.registerDefaultNetworkCallback(networkCallback);
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+ waitForIdle();
+
+ for (int i = 0; i < MAX_REQUESTS; i++) {
+ NetworkCallback networkCallback = new NetworkCallback();
+ mCm.registerDefaultNetworkCallback(networkCallback);
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+ waitForIdle();
+
+ withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> {
+ for (int i = 0; i < MAX_REQUESTS; i++) {
+ NetworkCallback networkCallback = new NetworkCallback();
+ mCm.registerDefaultNetworkCallbackAsUid(1000000 + i, networkCallback,
+ new Handler(ConnectivityThread.getInstanceLooper()));
+ mCm.unregisterNetworkCallback(networkCallback);
+ }
+ });
+ waitForIdle();
+
+ for (int i = 0; i < MAX_REQUESTS; i++) {
final PendingIntent pendingIntent = PendingIntent.getBroadcast(
mContext, 0 /* requestCode */, new Intent("e" + i), FLAG_IMMUTABLE);
mCm.requestNetwork(networkRequest, pendingIntent);
@@ -6461,8 +6642,7 @@
@Test
public void testVpnNetworkActive() throws Exception {
// NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback.
- mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
- PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
final int uid = Process.myUid();
@@ -6954,8 +7134,7 @@
@Test
public void testRestrictedProfileAffectsVpnUidRanges() throws Exception {
// NETWORK_SETTINGS is necessary to see the UID ranges in NetworkCapabilities.
- mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
- PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
final NetworkRequest request = new NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_NOT_VPN)
@@ -7041,8 +7220,7 @@
mServiceContext.setPermission(
Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
// Necessary to see the UID ranges in NetworkCapabilities.
- mServiceContext.setPermission(
- Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
final NetworkRequest request = new NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_NOT_VPN)
@@ -7277,6 +7455,20 @@
mMockVpn.disconnect();
}
+ private class DetailedBlockedStatusCallback extends TestNetworkCallback {
+ public void expectAvailableThenValidatedCallbacks(HasNetwork n, int blockedStatus) {
+ super.expectAvailableThenValidatedCallbacks(n.getNetwork(), blockedStatus, TIMEOUT_MS);
+ }
+ public void expectBlockedStatusCallback(HasNetwork n, int blockedStatus) {
+ // This doesn't work:
+ // super.expectBlockedStatusCallback(blockedStatus, n.getNetwork());
+ super.expectBlockedStatusCallback(blockedStatus, n.getNetwork(), TIMEOUT_MS);
+ }
+ public void onBlockedStatusChanged(Network network, int blockedReasons) {
+ getHistory().add(new CallbackEntry.BlockedStatusInt(network, blockedReasons));
+ }
+ }
+
@Test
public void testNetworkBlockedStatus() throws Exception {
final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
@@ -7284,11 +7476,16 @@
.addTransportType(TRANSPORT_CELLULAR)
.build();
mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
+ final DetailedBlockedStatusCallback detailedCallback = new DetailedBlockedStatusCallback();
+ mCm.registerNetworkCallback(cellRequest, detailedCallback);
+
mockUidNetworkingBlocked();
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
+ detailedCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent,
+ BLOCKED_REASON_NONE);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
@@ -7296,17 +7493,23 @@
setBlockedReasonChanged(BLOCKED_REASON_BATTERY_SAVER);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+ detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent,
+ BLOCKED_REASON_BATTERY_SAVER);
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertExtraInfoFromCmBlocked(mCellNetworkAgent);
- // ConnectivityService should cache it not to invoke the callback again.
+ // If blocked state does not change but blocked reason does, the boolean callback is called.
+ // TODO: investigate de-duplicating.
setBlockedReasonChanged(BLOCKED_METERED_REASON_USER_RESTRICTED);
- cellNetworkCallback.assertNoCallback();
+ cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+ detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent,
+ BLOCKED_METERED_REASON_USER_RESTRICTED);
setBlockedReasonChanged(BLOCKED_REASON_NONE);
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+ detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
@@ -7314,6 +7517,8 @@
setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+ detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent,
+ BLOCKED_METERED_REASON_DATA_SAVER);
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -7323,6 +7528,8 @@
mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED);
cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent);
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+ detailedCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent);
+ detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
@@ -7332,6 +7539,10 @@
cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
mCellNetworkAgent);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+ detailedCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED,
+ mCellNetworkAgent);
+ detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent,
+ BLOCKED_METERED_REASON_DATA_SAVER);
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -7339,6 +7550,7 @@
setBlockedReasonChanged(BLOCKED_REASON_NONE);
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+ detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE);
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
@@ -7346,10 +7558,13 @@
setBlockedReasonChanged(BLOCKED_REASON_NONE);
cellNetworkCallback.assertNoCallback();
+ detailedCallback.assertNoCallback();
// Restrict background data. Networking is not blocked because the network is unmetered.
setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER);
cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent);
+ detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent,
+ BLOCKED_METERED_REASON_DATA_SAVER);
assertNull(mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED);
@@ -7359,12 +7574,14 @@
setBlockedReasonChanged(BLOCKED_REASON_NONE);
cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent);
+ detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE);
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertExtraInfoFromCmPresent(mCellNetworkAgent);
setBlockedReasonChanged(BLOCKED_REASON_NONE);
cellNetworkCallback.assertNoCallback();
+ detailedCallback.assertNoCallback();
assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED);
@@ -7491,8 +7708,7 @@
Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED);
mServiceContext.setPermission(
Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
- mServiceContext.setPermission(
- Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
final TestNetworkCallback callback = new TestNetworkCallback();
final NetworkRequest request = new NetworkRequest.Builder()
@@ -7728,8 +7944,7 @@
mServiceContext.setPermission(
Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED);
// For LockdownVpnTracker to call registerSystemDefaultNetworkCallback.
- mServiceContext.setPermission(
- Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
final TestNetworkCallback callback = new TestNetworkCallback();
@@ -7918,12 +8133,12 @@
assertExtraInfoFromCmPresent(mWiFiNetworkAgent);
b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED);
+ b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
mWiFiNetworkAgent.disconnect();
callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
b1.expectBroadcast();
callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI));
- b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED);
mMockVpn.expectStopVpnRunnerPrivileged();
callback.expectCallback(CallbackEntry.LOST, mMockVpn);
b2.expectBroadcast();
@@ -8859,8 +9074,7 @@
private void denyAllLocationPrivilegedPermissions() {
mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
PERMISSION_DENIED);
- mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS,
- PERMISSION_DENIED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
mServiceContext.setPermission(Manifest.permission.NETWORK_STACK,
PERMISSION_DENIED);
mServiceContext.setPermission(Manifest.permission.NETWORK_SETUP_WIZARD,
@@ -9116,7 +9330,7 @@
@Test
public void testCreateForCallerWithLocalMacAddressSanitizedWithSettingsPermission()
throws Exception {
- mServiceContext.setPermission(Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
final TransportInfo transportInfo = mock(TransportInfo.class);
when(transportInfo.getApplicableRedactions())
@@ -10435,8 +10649,7 @@
private void registerDefaultNetworkCallbacks() {
// Using Manifest.permission.NETWORK_SETTINGS for registerSystemDefaultNetworkCallback()
- mServiceContext.setPermission(
- Manifest.permission.NETWORK_SETTINGS, PERMISSION_GRANTED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);
mSystemDefaultNetworkCallback = new TestNetworkCallback();
mDefaultNetworkCallback = new TestNetworkCallback();
mProfileDefaultNetworkCallback = new TestNetworkCallback();
@@ -10446,8 +10659,7 @@
registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback,
TEST_WORK_PROFILE_APP_UID);
// TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well.
- mServiceContext.setPermission(
- Manifest.permission.NETWORK_SETTINGS, PERMISSION_DENIED);
+ mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED);
}
private void unregisterDefaultNetworkCallbacks() {
@@ -10602,7 +10814,7 @@
defaultNetworkCallback.assertNoCallback();
final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
- withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+ withPermission(NETWORK_SETTINGS, () ->
mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
new Handler(ConnectivityThread.getInstanceLooper())));
@@ -10650,7 +10862,7 @@
defaultNetworkCallback.assertNoCallback();
final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
- withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+ withPermission(NETWORK_SETTINGS, () ->
mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
new Handler(ConnectivityThread.getInstanceLooper())));
@@ -10692,7 +10904,7 @@
defaultNetworkCallback.assertNoCallback();
final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback();
- withPermission(Manifest.permission.NETWORK_SETTINGS, () ->
+ withPermission(NETWORK_SETTINGS, () ->
mCm.registerDefaultNetworkCallbackAsUid(TEST_PACKAGE_UID, otherUidDefaultCallback,
new Handler(ConnectivityThread.getInstanceLooper())));
diff --git a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
index 8c5d1d6..8b072c4 100644
--- a/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
+++ b/tests/net/java/com/android/server/connectivity/IpConnectivityMetricsTest.java
@@ -22,7 +22,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
@@ -56,6 +58,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -80,6 +83,12 @@
IpConnectivityMetrics mService;
NetdEventListenerService mNetdListener;
+ private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
@Before
public void setUp() {
@@ -263,14 +272,6 @@
// TODO: instead of comparing textpb to textpb, parse textpb and compare proto to proto.
IpConnectivityLog logger = new IpConnectivityLog(mService.impl);
- NetworkCapabilities ncWifi = new NetworkCapabilities();
- NetworkCapabilities ncCell = new NetworkCapabilities();
- ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
- ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
- when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
- when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
-
ApfStats apfStats = new ApfStats.Builder()
.setDurationMs(45000)
.setReceivedRas(10)
@@ -584,11 +585,21 @@
return buffer.toString();
}
- void connectEvent(int netid, int error, int latencyMs, String ipAddr) throws Exception {
- mNetdListener.onConnectEvent(netid, error, latencyMs, ipAddr, 80, 1);
+ private void setCapabilities(int netId) {
+ final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
+ networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
+ netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
+ }
+
+ void connectEvent(int netId, int error, int latencyMs, String ipAddr) throws Exception {
+ setCapabilities(netId);
+ mNetdListener.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
}
void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+ setCapabilities(netId);
mNetdListener.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
}
diff --git a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 8ccea1a..50aaaee 100644
--- a/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/tests/net/java/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -23,8 +23,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -42,6 +43,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
@@ -61,18 +63,16 @@
NetdEventListenerService mService;
ConnectivityManager mCm;
+ private static final NetworkCapabilities CAPABILITIES_WIFI = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ private static final NetworkCapabilities CAPABILITIES_CELL = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
@Before
public void setUp() {
- NetworkCapabilities ncWifi = new NetworkCapabilities();
- NetworkCapabilities ncCell = new NetworkCapabilities();
- ncWifi.addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
- ncCell.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
-
mCm = mock(ConnectivityManager.class);
- when(mCm.getNetworkCapabilities(new Network(100))).thenReturn(ncWifi);
- when(mCm.getNetworkCapabilities(new Network(101))).thenReturn(ncCell);
-
mService = new NetdEventListenerService(mCm);
}
@@ -470,7 +470,16 @@
assertEquals(want, got);
}
+ private void setCapabilities(int netId) {
+ final ArgumentCaptor<ConnectivityManager.NetworkCallback> networkCallback =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+ verify(mCm).registerNetworkCallback(any(), networkCallback.capture());
+ networkCallback.getValue().onCapabilitiesChanged(new Network(netId),
+ netId == 100 ? CAPABILITIES_WIFI : CAPABILITIES_CELL);
+ }
+
Thread connectEventAction(int netId, int error, int latencyMs, String ipAddr) {
+ setCapabilities(netId);
return new Thread(() -> {
try {
mService.onConnectEvent(netId, error, latencyMs, ipAddr, 80, 1);
@@ -481,6 +490,7 @@
}
void dnsEvent(int netId, int type, int result, int latency) throws Exception {
+ setCapabilities(netId);
mService.onDnsEvent(netId, type, result, latency, "", null, 0, 0);
}
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 9334e2c..eeeb4fb 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -89,6 +89,7 @@
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.UnderlyingNetworkInfo;
+import android.net.TelephonyNetworkSpecifier;
import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.os.ConditionVariable;
import android.os.Handler;
@@ -1280,6 +1281,77 @@
}
@Test
+ public void testDualVilteProviderStats() throws Exception {
+ // Pretend that network comes online.
+ expectDefaultSettings();
+ final int subId1 = 1;
+ final int subId2 = 2;
+ final NetworkStateSnapshot[] states = new NetworkStateSnapshot[]{
+ buildImsState(IMSI_1, subId1, TEST_IFACE),
+ buildImsState(IMSI_2, subId2, TEST_IFACE2)};
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ // Register custom provider and retrieve callback.
+ final TestableNetworkStatsProviderBinder provider =
+ new TestableNetworkStatsProviderBinder();
+ final INetworkStatsProviderCallback cb =
+ mService.registerNetworkStatsProvider("TEST", provider);
+ assertNotNull(cb);
+
+ mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states),
+ new UnderlyingNetworkInfo[0]);
+
+ // Verifies that one requestStatsUpdate will be called during iface update.
+ provider.expectOnRequestStatsUpdate(0 /* unused */);
+
+ // Create some initial traffic and report to the service.
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ final String vtIface1 = NetworkStats.IFACE_VT + subId1;
+ final String vtIface2 = NetworkStats.IFACE_VT + subId2;
+ final NetworkStats expectedStats = new NetworkStats(0L, 1)
+ .addEntry(new NetworkStats.Entry(vtIface1, UID_RED, SET_DEFAULT,
+ TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+ 128L, 2L, 128L, 2L, 1L))
+ .addEntry(new NetworkStats.Entry(vtIface2, UID_RED, SET_DEFAULT,
+ TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+ 64L, 1L, 64L, 1L, 1L));
+ cb.notifyStatsUpdated(0 /* unused */, expectedStats, expectedStats);
+
+ // Make another empty mutable stats object. This is necessary since the new NetworkStats
+ // object will be used to compare with the old one in NetworkStatsRecoder, two of them
+ // cannot be the same object.
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ forcePollAndWaitForIdle();
+
+ // Verifies that one requestStatsUpdate and setAlert will be called during polling.
+ provider.expectOnRequestStatsUpdate(0 /* unused */);
+ provider.expectOnSetAlert(MB_IN_BYTES);
+
+ // Verifies that service recorded history, does not verify uid tag part.
+ assertUidTotal(sTemplateImsi1, UID_RED, 128L, 2L, 128L, 2L, 1);
+
+ // Verifies that onStatsUpdated updates the stats accordingly.
+ NetworkStats stats = mSession.getSummaryForAllUid(
+ sTemplateImsi1, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(1, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L);
+
+ stats = mSession.getSummaryForAllUid(
+ sTemplateImsi2, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(1, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L);
+
+ // Verifies that unregister the callback will remove the provider from service.
+ cb.unregister();
+ forcePollAndWaitForIdle();
+ provider.assertNoCallback();
+ }
+
+ @Test
public void testStatsProviderSetAlert() throws Exception {
// Pretend that network comes online.
expectDefaultSettings();
@@ -1616,6 +1688,20 @@
TYPE_MOBILE);
}
+ private static NetworkStateSnapshot buildImsState(
+ String subscriberId, int subId, String ifaceName) {
+ final LinkProperties prop = new LinkProperties();
+ prop.setInterfaceName(ifaceName);
+ final NetworkCapabilities capabilities = new NetworkCapabilities();
+ capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED, true);
+ capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING, true);
+ capabilities.setCapability(NetworkCapabilities.NET_CAPABILITY_IMS, true);
+ capabilities.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
+ capabilities.setNetworkSpecifier(new TelephonyNetworkSpecifier(subId));
+ return new NetworkStateSnapshot(
+ MOBILE_NETWORK, capabilities, prop, subscriberId, TYPE_MOBILE);
+ }
+
private long getElapsedRealtime() {
return mElapsedRealtime;
}