Don\'t use framework permission strings for netd permissions. am: 5c36486ea0 am: f51479864b am: e7267a355d am: d3b6e2d790
am: c987320b4a
* commit 'c987320b4ad1a6fea7bb948cf045e6afb768d339':
Don't use framework permission strings for netd permissions.
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
new file mode 100644
index 0000000..ee05f28
--- /dev/null
+++ b/core/java/android/net/CaptivePortal.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed urnder the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
+ * activity to indicate to the system different outcomes of captive portal sign in. This class is
+ * passed as an extra named {@link ConnectivityManager#EXTRA_CAPTIVE_PORTAL} with the
+ * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} activity.
+ */
+public class CaptivePortal implements Parcelable {
+ /** @hide */
+ public static final int APP_RETURN_DISMISSED = 0;
+ /** @hide */
+ public static final int APP_RETURN_UNWANTED = 1;
+ /** @hide */
+ public static final int APP_RETURN_WANTED_AS_IS = 2;
+
+ private final IBinder mBinder;
+
+ /** @hide */
+ public CaptivePortal(IBinder binder) {
+ mBinder = binder;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeStrongBinder(mBinder);
+ }
+
+ public static final Parcelable.Creator<CaptivePortal> CREATOR
+ = new Parcelable.Creator<CaptivePortal>() {
+ @Override
+ public CaptivePortal createFromParcel(Parcel in) {
+ return new CaptivePortal(in.readStrongBinder());
+ }
+
+ @Override
+ public CaptivePortal[] newArray(int size) {
+ return new CaptivePortal[size];
+ }
+ };
+
+ /**
+ * Indicate to the system that the captive portal has been
+ * dismissed. In response the framework will re-evaluate the network's
+ * connectivity and might take further action thereafter.
+ */
+ public void reportCaptivePortalDismissed() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_DISMISSED);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Indicate to the system that the user does not want to pursue signing in to the
+ * captive portal and the system should continue to prefer other networks
+ * without captive portals for use as the default active data network. The
+ * system will not retest the network for a captive portal so as to avoid
+ * disturbing the user with further sign in to network notifications.
+ */
+ public void ignoreNetwork() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_UNWANTED);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Indicate to the system the user wants to use this network as is, even though
+ * the captive portal is still in place. The system will treat the network
+ * as if it did not have a captive portal when selecting the network to use
+ * as the default active data network. This may result in this network
+ * becoming the default active data network, which could disrupt network
+ * connectivity for apps because the captive portal is still in place.
+ * @hide
+ */
+ public void useNetwork() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS);
+ } catch (RemoteException e) {
+ }
+ }
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index f5457bd..8e55736 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -22,10 +22,10 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.NetworkUtils;
import android.os.Binder;
import android.os.Build.VERSION_CODES;
-import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -34,11 +34,11 @@
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.Log;
@@ -100,14 +100,33 @@
public static final String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
/**
- * Identical to {@link #CONNECTIVITY_ACTION} broadcast, but sent without any
- * historic {@link Settings.Global#CONNECTIVITY_CHANGE_DELAY}.
- *
- * @hide
+ * The device has connected to a network that has presented a captive
+ * portal, which is blocking Internet connectivity. The user was presented
+ * with a notification that network sign in is required,
+ * and the user invoked the notification's action indicating they
+ * desire to sign in to the network. Apps handling this activity should
+ * facilitate signing in to the network. This action includes a
+ * {@link Network} typed extra called {@link #EXTRA_NETWORK} that represents
+ * the network presenting the captive portal; all communication with the
+ * captive portal must be done using this {@code Network} object.
+ * <p/>
+ * This activity includes a {@link CaptivePortal} extra named
+ * {@link #EXTRA_CAPTIVE_PORTAL} that can be used to indicate different
+ * outcomes of the captive portal sign in to the system:
+ * <ul>
+ * <li> When the app handling this action believes the user has signed in to
+ * the network and the captive portal has been dismissed, the app should
+ * call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can
+ * reevaluate the network. If reevaluation finds the network no longer
+ * subject to a captive portal, the network may become the default active
+ * data network. </li>
+ * <li> When the app handling this action believes the user explicitly wants
+ * to ignore the captive portal and the network, the app should call
+ * {@link CaptivePortal#ignoreNetwork}. </li>
+ * </ul>
*/
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String CONNECTIVITY_ACTION_IMMEDIATE =
- "android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE";
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
/**
* The lookup key for a {@link NetworkInfo} object. Retrieve with
@@ -115,8 +134,7 @@
*
* @deprecated Since {@link NetworkInfo} can vary based on UID, applications
* should always obtain network information through
- * {@link #getActiveNetworkInfo()} or
- * {@link #getAllNetworkInfo()}.
+ * {@link #getActiveNetworkInfo()}.
* @see #EXTRA_NETWORK_TYPE
*/
@Deprecated
@@ -124,8 +142,6 @@
/**
* Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast.
- * Can be used with {@link #getNetworkInfo(int)} to get {@link NetworkInfo}
- * state based on the calling application.
*
* @see android.content.Intent#getIntExtra(String, int)
*/
@@ -173,7 +189,15 @@
* {@hide}
*/
public static final String EXTRA_INET_CONDITION = "inetCondition";
-
+ /**
+ * The lookup key for a {@link CaptivePortal} object included with the
+ * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} intent. The {@code CaptivePortal}
+ * object can be used to either indicate to the system that the captive
+ * portal has been dismissed or that the user does not want to pursue
+ * signing in to captive portal. Retrieve it with
+ * {@link android.content.Intent#getParcelableExtra(String)}.
+ */
+ public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
/**
* Broadcast action to indicate the change of data activity status
* (idle or active) on a network in a recent period.
@@ -287,6 +311,14 @@
public static final String EXTRA_IS_CAPTIVE_PORTAL = "captivePortal";
/**
+ * Action used to display a dialog that asks the user whether to connect to a network that is
+ * not validated. This intent is used to start the dialog in settings via startActivity.
+ *
+ * @hide
+ */
+ public static final String ACTION_PROMPT_UNVALIDATED = "android.net.conn.PROMPT_UNVALIDATED";
+
+ /**
* The absence of a connection type.
* @hide
*/
@@ -309,6 +341,10 @@
* same network interface as {@link #TYPE_MOBILE} or it may use a different
* one. This is used by applications needing to talk to the carrier's
* Multimedia Messaging Service servers.
+ *
+ * @deprecated Applications should instead use
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
+ * provides the {@link NetworkCapabilities#NET_CAPABILITY_MMS} capability.
*/
public static final int TYPE_MOBILE_MMS = 2;
/**
@@ -316,6 +352,10 @@
* same network interface as {@link #TYPE_MOBILE} or it may use a different
* one. This is used by applications needing to talk to the carrier's
* Secure User Plane Location servers for help locating the device.
+ *
+ * @deprecated Applications should instead use
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
+ * provides the {@link NetworkCapabilities#NET_CAPABILITY_SUPL} capability.
*/
public static final int TYPE_MOBILE_SUPL = 3;
/**
@@ -328,9 +368,11 @@
/**
* A High Priority Mobile data connection. This network type uses the
* same network interface as {@link #TYPE_MOBILE} but the routing setup
- * is different. Only requesting processes will have access to the
- * Mobile DNS servers and only IP's explicitly requested via {@link #requestRouteToHost}
- * will route over this interface if no default route exists.
+ * is different.
+ *
+ * @deprecated Applications should instead use
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} to request a network that
+ * uses the {@link NetworkCapabilities#TRANSPORT_CELLULAR} transport.
*/
public static final int TYPE_MOBILE_HIPRI = 5;
/**
@@ -390,8 +432,9 @@
*/
public static final int TYPE_MOBILE_IA = 14;
-/**
- * Emergency PDN connection for emergency calls
+ /**
+ * Emergency PDN connection for emergency services. This
+ * may include IMS and MMS in emergency situations.
* {@hide}
*/
public static final int TYPE_MOBILE_EMERGENCY = 15;
@@ -449,12 +492,16 @@
*/
private static ConnectivityManager sInstance;
+ private final Context mContext;
+
private INetworkManagementService mNMService;
/**
* Tests if a given integer represents a valid network type.
* @param networkType the type to be tested
* @return a boolean. {@code true} if the type is valid, else {@code false}
+ * @deprecated All APIs accepting a network type are deprecated. There should be no need to
+ * validate a network type.
*/
public static boolean isNetworkTypeValid(int networkType) {
return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
@@ -504,6 +551,8 @@
return "MOBILE_EMERGENCY";
case TYPE_PROXY:
return "PROXY";
+ case TYPE_VPN:
+ return "VPN";
default:
return Integer.toString(type);
}
@@ -566,11 +615,11 @@
/**
* Retrieves the current preferred network type.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return an integer representing the preferred network type
*
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* @deprecated Functionality has been removed as it no longer makes sense,
* with many more than two networks - we'd need an array to express
* preference. Instead we use dynamic network properties of
@@ -586,12 +635,11 @@
* You should always check {@link NetworkInfo#isConnected()} before initiating
* network traffic. This may return {@code null} when there is no default
* network.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return a {@link NetworkInfo} object for the current default network
- * or {@code null} if no network default network is currently active
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * or {@code null} if no default network is currently active
*/
public NetworkInfo getActiveNetworkInfo() {
try {
@@ -602,16 +650,36 @@
}
/**
+ * Returns a {@link Network} object corresponding to the currently active
+ * default data network. In the event that the current active default data
+ * network disconnects, the returned {@code Network} object will no longer
+ * be usable. This will return {@code null} when there is no default
+ * network.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ *
+ * @return a {@link Network} object for the current default network or
+ * {@code null} if no default network is currently active
+ */
+ public Network getActiveNetwork() {
+ try {
+ return mService.getActiveNetwork();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
* Returns details about the currently active default data network
* for a given uid. This is for internal use only to avoid spying
* other apps.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
*
* @return a {@link NetworkInfo} object for the current default network
* for the given uid or {@code null} if no default network is
* available for the specified uid.
*
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
* {@hide}
*/
public NetworkInfo getActiveNetworkInfoForUid(int uid) {
@@ -625,6 +693,8 @@
/**
* Returns connection status information about a particular
* network type.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @param networkType integer specifying which networkType in
* which you're interested.
@@ -632,8 +702,9 @@
* network type or {@code null} if the type is not
* supported by the device.
*
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * @deprecated This method does not support multiple connected networks
+ * of the same type. Use {@link #getAllNetworks} and
+ * {@link #getNetworkInfo(android.net.Network)} instead.
*/
public NetworkInfo getNetworkInfo(int networkType) {
try {
@@ -646,15 +717,14 @@
/**
* Returns connection status information about a particular
* Network.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @param network {@link Network} specifying which network
* in which you're interested.
* @return a {@link NetworkInfo} object for the requested
* network or {@code null} if the {@code Network}
* is not valid.
- *
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*/
public NetworkInfo getNetworkInfo(Network network) {
try {
@@ -667,12 +737,15 @@
/**
* Returns connection status information about all network
* types supported by the device.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return an array of {@link NetworkInfo} objects. Check each
* {@link NetworkInfo#getType} for which type each applies.
*
- * <p>This method requires the caller to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * @deprecated This method does not support multiple connected networks
+ * of the same type. Use {@link #getAllNetworks} and
+ * {@link #getNetworkInfo(android.net.Network)} instead.
*/
public NetworkInfo[] getAllNetworkInfo() {
try {
@@ -690,6 +763,9 @@
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @hide
+ * @deprecated This method does not support multiple connected networks
+ * of the same type. Use {@link #getAllNetworks} and
+ * {@link #getNetworkInfo(android.net.Network)} instead.
*/
public Network getNetworkForType(int networkType) {
try {
@@ -702,11 +778,10 @@
/**
* Returns an array of all {@link Network} currently tracked by the
* framework.
- *
- * @return an array of {@link Network} objects.
- *
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ *
+ * @return an array of {@link Network} objects.
*/
public Network[] getAllNetworks() {
try {
@@ -717,7 +792,7 @@
}
/**
- * Returns an array of of {@link NetworkCapabilities} objects, representing
+ * Returns an array of {@link android.net.NetworkCapabilities} objects, representing
* the Networks that applications run by the given user will use by default.
* @hide
*/
@@ -730,37 +805,14 @@
}
/**
- * Returns details about the Provisioning or currently active default data network. When
- * connected, this network is the default route for outgoing connections.
- * You should always check {@link NetworkInfo#isConnected()} before initiating
- * network traffic. This may return {@code null} when there is no default
- * network.
- *
- * @return a {@link NetworkInfo} object for the current default network
- * or {@code null} if no network default network is currently active
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
- *
- * {@hide}
- */
- public NetworkInfo getProvisioningOrActiveNetworkInfo() {
- try {
- return mService.getProvisioningOrActiveNetworkInfo();
- } catch (RemoteException e) {
- return null;
- }
- }
-
- /**
* Returns the IP information for the current default network.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return a {@link LinkProperties} object describing the IP info
* for the current default network, or {@code null} if there
* is no current default network.
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public LinkProperties getActiveLinkProperties() {
@@ -773,15 +825,19 @@
/**
* Returns the IP information for a given network type.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @param networkType the network type of interest.
* @return a {@link LinkProperties} object describing the IP info
* for the given networkType, or {@code null} if there is
* no current default network.
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
+ * @deprecated This method does not support multiple connected networks
+ * of the same type. Use {@link #getAllNetworks},
+ * {@link #getNetworkInfo(android.net.Network)}, and
+ * {@link #getLinkProperties(android.net.Network)} instead.
*/
public LinkProperties getLinkProperties(int networkType) {
try {
@@ -794,10 +850,12 @@
/**
* Get the {@link LinkProperties} for the given {@link Network}. This
* will return {@code null} if the network is unknown.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @param network The {@link Network} object identifying the network in question.
* @return The {@link LinkProperties} for the network, or {@code null}.
- **/
+ */
public LinkProperties getLinkProperties(Network network) {
try {
return mService.getLinkProperties(network);
@@ -807,11 +865,13 @@
}
/**
- * Get the {@link NetworkCapabilities} for the given {@link Network}. This
+ * Get the {@link android.net.NetworkCapabilities} for the given {@link Network}. This
* will return {@code null} if the network is unknown.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @param network The {@link Network} object identifying the network in question.
- * @return The {@link NetworkCapabilities} for the network, or {@code null}.
+ * @return The {@link android.net.NetworkCapabilities} for the network, or {@code null}.
*/
public NetworkCapabilities getNetworkCapabilities(Network network) {
try {
@@ -822,48 +882,6 @@
}
/**
- * Tells each network type to set its radio power state as directed.
- *
- * @param turnOn a boolean, {@code true} to turn the radios on,
- * {@code false} to turn them off.
- * @return a boolean, {@code true} indicating success. All network types
- * will be tried, even if some fail.
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
- * {@hide}
- */
-// TODO - check for any callers and remove
-// public boolean setRadios(boolean turnOn) {
-// try {
-// return mService.setRadios(turnOn);
-// } catch (RemoteException e) {
-// return false;
-// }
-// }
-
- /**
- * Tells a given networkType to set its radio power state as directed.
- *
- * @param networkType the int networkType of interest.
- * @param turnOn a boolean, {@code true} to turn the radio on,
- * {@code} false to turn it off.
- * @return a boolean, {@code true} indicating success.
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
- * {@hide}
- */
-// TODO - check for any callers and remove
-// public boolean setRadio(int networkType, boolean turnOn) {
-// try {
-// return mService.setRadio(networkType, turnOn);
-// } catch (RemoteException e) {
-// return false;
-// }
-// }
-
- /**
* Tells the underlying networking system that the caller wants to
* begin using the named feature. The interpretation of {@code feature}
* is completely up to each networking implementation.
@@ -876,9 +894,13 @@
* implementation+feature combination, except that the value {@code -1}
* always indicates failure.
*
- * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
+ * @deprecated Deprecated in favor of the cleaner
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} API.
+ * In {@link VERSION_CODES#M}, and above, this method is unsupported and will
+ * throw {@code UnsupportedOperationException} if called.
*/
public int startUsingNetworkFeature(int networkType, String feature) {
+ checkLegacyRoutingApiAccess();
NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
if (netCap == null) {
Log.d(TAG, "Can't satisfy startUsingNetworkFeature for " + networkType + ", " +
@@ -923,9 +945,12 @@
* implementation+feature combination, except that the value {@code -1}
* always indicates failure.
*
- * @deprecated Deprecated in favor of the cleaner {@link #requestNetwork} api.
+ * @deprecated Deprecated in favor of the cleaner {@link #unregisterNetworkCallback} API.
+ * In {@link VERSION_CODES#M}, and above, this method is unsupported and will
+ * throw {@code UnsupportedOperationException} if called.
*/
public int stopUsingNetworkFeature(int networkType, String feature) {
+ checkLegacyRoutingApiAccess();
NetworkCapabilities netCap = networkCapabilitiesForFeature(networkType, feature);
if (netCap == null) {
Log.d(TAG, "Can't satisfy stopUsingNetworkFeature for " + networkType + ", " +
@@ -1013,11 +1038,13 @@
type = "enableDUN";
result = TYPE_MOBILE_DUN;
} else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL)) {
- type = "enableSUPL";
+ type = "enableSUPL";
result = TYPE_MOBILE_SUPL;
- } else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
- type = "enableMMS";
- result = TYPE_MOBILE_MMS;
+ // back out this hack for mms as they no longer need this and it's causing
+ // device slowdowns - b/23350688 (note, supl still needs this)
+ //} else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS)) {
+ // type = "enableMMS";
+ // result = TYPE_MOBILE_MMS;
} else if (netCap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
type = "enableHIPRI";
result = TYPE_MOBILE_HIPRI;
@@ -1165,8 +1192,11 @@
* @param hostAddress the IP address of the host to which the route is desired
* @return {@code true} on success, {@code false} on failure
*
- * @deprecated Deprecated in favor of the {@link #requestNetwork},
- * {@link #setProcessDefaultNetwork} and {@link Network#getSocketFactory} api.
+ * @deprecated Deprecated in favor of the
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)},
+ * {@link #bindProcessToNetwork} and {@link Network#getSocketFactory} API.
+ * In {@link VERSION_CODES#M}, and above, this method is unsupported and will
+ * throw {@code UnsupportedOperationException} if called.
*/
public boolean requestRouteToHost(int networkType, int hostAddress) {
return requestRouteToHostAddress(networkType, NetworkUtils.intToInetAddress(hostAddress));
@@ -1184,9 +1214,10 @@
* @return {@code true} on success, {@code false} on failure
* @hide
* @deprecated Deprecated in favor of the {@link #requestNetwork} and
- * {@link #setProcessDefaultNetwork} api.
+ * {@link #bindProcessToNetwork} API.
*/
public boolean requestRouteToHostAddress(int networkType, InetAddress hostAddress) {
+ checkLegacyRoutingApiAccess();
try {
return mService.requestRouteToHostAddress(networkType, hostAddress.getAddress());
} catch (RemoteException e) {
@@ -1237,7 +1268,7 @@
* network is active. Quota status can change rapidly, so these values
* shouldn't be cached.
*
- * <p>This method requires the call to hold the permission
+ * <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @hide
@@ -1309,7 +1340,7 @@
* listener.
* <p>
* If the process default network has been set with
- * {@link ConnectivityManager#setProcessDefaultNetwork} this function will not
+ * {@link ConnectivityManager#bindProcessToNetwork} this function will not
* reflect the process's default, but the system default.
*
* @param l The listener to be told when the network is active.
@@ -1365,7 +1396,8 @@
/**
* {@hide}
*/
- public ConnectivityManager(IConnectivityManager service) {
+ public ConnectivityManager(Context context, IConnectivityManager service) {
+ mContext = checkNotNull(context, "missing context");
mService = checkNotNull(service, "missing IConnectivityManager");
sInstance = this;
}
@@ -1384,8 +1416,9 @@
context.enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL, "ConnectivityService");
} else {
- context.enforceCallingOrSelfPermission(
- android.Manifest.permission.CHANGE_NETWORK_STATE, "ConnectivityService");
+ int uid = Binder.getCallingUid();
+ Settings.checkAndNoteChangeNetworkStateOperation(context, uid, Settings
+ .getPackageNameForUid(context, uid), true);
}
}
@@ -1394,21 +1427,30 @@
* situations where a Context pointer is unavailable.
* @hide
*/
- public static ConnectivityManager getInstance() {
- if (sInstance == null) {
+ static ConnectivityManager getInstanceOrNull() {
+ return sInstance;
+ }
+
+ /**
+ * @deprecated - use getSystemService. This is a kludge to support static access in certain
+ * situations where a Context pointer is unavailable.
+ * @hide
+ */
+ private static ConnectivityManager getInstance() {
+ if (getInstanceOrNull() == null) {
throw new IllegalStateException("No ConnectivityManager yet constructed");
}
- return sInstance;
+ return getInstanceOrNull();
}
/**
* Get the set of tetherable, available interfaces. This list is limited by
* device configuration and current interface existence.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return an array of 0 or more Strings of tetherable interface names.
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableIfaces() {
@@ -1421,11 +1463,11 @@
/**
* Get the set of tethered interfaces.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return an array of 0 or more String of currently tethered interface names.
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetheredIfaces() {
@@ -1443,12 +1485,12 @@
* may cause them to reset to the available state.
* {@link ConnectivityManager#getLastTetherError} can be used to get more
* information on the cause of the errors.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return an array of 0 or more String indicating the interface names
* which failed to tether.
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetheringErroredIfaces() {
@@ -1481,12 +1523,12 @@
* allowed between the tethered devices and this device, though upstream net
* access will of course fail until an upstream network interface becomes
* active.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
*
* @param iface the interface name to tether.
* @return error a {@code TETHER_ERROR} value indicating success or failure type
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
public int tether(String iface) {
@@ -1499,12 +1541,12 @@
/**
* Stop tethering the named interface.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
*
* @param iface the interface name to untether.
* @return error a {@code TETHER_ERROR} value indicating success or failure type
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
public int untether(String iface) {
@@ -1519,11 +1561,11 @@
* Check if the device allows for tethering. It may be disabled via
* {@code ro.tether.denied} system property, Settings.TETHER_SUPPORTED or
* due to device configuration.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return a boolean - {@code true} indicating Tethering is supported.
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public boolean isTetheringSupported() {
@@ -1538,12 +1580,12 @@
* Get the list of regular expressions that define any tetherable
* USB network interfaces. If USB tethering is not supported by the
* device, this list should be empty.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return an array of 0 or more regular expression Strings defining
* what interfaces are considered tetherable usb interfaces.
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableUsbRegexs() {
@@ -1558,12 +1600,12 @@
* Get the list of regular expressions that define any tetherable
* Wifi network interfaces. If Wifi tethering is not supported by the
* device, this list should be empty.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return an array of 0 or more regular expression Strings defining
* what interfaces are considered tetherable wifi interfaces.
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableWifiRegexs() {
@@ -1578,12 +1620,12 @@
* Get the list of regular expressions that define any tetherable
* Bluetooth network interfaces. If Bluetooth tethering is not supported by the
* device, this list should be empty.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return an array of 0 or more regular expression Strings defining
* what interfaces are considered tetherable bluetooth interfaces.
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public String[] getTetherableBluetoothRegexs() {
@@ -1600,12 +1642,12 @@
* attempt to switch to Rndis and subsequently tether the resulting
* interface on {@code true} or turn off tethering and switch off
* Rndis on {@code false}.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
*
* @param enable a boolean - {@code true} to enable tethering
* @return error a {@code TETHER_ERROR} value indicating success or failure type
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* {@hide}
*/
public int setUsbTethering(boolean enable) {
@@ -1642,13 +1684,13 @@
/**
* Get a more detailed error code after a Tethering or Untethering
* request asynchronously failed.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @param iface The name of the interface of interest
* @return error The error code of the last error tethering or untethering the named
* interface
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* {@hide}
*/
public int getLastTetherError(String iface) {
@@ -1662,12 +1704,11 @@
/**
* Report network connectivity status. This is currently used only
* to alter status bar UI.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#STATUS_BAR}.
*
* @param networkType The type of network you want to report on
* @param percentage The quality of the connection 0 is bad, 100 is good
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#STATUS_BAR}.
* {@hide}
*/
public void reportInetCondition(int networkType, int percentage) {
@@ -1685,10 +1726,33 @@
*
* @param network The {@link Network} the application was attempting to use
* or {@code null} to indicate the current default network.
+ * @deprecated Use {@link #reportNetworkConnectivity} which allows reporting both
+ * working and non-working connectivity.
*/
public void reportBadNetwork(Network network) {
try {
- mService.reportBadNetwork(network);
+ // One of these will be ignored because it matches system's current state.
+ // The other will trigger the necessary reevaluation.
+ mService.reportNetworkConnectivity(network, true);
+ mService.reportNetworkConnectivity(network, false);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Report to the framework whether a network has working connectivity.
+ * This provides a hint to the system that a particular network is providing
+ * working connectivity or not. In response the framework may re-evaluate
+ * the network's connectivity and might take further action thereafter.
+ *
+ * @param network The {@link Network} the application was attempting to use
+ * or {@code null} to indicate the current default network.
+ * @param hasConnectivity {@code true} if the application was able to successfully access the
+ * Internet using {@code network} or {@code false} if not.
+ */
+ public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
+ try {
+ mService.reportNetworkConnectivity(network, hasConnectivity);
} catch (RemoteException e) {
}
}
@@ -1698,12 +1762,11 @@
* for typical HTTP proxies - they are general network dependent. However if you're
* doing something unusual like general internal filtering this may be useful. On
* a private network where the proxy is not accessible, you may break HTTP using this.
- *
- * @param p The a {@link ProxyInfo} object defining the new global
- * HTTP proxy. A {@code null} value will clear the global HTTP proxy.
- *
- * <p>This method requires the call to hold the permission
+ * <p>This method requires the caller to hold the permission
* android.Manifest.permission#CONNECTIVITY_INTERNAL.
+ *
+ * @param p A {@link ProxyInfo} object defining the new global
+ * HTTP proxy. A {@code null} value will clear the global HTTP proxy.
* @hide
*/
public void setGlobalProxy(ProxyInfo p) {
@@ -1718,9 +1781,6 @@
*
* @return {@link ProxyInfo} for the current global HTTP proxy or {@code null}
* if no global HTTP proxy is set.
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* @hide
*/
public ProxyInfo getGlobalProxy() {
@@ -1732,29 +1792,38 @@
}
/**
+ * Retrieve the global HTTP proxy, or if no global HTTP proxy is set, a
+ * network-specific HTTP proxy. If {@code network} is null, the
+ * network-specific proxy returned is the proxy of the default active
+ * network.
+ *
+ * @return {@link ProxyInfo} for the current global HTTP proxy, or if no
+ * global HTTP proxy is set, {@code ProxyInfo} for {@code network},
+ * or when {@code network} is {@code null},
+ * the {@code ProxyInfo} for the default active network. Returns
+ * {@code null} when no proxy applies or the caller doesn't have
+ * permission to use {@code network}.
+ * @hide
+ */
+ public ProxyInfo getProxyForNetwork(Network network) {
+ try {
+ return mService.getProxyForNetwork(network);
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
+
+ /**
* Get the current default HTTP proxy settings. If a global proxy is set it will be returned,
* otherwise if this process is bound to a {@link Network} using
- * {@link #setProcessDefaultNetwork} then that {@code Network}'s proxy is returned, otherwise
+ * {@link #bindProcessToNetwork} then that {@code Network}'s proxy is returned, otherwise
* the default network's proxy is returned.
*
* @return the {@link ProxyInfo} for the current HTTP proxy, or {@code null} if no
* HTTP proxy is active.
- * @hide
*/
public ProxyInfo getDefaultProxy() {
- final Network network = getProcessDefaultNetwork();
- if (network != null) {
- final ProxyInfo globalProxy = getGlobalProxy();
- if (globalProxy != null) return globalProxy;
- final LinkProperties lp = getLinkProperties(network);
- if (lp != null) return lp.getHttpProxy();
- return null;
- }
- try {
- return mService.getDefaultProxy();
- } catch (RemoteException e) {
- return null;
- }
+ return getProxyForNetwork(getBoundNetworkForProcess());
}
/**
@@ -1764,12 +1833,12 @@
* hardware supports it. For example a GSM phone without a SIM
* should still return {@code true} for mobile data, but a wifi only
* tablet would return {@code false}.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @param networkType The network type we'd like to check
* @return {@code true} if supported, else {@code false}
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
* @hide
*/
public boolean isNetworkSupported(int networkType) {
@@ -1786,12 +1855,11 @@
* battery/performance issues. You should check this before doing large
* data transfers, and warn the user or delay the operation until another
* network is available.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @return {@code true} if large transfers should be avoided, otherwise
* {@code false}.
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*/
public boolean isActiveNetworkMetered() {
try {
@@ -1819,25 +1887,6 @@
}
/**
- * Signal that the captive portal check on the indicated network
- * is complete and whether its a captive portal or not.
- *
- * @param info the {@link NetworkInfo} object for the networkType
- * in question.
- * @param isCaptivePortal true/false.
- *
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
- * {@hide}
- */
- public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
- try {
- mService.captivePortalCheckCompleted(info, isCaptivePortal);
- } catch (RemoteException e) {
- }
- }
-
- /**
* Check mobile provisioning.
*
* @param suggestedTimeOutMs, timeout in milliseconds
@@ -1869,24 +1918,13 @@
}
/**
- * Get the mobile redirected provisioning url.
- * {@hide}
- */
- public String getMobileRedirectedProvisioningUrl() {
- try {
- return mService.getMobileRedirectedProvisioningUrl();
- } catch (RemoteException e) {
- }
- return null;
- }
-
- /**
* Set sign in error notification to visible or in visible
*
* @param visible
* @param networkType
*
* {@hide}
+ * @deprecated Doesn't properly deal with multiple connected networks of the same type.
*/
public void setProvisioningNotificationVisible(boolean visible, int networkType,
String action) {
@@ -1898,11 +1936,11 @@
/**
* Set the value for enabling/disabling airplane mode
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
*
* @param enable whether to enable airplane mode or not
*
- * <p>This method requires the call to hold the permission
- * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
* @hide
*/
public void setAirplaneMode(boolean enable) {
@@ -1926,12 +1964,18 @@
} catch (RemoteException e) { }
}
- /** {@hide} */
- public void registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ /**
+ * @hide
+ * Register a NetworkAgent with ConnectivityService.
+ * @return NetID corresponding to NetworkAgent.
+ */
+ public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
NetworkCapabilities nc, int score, NetworkMisc misc) {
try {
- mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc);
- } catch (RemoteException e) { }
+ return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc);
+ } catch (RemoteException e) {
+ return NETID_UNSET;
+ }
}
/**
@@ -1939,32 +1983,25 @@
* changes. Should be extended by applications wanting notifications.
*/
public static class NetworkCallback {
- /** @hide */
- public static final int PRECHECK = 1;
- /** @hide */
- public static final int AVAILABLE = 2;
- /** @hide */
- public static final int LOSING = 3;
- /** @hide */
- public static final int LOST = 4;
- /** @hide */
- public static final int UNAVAIL = 5;
- /** @hide */
- public static final int CAP_CHANGED = 6;
- /** @hide */
- public static final int PROP_CHANGED = 7;
- /** @hide */
- public static final int CANCELED = 8;
-
/**
+ * Called when the framework connects to a new network to evaluate whether it satisfies this
+ * request. If evaluation succeeds, this callback may be followed by an {@link #onAvailable}
+ * callback. There is no guarantee that this new network will satisfy any requests, or that
+ * the network will stay connected for longer than the time necessary to evaluate it.
+ * <p>
+ * Most applications <b>should not</b> act on this callback, and should instead use
+ * {@link #onAvailable}. This callback is intended for use by applications that can assist
+ * the framework in properly evaluating the network — for example, an application that
+ * can automatically log in to a captive portal without user intervention.
+ *
+ * @param network The {@link Network} of the network that is being evaluated.
+ *
* @hide
- * Called whenever the framework connects to a network that it may use to
- * satisfy this request
*/
public void onPreCheck(Network network) {}
/**
- * Called when the framework connects and has declared new network ready for use.
+ * Called when the framework connects and has declared a new network ready for use.
* This callback may be called more than once if the {@link Network} that is
* satisfying the request changes.
*
@@ -2008,7 +2045,7 @@
* changes capabilities but still satisfies the stated need.
*
* @param network The {@link Network} whose capabilities have changed.
- * @param networkCapabilities The new {@link NetworkCapabilities} for this network.
+ * @param networkCapabilities The new {@link android.net.NetworkCapabilities} for this network.
*/
public void onCapabilitiesChanged(Network network,
NetworkCapabilities networkCapabilities) {}
@@ -2022,30 +2059,54 @@
*/
public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {}
+ /**
+ * Called when the network the framework connected to for this request
+ * goes into {@link NetworkInfo.DetailedState.SUSPENDED}.
+ * This generally means that while the TCP connections are still live,
+ * temporarily network data fails to transfer. Specifically this is used
+ * on cellular networks to mask temporary outages when driving through
+ * a tunnel, etc.
+ * @hide
+ */
+ public void onNetworkSuspended(Network network) {}
+
+ /**
+ * Called when the network the framework connected to for this request
+ * returns from a {@link NetworkInfo.DetailedState.SUSPENDED} state.
+ * This should always be preceeded by a matching {@code onNetworkSuspended}
+ * call.
+ * @hide
+ */
+ public void onNetworkResumed(Network network) {}
+
private NetworkRequest networkRequest;
}
private static final int BASE = Protocol.BASE_CONNECTIVITY_MANAGER;
- /** @hide obj = pair(NetworkRequest, Network) */
- public static final int CALLBACK_PRECHECK = BASE + 1;
- /** @hide obj = pair(NetworkRequest, Network) */
- public static final int CALLBACK_AVAILABLE = BASE + 2;
- /** @hide obj = pair(NetworkRequest, Network), arg1 = ttl */
- public static final int CALLBACK_LOSING = BASE + 3;
- /** @hide obj = pair(NetworkRequest, Network) */
- public static final int CALLBACK_LOST = BASE + 4;
- /** @hide obj = NetworkRequest */
- public static final int CALLBACK_UNAVAIL = BASE + 5;
- /** @hide obj = pair(NetworkRequest, Network) */
- public static final int CALLBACK_CAP_CHANGED = BASE + 6;
- /** @hide obj = pair(NetworkRequest, Network) */
- public static final int CALLBACK_IP_CHANGED = BASE + 7;
- /** @hide obj = NetworkRequest */
- public static final int CALLBACK_RELEASED = BASE + 8;
/** @hide */
- public static final int CALLBACK_EXIT = BASE + 9;
+ public static final int CALLBACK_PRECHECK = BASE + 1;
+ /** @hide */
+ public static final int CALLBACK_AVAILABLE = BASE + 2;
+ /** @hide arg1 = TTL */
+ public static final int CALLBACK_LOSING = BASE + 3;
+ /** @hide */
+ public static final int CALLBACK_LOST = BASE + 4;
+ /** @hide */
+ public static final int CALLBACK_UNAVAIL = BASE + 5;
+ /** @hide */
+ public static final int CALLBACK_CAP_CHANGED = BASE + 6;
+ /** @hide */
+ public static final int CALLBACK_IP_CHANGED = BASE + 7;
+ /** @hide */
+ public static final int CALLBACK_RELEASED = BASE + 8;
+ /** @hide */
+ public static final int CALLBACK_EXIT = BASE + 9;
/** @hide obj = NetworkCapabilities, arg1 = seq number */
- private static final int EXPIRE_LEGACY_REQUEST = BASE + 10;
+ private static final int EXPIRE_LEGACY_REQUEST = BASE + 10;
+ /** @hide */
+ public static final int CALLBACK_SUSPENDED = BASE + 11;
+ /** @hide */
+ public static final int CALLBACK_RESUMED = BASE + 12;
private class CallbackHandler extends Handler {
private final HashMap<NetworkRequest, NetworkCallback>mCallbackMap;
@@ -2064,116 +2125,96 @@
@Override
public void handleMessage(Message message) {
Log.d(TAG, "CM callback handler got msg " + message.what);
+ NetworkRequest request = (NetworkRequest) getObject(message, NetworkRequest.class);
+ Network network = (Network) getObject(message, Network.class);
switch (message.what) {
case CALLBACK_PRECHECK: {
- NetworkRequest request = (NetworkRequest)getObject(message,
- NetworkRequest.class);
- NetworkCallback callbacks = getCallbacks(request);
- if (callbacks != null) {
- callbacks.onPreCheck((Network)getObject(message, Network.class));
- } else {
- Log.e(TAG, "callback not found for PRECHECK message");
+ NetworkCallback callback = getCallback(request, "PRECHECK");
+ if (callback != null) {
+ callback.onPreCheck(network);
}
break;
}
case CALLBACK_AVAILABLE: {
- NetworkRequest request = (NetworkRequest)getObject(message,
- NetworkRequest.class);
- NetworkCallback callbacks = getCallbacks(request);
- if (callbacks != null) {
- callbacks.onAvailable((Network)getObject(message, Network.class));
- } else {
- Log.e(TAG, "callback not found for AVAILABLE message");
+ NetworkCallback callback = getCallback(request, "AVAILABLE");
+ if (callback != null) {
+ callback.onAvailable(network);
}
break;
}
case CALLBACK_LOSING: {
- NetworkRequest request = (NetworkRequest)getObject(message,
- NetworkRequest.class);
- NetworkCallback callbacks = getCallbacks(request);
- if (callbacks != null) {
- callbacks.onLosing((Network)getObject(message, Network.class),
- message.arg1);
- } else {
- Log.e(TAG, "callback not found for LOSING message");
+ NetworkCallback callback = getCallback(request, "LOSING");
+ if (callback != null) {
+ callback.onLosing(network, message.arg1);
}
break;
}
case CALLBACK_LOST: {
- NetworkRequest request = (NetworkRequest)getObject(message,
- NetworkRequest.class);
-
- NetworkCallback callbacks = getCallbacks(request);
- if (callbacks != null) {
- callbacks.onLost((Network)getObject(message, Network.class));
- } else {
- Log.e(TAG, "callback not found for LOST message");
+ NetworkCallback callback = getCallback(request, "LOST");
+ if (callback != null) {
+ callback.onLost(network);
}
break;
}
case CALLBACK_UNAVAIL: {
- NetworkRequest request = (NetworkRequest)getObject(message,
- NetworkRequest.class);
- NetworkCallback callbacks = null;
- synchronized(mCallbackMap) {
- callbacks = mCallbackMap.get(request);
- }
- if (callbacks != null) {
- callbacks.onUnavailable();
- } else {
- Log.e(TAG, "callback not found for UNAVAIL message");
+ NetworkCallback callback = getCallback(request, "UNAVAIL");
+ if (callback != null) {
+ callback.onUnavailable();
}
break;
}
case CALLBACK_CAP_CHANGED: {
- NetworkRequest request = (NetworkRequest)getObject(message,
- NetworkRequest.class);
- NetworkCallback callbacks = getCallbacks(request);
- if (callbacks != null) {
- Network network = (Network)getObject(message, Network.class);
+ NetworkCallback callback = getCallback(request, "CAP_CHANGED");
+ if (callback != null) {
NetworkCapabilities cap = (NetworkCapabilities)getObject(message,
NetworkCapabilities.class);
- callbacks.onCapabilitiesChanged(network, cap);
- } else {
- Log.e(TAG, "callback not found for CAP_CHANGED message");
+ callback.onCapabilitiesChanged(network, cap);
}
break;
}
case CALLBACK_IP_CHANGED: {
- NetworkRequest request = (NetworkRequest)getObject(message,
- NetworkRequest.class);
- NetworkCallback callbacks = getCallbacks(request);
- if (callbacks != null) {
- Network network = (Network)getObject(message, Network.class);
+ NetworkCallback callback = getCallback(request, "IP_CHANGED");
+ if (callback != null) {
LinkProperties lp = (LinkProperties)getObject(message,
LinkProperties.class);
- callbacks.onLinkPropertiesChanged(network, lp);
- } else {
- Log.e(TAG, "callback not found for IP_CHANGED message");
+ callback.onLinkPropertiesChanged(network, lp);
+ }
+ break;
+ }
+ case CALLBACK_SUSPENDED: {
+ NetworkCallback callback = getCallback(request, "SUSPENDED");
+ if (callback != null) {
+ callback.onNetworkSuspended(network);
+ }
+ break;
+ }
+ case CALLBACK_RESUMED: {
+ NetworkCallback callback = getCallback(request, "RESUMED");
+ if (callback != null) {
+ callback.onNetworkResumed(network);
}
break;
}
case CALLBACK_RELEASED: {
- NetworkRequest req = (NetworkRequest)getObject(message, NetworkRequest.class);
- NetworkCallback callbacks = null;
+ NetworkCallback callback = null;
synchronized(mCallbackMap) {
- callbacks = mCallbackMap.remove(req);
+ callback = mCallbackMap.remove(request);
}
- if (callbacks != null) {
+ if (callback != null) {
synchronized(mRefCount) {
if (mRefCount.decrementAndGet() == 0) {
getLooper().quit();
}
}
} else {
- Log.e(TAG, "callback not found for CANCELED message");
+ Log.e(TAG, "callback not found for RELEASED message");
}
break;
}
case CALLBACK_EXIT: {
- Log.d(TAG, "Listener quiting");
+ Log.d(TAG, "Listener quitting");
getLooper().quit();
break;
}
@@ -2187,10 +2228,16 @@
private Object getObject(Message msg, Class c) {
return msg.getData().getParcelable(c.getSimpleName());
}
- private NetworkCallback getCallbacks(NetworkRequest req) {
+
+ private NetworkCallback getCallback(NetworkRequest req, String name) {
+ NetworkCallback callback;
synchronized(mCallbackMap) {
- return mCallbackMap.get(req);
+ callback = mCallbackMap.get(req);
}
+ if (callback == null) {
+ Log.e(TAG, "callback not found for " + name + " message");
+ }
+ return callback;
}
}
@@ -2250,18 +2297,30 @@
}
/**
- * Request a network to satisfy a set of {@link NetworkCapabilities}.
+ * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
*
* This {@link NetworkRequest} will live until released via
* {@link #unregisterNetworkCallback} or the calling application exits.
* Status of the request can be followed by listening to the various
* callbacks described in {@link NetworkCallback}. The {@link Network}
* can be used to direct traffic to the network.
+ * <p>It is presently unsupported to request a network with mutable
+ * {@link NetworkCapabilities} such as
+ * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+ * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
+ * as these {@code NetworkCapabilities} represent states that a particular
+ * network may never attain, and whether a network will attain these states
+ * is unknown prior to bringing up the network so the framework does not
+ * know how to go about satisfing a request with these capabilities.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
*
* @param request {@link NetworkRequest} describing this request.
* @param networkCallback The {@link NetworkCallback} to be utilized for this
* request. Note the callback must not be shared - they
* uniquely specify this request.
+ * @throws IllegalArgumentException if {@code request} specifies any mutable
+ * {@code NetworkCapabilities}.
*/
public void requestNetwork(NetworkRequest request, NetworkCallback networkCallback) {
sendRequestForNetwork(request.networkCapabilities, networkCallback, 0,
@@ -2269,13 +2328,15 @@
}
/**
- * Request a network to satisfy a set of {@link NetworkCapabilities}, limited
+ * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}, limited
* by a timeout.
*
* This function behaves identically to the non-timedout version, but if a suitable
* network is not found within the given time (in milliseconds) the
* {@link NetworkCallback#unavailable} callback is called. The request must
* still be released normally by calling {@link releaseNetworkRequest}.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* @param request {@link NetworkRequest} describing this request.
* @param networkCallback The callbacks to be utilized for this request. Note
* the callbacks must not be shared - they uniquely specify
@@ -2302,9 +2363,8 @@
* successfully finding a network for the applications request. Retrieve it with
* {@link android.content.Intent#getParcelableExtra(String)}.
* <p>
- * Note that if you intend to invoke (@link #setProcessDefaultNetwork(Network)) or
- * {@link Network#openConnection(java.net.URL)} then you must get a
- * ConnectivityManager instance before doing so.
+ * Note that if you intend to invoke {@link Network#openConnection(java.net.URL)}
+ * then you must get a ConnectivityManager instance before doing so.
*/
public static final String EXTRA_NETWORK = "android.net.extra.NETWORK";
@@ -2317,7 +2377,7 @@
/**
- * Request a network to satisfy a set of {@link NetworkCapabilities}.
+ * Request a network to satisfy a set of {@link android.net.NetworkCapabilities}.
*
* This function behaves identically to the version that takes a NetworkCallback, but instead
* of {@link NetworkCallback} a {@link PendingIntent} is used. This means
@@ -2336,17 +2396,28 @@
* Intent to reserve the network or it will be released shortly after the Intent
* is processed.
* <p>
- * If there is already an request for this Intent registered (with the equality of
+ * If there is already a request for this Intent registered (with the equality of
* two Intents defined by {@link Intent#filterEquals}), then it will be removed and
* replaced by this one, effectively releasing the previous {@link NetworkRequest}.
* <p>
* The request may be released normally by calling
* {@link #releaseNetworkRequest(android.app.PendingIntent)}.
- *
+ * <p>It is presently unsupported to request a network with either
+ * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+ * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}
+ * as these {@code NetworkCapabilities} represent states that a particular
+ * network may never attain, and whether a network will attain these states
+ * is unknown prior to bringing up the network so the framework does not
+ * know how to go about satisfing a request with these capabilities.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CHANGE_NETWORK_STATE}.
* @param request {@link NetworkRequest} describing this request.
* @param operation Action to perform when the network is available (corresponds
* to the {@link NetworkCallback#onAvailable} call. Typically
* comes from {@link PendingIntent#getBroadcast}. Cannot be null.
+ * @throws IllegalArgumentException if {@code request} contains either
+ * {@link NetworkCapabilities#NET_CAPABILITY_VALIDATED} or
+ * {@link NetworkCapabilities#NET_CAPABILITY_CAPTIVE_PORTAL}.
*/
public void requestNetwork(NetworkRequest request, PendingIntent operation) {
checkPendingIntent(operation);
@@ -2383,6 +2454,8 @@
* Registers to receive notifications about all networks which satisfy the given
* {@link NetworkRequest}. The callbacks will continue to be called until
* either the application exits or {@link #unregisterNetworkCallback} is called
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
*
* @param request {@link NetworkRequest} describing this request.
* @param networkCallback The {@link NetworkCallback} that the system will call as suitable
@@ -2393,11 +2466,68 @@
}
/**
+ * Registers a PendingIntent to be sent when a network is available which satisfies the given
+ * {@link NetworkRequest}.
+ *
+ * This function behaves identically to the version that takes a NetworkCallback, but instead
+ * of {@link NetworkCallback} a {@link PendingIntent} is used. This means
+ * the request may outlive the calling application and get called back when a suitable
+ * network is found.
+ * <p>
+ * The operation is an Intent broadcast that goes to a broadcast receiver that
+ * you registered with {@link Context#registerReceiver} or through the
+ * <receiver> tag in an AndroidManifest.xml file
+ * <p>
+ * The operation Intent is delivered with two extras, a {@link Network} typed
+ * extra called {@link #EXTRA_NETWORK} and a {@link NetworkRequest}
+ * typed extra called {@link #EXTRA_NETWORK_REQUEST} containing
+ * the original requests parameters.
+ * <p>
+ * If there is already a request for this Intent registered (with the equality of
+ * two Intents defined by {@link Intent#filterEquals}), then it will be removed and
+ * replaced by this one, effectively releasing the previous {@link NetworkRequest}.
+ * <p>
+ * The request may be released normally by calling
+ * {@link #unregisterNetworkCallback(android.app.PendingIntent)}.
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+ * @param request {@link NetworkRequest} describing this request.
+ * @param operation Action to perform when the network is available (corresponds
+ * to the {@link NetworkCallback#onAvailable} call. Typically
+ * comes from {@link PendingIntent#getBroadcast}. Cannot be null.
+ */
+ public void registerNetworkCallback(NetworkRequest request, PendingIntent operation) {
+ checkPendingIntent(operation);
+ try {
+ mService.pendingListenForNetwork(request.networkCapabilities, operation);
+ } catch (RemoteException e) {}
+ }
+
+ /**
+ * Requests bandwidth update for a given {@link Network} and returns whether the update request
+ * is accepted by ConnectivityService. Once accepted, ConnectivityService will poll underlying
+ * network connection for updated bandwidth information. The caller will be notified via
+ * {@link ConnectivityManager.NetworkCallback} if there is an update. Notice that this
+ * method assumes that the caller has previously called {@link #registerNetworkCallback} to
+ * listen for network changes.
+ *
+ * @param network {@link Network} specifying which network you're interested.
+ * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
+ */
+ public boolean requestBandwidthUpdate(Network network) {
+ try {
+ return mService.requestBandwidthUpdate(network);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
* Unregisters callbacks about and possibly releases networks originating from
- * {@link #requestNetwork} and {@link #registerNetworkCallback} calls. If the
- * given {@code NetworkCallback} had previously been used with {@code #requestNetwork},
- * any networks that had been connected to only to satisfy that request will be
- * disconnected.
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback)} and {@link #registerNetworkCallback}
+ * calls. If the given {@code NetworkCallback} had previously been used with
+ * {@code #requestNetwork}, any networks that had been connected to only to satisfy that request
+ * will be disconnected.
*
* @param networkCallback The {@link NetworkCallback} used when making the request.
*/
@@ -2412,6 +2542,77 @@
}
/**
+ * Unregisters a callback previously registered via
+ * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}.
+ *
+ * @param operation A PendingIntent equal (as defined by {@link Intent#filterEquals}) to the
+ * PendingIntent passed to
+ * {@link #registerNetworkCallback(NetworkRequest, android.app.PendingIntent)}.
+ * Cannot be null.
+ */
+ public void unregisterNetworkCallback(PendingIntent operation) {
+ releaseNetworkRequest(operation);
+ }
+
+ /**
+ * Informs the system whether it should switch to {@code network} regardless of whether it is
+ * validated or not. If {@code accept} is true, and the network was explicitly selected by the
+ * user (e.g., by selecting a Wi-Fi network in the Settings app), then the network will become
+ * the system default network regardless of any other network that's currently connected. If
+ * {@code always} is true, then the choice is remembered, so that the next time the user
+ * connects to this network, the system will switch to it.
+ *
+ * <p>This method requires the caller to hold the permission
+ * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
+ *
+ * @param network The network to accept.
+ * @param accept Whether to accept the network even if unvalidated.
+ * @param always Whether to remember this choice in the future.
+ *
+ * @hide
+ */
+ public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
+ try {
+ mService.setAcceptUnvalidated(network, accept, always);
+ } catch (RemoteException e) {}
+ }
+
+ /**
+ * Resets all connectivity manager settings back to factory defaults.
+ * @hide
+ */
+ public void factoryReset() {
+ try {
+ mService.factoryReset();
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
+ * Binds the current process to {@code network}. All Sockets created in the future
+ * (and not explicitly bound via a bound SocketFactory from
+ * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to
+ * {@code network}. All host name resolutions will be limited to {@code network} as well.
+ * Note that if {@code network} ever disconnects, all Sockets created in this way will cease to
+ * work and all host name resolutions will fail. This is by design so an application doesn't
+ * accidentally use Sockets it thinks are still bound to a particular {@link Network}.
+ * To clear binding pass {@code null} for {@code network}. Using individually bound
+ * Sockets created by Network.getSocketFactory().createSocket() and
+ * performing network-specific host name resolutions via
+ * {@link Network#getAllByName Network.getAllByName} is preferred to calling
+ * {@code bindProcessToNetwork}.
+ *
+ * @param network The {@link Network} to bind the current process to, or {@code null} to clear
+ * the current binding.
+ * @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
+ */
+ public boolean bindProcessToNetwork(Network network) {
+ // Forcing callers to call thru non-static function ensures ConnectivityManager
+ // instantiated.
+ return setProcessDefaultNetwork(network);
+ }
+
+ /**
* Binds the current process to {@code network}. All Sockets created in the future
* (and not explicitly bound via a bound SocketFactory from
* {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to
@@ -2428,16 +2629,24 @@
* @param network The {@link Network} to bind the current process to, or {@code null} to clear
* the current binding.
* @return {@code true} on success, {@code false} if the {@link Network} is no longer valid.
+ * @deprecated This function can throw {@link IllegalStateException}. Use
+ * {@link #bindProcessToNetwork} instead. {@code bindProcessToNetwork}
+ * is a direct replacement.
*/
public static boolean setProcessDefaultNetwork(Network network) {
int netId = (network == null) ? NETID_UNSET : network.netId;
- if (netId == NetworkUtils.getNetworkBoundToProcess()) {
+ if (netId == NetworkUtils.getBoundNetworkForProcess()) {
return true;
}
if (NetworkUtils.bindProcessToNetwork(netId)) {
// Set HTTP proxy system properties to match network.
// TODO: Deprecate this static method and replace it with a non-static version.
- Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
+ try {
+ Proxy.setHttpProxySystemProperty(getInstance().getDefaultProxy());
+ } catch (SecurityException e) {
+ // The process doesn't have ACCESS_NETWORK_STATE, so we can't fetch the proxy.
+ Log.e(TAG, "Can't set proxy properties", e);
+ }
// Must flush DNS cache as new network may have different DNS resolutions.
InetAddress.clearDnsCache();
// Must flush socket pool as idle sockets will be bound to previous network and may
@@ -2451,19 +2660,62 @@
/**
* Returns the {@link Network} currently bound to this process via
- * {@link #setProcessDefaultNetwork}, or {@code null} if no {@link Network} is explicitly bound.
+ * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound.
*
* @return {@code Network} to which this process is bound, or {@code null}.
*/
+ public Network getBoundNetworkForProcess() {
+ // Forcing callers to call thru non-static function ensures ConnectivityManager
+ // instantiated.
+ return getProcessDefaultNetwork();
+ }
+
+ /**
+ * Returns the {@link Network} currently bound to this process via
+ * {@link #bindProcessToNetwork}, or {@code null} if no {@link Network} is explicitly bound.
+ *
+ * @return {@code Network} to which this process is bound, or {@code null}.
+ * @deprecated Using this function can lead to other functions throwing
+ * {@link IllegalStateException}. Use {@link #getBoundNetworkForProcess} instead.
+ * {@code getBoundNetworkForProcess} is a direct replacement.
+ */
public static Network getProcessDefaultNetwork() {
- int netId = NetworkUtils.getNetworkBoundToProcess();
+ int netId = NetworkUtils.getBoundNetworkForProcess();
if (netId == NETID_UNSET) return null;
return new Network(netId);
}
+ private void unsupportedStartingFrom(int version) {
+ if (Process.myUid() == Process.SYSTEM_UID) {
+ // The getApplicationInfo() call we make below is not supported in system context, and
+ // we want to allow the system to use these APIs anyway.
+ return;
+ }
+
+ if (mContext.getApplicationInfo().targetSdkVersion >= version) {
+ throw new UnsupportedOperationException(
+ "This method is not supported in target SDK version " + version + " and above");
+ }
+ }
+
+ // Checks whether the calling app can use the legacy routing API (startUsingNetworkFeature,
+ // stopUsingNetworkFeature, requestRouteToHost), and if not throw UnsupportedOperationException.
+ // TODO: convert the existing system users (Tethering, GpsLocationProvider) to the new APIs and
+ // remove these exemptions. Note that this check is not secure, and apps can still access these
+ // functions by accessing ConnectivityService directly. However, it should be clear that doing
+ // so is unsupported and may break in the future. http://b/22728205
+ private void checkLegacyRoutingApiAccess() {
+ if (mContext.checkCallingOrSelfPermission("com.android.permission.INJECT_OMADM_SETTINGS")
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+
+ unsupportedStartingFrom(VERSION_CODES.M);
+ }
+
/**
* Binds host resolutions performed by this process to {@code network}.
- * {@link #setProcessDefaultNetwork} takes precedence over this setting.
+ * {@link #bindProcessToNetwork} takes precedence over this setting.
*
* @param network The {@link Network} to bind host resolutions from the current process to, or
* {@code null} to clear the current binding.
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index d8852f8..46c28a6 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -33,6 +33,7 @@
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnInfo;
import com.android.internal.net.VpnProfile;
/**
@@ -42,6 +43,7 @@
/** {@hide} */
interface IConnectivityManager
{
+ Network getActiveNetwork();
NetworkInfo getActiveNetworkInfo();
NetworkInfo getActiveNetworkInfoForUid(int uid);
NetworkInfo getNetworkInfo(int networkType);
@@ -51,8 +53,6 @@
Network[] getAllNetworks();
NetworkCapabilities[] getDefaultNetworkCapabilitiesForUser(int userId);
- NetworkInfo getProvisioningOrActiveNetworkInfo();
-
boolean isNetworkSupported(int networkType);
LinkProperties getActiveLinkProperties();
@@ -94,47 +94,45 @@
void reportInetCondition(int networkType, int percentage);
- void reportBadNetwork(in Network network);
+ void reportNetworkConnectivity(in Network network, boolean hasConnectivity);
ProxyInfo getGlobalProxy();
void setGlobalProxy(in ProxyInfo p);
- ProxyInfo getDefaultProxy();
+ ProxyInfo getProxyForNetwork(in Network nework);
- boolean prepareVpn(String oldPackage, String newPackage);
+ boolean prepareVpn(String oldPackage, String newPackage, int userId);
- void setVpnPackageAuthorization(boolean authorized);
+ void setVpnPackageAuthorization(String packageName, int userId, boolean authorized);
ParcelFileDescriptor establishVpn(in VpnConfig config);
- VpnConfig getVpnConfig();
+ VpnConfig getVpnConfig(int userId);
void startLegacyVpn(in VpnProfile profile);
- LegacyVpnInfo getLegacyVpnInfo();
+ LegacyVpnInfo getLegacyVpnInfo(int userId);
+
+ VpnInfo[] getAllVpnInfo();
boolean updateLockdownVpn();
- void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
-
- int findConnectionTypeForIface(in String iface);
-
int checkMobileProvisioning(int suggestedTimeOutMs);
String getMobileProvisioningUrl();
- String getMobileRedirectedProvisioningUrl();
-
void setProvisioningNotificationVisible(boolean visible, int networkType, in String action);
void setAirplaneMode(boolean enable);
void registerNetworkFactory(in Messenger messenger, in String name);
+ boolean requestBandwidthUpdate(in Network network);
+
void unregisterNetworkFactory(in Messenger messenger);
- void registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
+ int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
in NetworkCapabilities nc, int score, in NetworkMisc misc);
NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
@@ -153,9 +151,13 @@
void releaseNetworkRequest(in NetworkRequest networkRequest);
+ void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
+
int getRestoreDefaultNetworkDelay(int networkType);
boolean addVpnAddress(String address, int prefixLength);
boolean removeVpnAddress(String address, int prefixLength);
boolean setUnderlyingNetworksForVpn(in Network[] networks);
+
+ void factoryReset();
}
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index b268986..6b4f2d5 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -170,6 +170,21 @@
}
/**
+ * Determines whether the prefix contains the specified address.
+ *
+ * @param address An {@link InetAddress} to test.
+ * @return {@code true} if the prefix covers the given address.
+ */
+ public boolean contains(InetAddress address) {
+ byte[] addrBytes = (address == null) ? null : address.getAddress();
+ if (addrBytes == null || addrBytes.length != this.address.length) {
+ return false;
+ }
+ NetworkUtils.maskRawAddress(addrBytes, prefixLength);
+ return Arrays.equals(this.address, addrBytes);
+ }
+
+ /**
* Returns a string representation of this {@code IpPrefix}.
*
* @return a string such as {@code "192.0.2.0/24"} or {@code "2001:db8:1:2::/64"}.
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 8b0dfc9..c4de4a2 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -88,6 +88,54 @@
/**
* @hide
*/
+ public enum ProvisioningChange {
+ STILL_NOT_PROVISIONED,
+ LOST_PROVISIONING,
+ GAINED_PROVISIONING,
+ STILL_PROVISIONED,
+ }
+
+ /**
+ * Compare the provisioning states of two LinkProperties instances.
+ *
+ * @hide
+ */
+ public static ProvisioningChange compareProvisioning(
+ LinkProperties before, LinkProperties after) {
+ if (before.isProvisioned() && after.isProvisioned()) {
+ // On dualstack networks, DHCPv4 renewals can occasionally fail.
+ // When this happens, IPv6-reachable services continue to function
+ // normally but IPv4-only services (naturally) fail.
+ //
+ // When an application using an IPv4-only service reports a bad
+ // network condition to the framework, attempts to re-validate
+ // the network succeed (since we support IPv6-only networks) and
+ // nothing is changed.
+ //
+ // For users, this is confusing and unexpected behaviour, and is
+ // not necessarily easy to diagnose. Therefore, we treat changing
+ // from a dualstack network to an IPv6-only network equivalent to
+ // a total loss of provisioning.
+ //
+ // For one such example of this, see b/18867306.
+ //
+ // TODO: Remove this special case altogether.
+ if (before.isIPv4Provisioned() && !after.isIPv4Provisioned()) {
+ return ProvisioningChange.LOST_PROVISIONING;
+ }
+ return ProvisioningChange.STILL_PROVISIONED;
+ } else if (before.isProvisioned() && !after.isProvisioned()) {
+ return ProvisioningChange.LOST_PROVISIONING;
+ } else if (!before.isProvisioned() && after.isProvisioned()) {
+ return ProvisioningChange.GAINED_PROVISIONING;
+ } else { // !before.isProvisioned() && !after.isProvisioned()
+ return ProvisioningChange.STILL_NOT_PROVISIONED;
+ }
+ }
+
+ /**
+ * @hide
+ */
public LinkProperties() {
}
@@ -287,6 +335,20 @@
}
/**
+ * Removes the given {@link InetAddress} from the list of DNS servers.
+ *
+ * @param dnsServer The {@link InetAddress} to remove from the list of DNS servers.
+ * @return true if the DNS server was removed, false if it did not exist.
+ * @hide
+ */
+ public boolean removeDnsServer(InetAddress dnsServer) {
+ if (dnsServer != null) {
+ return mDnses.remove(dnsServer);
+ }
+ return false;
+ }
+
+ /**
* Replaces the DNS servers in this {@code LinkProperties} with
* the given {@link Collection} of {@link InetAddress} objects.
*
@@ -455,7 +517,7 @@
* Note that Http Proxies are only a hint - the system recommends their use, but it does
* not enforce it and applications may ignore them.
*
- * @param proxy A {@link ProxyInfo} defining the Http Proxy to use on this link.
+ * @param proxy A {@link ProxyInfo} defining the HTTP Proxy to use on this link.
* @hide
*/
public void setHttpProxy(ProxyInfo proxy) {
@@ -600,6 +662,17 @@
}
/**
+ * Returns true if this link or any of its stacked interfaces has an IPv4 address.
+ *
+ * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
+ */
+ private boolean hasIPv4AddressOnInterface(String iface) {
+ return (mIfaceName.equals(iface) && hasIPv4Address()) ||
+ (iface != null && mStackedLinks.containsKey(iface) &&
+ mStackedLinks.get(iface).hasIPv4Address());
+ }
+
+ /**
* Returns true if this link has a global preferred IPv6 address.
*
* @return {@code true} if there is a global preferred IPv6 address, {@code false} otherwise.
@@ -679,8 +752,9 @@
* This requires an IP address, default route, and DNS server.
*
* @return {@code true} if the link is provisioned, {@code false} otherwise.
+ * @hide
*/
- private boolean hasIPv4() {
+ public boolean isIPv4Provisioned() {
return (hasIPv4Address() &&
hasIPv4DefaultRoute() &&
hasIPv4DnsServer());
@@ -691,8 +765,9 @@
* This requires an IP address, default route, and DNS server.
*
* @return {@code true} if the link is provisioned, {@code false} otherwise.
+ * @hide
*/
- private boolean hasIPv6() {
+ public boolean isIPv6Provisioned() {
return (hasGlobalIPv6Address() &&
hasIPv6DefaultRoute() &&
hasIPv6DnsServer());
@@ -706,7 +781,44 @@
* @hide
*/
public boolean isProvisioned() {
- return (hasIPv4() || hasIPv6());
+ return (isIPv4Provisioned() || isIPv6Provisioned());
+ }
+
+ /**
+ * Evaluate whether the {@link InetAddress} is considered reachable.
+ *
+ * @return {@code true} if the given {@link InetAddress} is considered reachable,
+ * {@code false} otherwise.
+ * @hide
+ */
+ public boolean isReachable(InetAddress ip) {
+ final List<RouteInfo> allRoutes = getAllRoutes();
+ // If we don't have a route to this IP address, it's not reachable.
+ final RouteInfo bestRoute = RouteInfo.selectBestRoute(allRoutes, ip);
+ if (bestRoute == null) {
+ return false;
+ }
+
+ // TODO: better source address evaluation for destination addresses.
+
+ if (ip instanceof Inet4Address) {
+ // For IPv4, it suffices for now to simply have any address.
+ return hasIPv4AddressOnInterface(bestRoute.getInterface());
+ } else if (ip instanceof Inet6Address) {
+ if (ip.isLinkLocalAddress()) {
+ // For now, just make sure link-local destinations have
+ // scopedIds set, since transmits will generally fail otherwise.
+ // TODO: verify it matches the ifindex of one of the interfaces.
+ return (((Inet6Address)ip).getScopeId() != 0);
+ } else {
+ // For non-link-local destinations check that either the best route
+ // is directly connected or that some global preferred address exists.
+ // TODO: reconsider all cases (disconnected ULA networks, ...).
+ return (!bestRoute.hasGateway() || hasGlobalIPv6Address());
+ }
+ }
+
+ return false;
}
/**
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index dfe2413..fe69320 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -19,6 +19,8 @@
import android.os.Parcelable;
import android.os.Parcel;
import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
import java.io.FileDescriptor;
import java.io.IOException;
@@ -26,7 +28,6 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
-import java.net.ProxySelector;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
@@ -49,7 +50,7 @@
* {@link ConnectivityManager#registerNetworkCallback} calls.
* It is used to direct traffic to the given {@code Network}, either on a {@link Socket} basis
* through a targeted {@link SocketFactory} or process-wide via
- * {@link ConnectivityManager#setProcessDefaultNetwork}.
+ * {@link ConnectivityManager#bindProcessToNetwork}.
*/
public class Network implements Parcelable {
@@ -65,7 +66,7 @@
// maybeInitHttpClient() must be called prior to reading either variable.
private volatile ConnectionPool mConnectionPool = null;
private volatile com.android.okhttp.internal.Network mNetwork = null;
- private Object mLock = new Object();
+ private final Object mLock = new Object();
// Default connection pool values. These are evaluated at startup, just
// like the OkHttp code. Also like the OkHttp code, we will throw parse
@@ -243,14 +244,12 @@
* @see java.net.URL#openConnection()
*/
public URLConnection openConnection(URL url) throws IOException {
- final ConnectivityManager cm = ConnectivityManager.getInstance();
- // TODO: Should this be optimized to avoid fetching the global proxy for every request?
- ProxyInfo proxyInfo = cm.getGlobalProxy();
- if (proxyInfo == null) {
- // TODO: Should this be optimized to avoid fetching LinkProperties for every request?
- final LinkProperties lp = cm.getLinkProperties(this);
- if (lp != null) proxyInfo = lp.getHttpProxy();
+ final ConnectivityManager cm = ConnectivityManager.getInstanceOrNull();
+ if (cm == null) {
+ throw new IOException("No ConnectivityManager yet constructed, please construct one");
}
+ // TODO: Should this be optimized to avoid fetching the global proxy for every request?
+ final ProxyInfo proxyInfo = cm.getProxyForNetwork(this);
java.net.Proxy proxy = null;
if (proxyInfo != null) {
proxy = proxyInfo.makeProxy();
@@ -270,7 +269,6 @@
* @throws IllegalArgumentException if the argument proxy is null.
* @throws IOException if an error occurs while opening the connection.
* @see java.net.URL#openConnection()
- * @hide
*/
public URLConnection openConnection(URL url, java.net.Proxy proxy) throws IOException {
if (proxy == null) throw new IllegalArgumentException("proxy is null");
@@ -300,38 +298,54 @@
/**
* Binds the specified {@link DatagramSocket} to this {@code Network}. All data traffic on the
* socket will be sent on this {@code Network}, irrespective of any process-wide network binding
- * set by {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be
+ * set by {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be
* connected.
*/
public void bindSocket(DatagramSocket socket) throws IOException {
- // Apparently, the kernel doesn't update a connected UDP socket's routing upon mark changes.
- if (socket.isConnected()) {
- throw new SocketException("Socket is connected");
- }
// Query a property of the underlying socket to ensure that the socket's file descriptor
// exists, is available to bind to a network and is not closed.
socket.getReuseAddress();
- bindSocketFd(socket.getFileDescriptor$());
+ bindSocket(socket.getFileDescriptor$());
}
/**
* Binds the specified {@link Socket} to this {@code Network}. All data traffic on the socket
* will be sent on this {@code Network}, irrespective of any process-wide network binding set by
- * {@link ConnectivityManager#setProcessDefaultNetwork}. The socket must not be connected.
+ * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
*/
public void bindSocket(Socket socket) throws IOException {
- // Apparently, the kernel doesn't update a connected TCP socket's routing upon mark changes.
- if (socket.isConnected()) {
- throw new SocketException("Socket is connected");
- }
// Query a property of the underlying socket to ensure that the socket's file descriptor
// exists, is available to bind to a network and is not closed.
socket.getReuseAddress();
- bindSocketFd(socket.getFileDescriptor$());
+ bindSocket(socket.getFileDescriptor$());
}
- private void bindSocketFd(FileDescriptor fd) throws IOException {
- int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId);
+ /**
+ * Binds the specified {@link FileDescriptor} to this {@code Network}. All data traffic on the
+ * socket represented by this file descriptor will be sent on this {@code Network},
+ * irrespective of any process-wide network binding set by
+ * {@link ConnectivityManager#bindProcessToNetwork}. The socket must not be connected.
+ */
+ public void bindSocket(FileDescriptor fd) throws IOException {
+ try {
+ final SocketAddress peer = Os.getpeername(fd);
+ final InetAddress inetPeer = ((InetSocketAddress) peer).getAddress();
+ if (!inetPeer.isAnyLocalAddress()) {
+ // Apparently, the kernel doesn't update a connected UDP socket's
+ // routing upon mark changes.
+ throw new SocketException("Socket is connected");
+ }
+ } catch (ErrnoException e) {
+ // getpeername() failed.
+ if (e.errno != OsConstants.ENOTCONN) {
+ throw e.rethrowAsSocketException();
+ }
+ } catch (ClassCastException e) {
+ // Wasn't an InetSocketAddress.
+ throw new SocketException("Only AF_INET/AF_INET6 sockets supported");
+ }
+
+ final int err = NetworkUtils.bindSocketToNetwork(fd.getInt$(), netId);
if (err != 0) {
// bindSocketToNetwork returns negative errno.
throw new ErrnoException("Binding socket to network " + netId, -err)
@@ -339,6 +353,38 @@
}
}
+ /**
+ * Returns a handle representing this {@code Network}, for use with the NDK API.
+ */
+ public long getNetworkHandle() {
+ // The network handle is explicitly not the same as the netId.
+ //
+ // The netId is an implementation detail which might be changed in the
+ // future, or which alone (i.e. in the absence of some additional
+ // context) might not be sufficient to fully identify a Network.
+ //
+ // As such, the intention is to prevent accidental misuse of the API
+ // that might result if a developer assumed that handles and netIds
+ // were identical and passing a netId to a call expecting a handle
+ // "just worked". Such accidental misuse, if widely deployed, might
+ // prevent future changes to the semantics of the netId field or
+ // inhibit the expansion of state required for Network objects.
+ //
+ // This extra layer of indirection might be seen as paranoia, and might
+ // never end up being necessary, but the added complexity is trivial.
+ // At some future date it may be desirable to realign the handle with
+ // Multiple Provisioning Domains API recommendations, as made by the
+ // IETF mif working group.
+ //
+ // The HANDLE_MAGIC value MUST be kept in sync with the corresponding
+ // value in the native/android/net.c NDK implementation.
+ if (netId == 0) {
+ return 0L; // make this zero condition obvious for debugging
+ }
+ final long HANDLE_MAGIC = 0xfacade;
+ return (((long) netId) << 32) | HANDLE_MAGIC;
+ }
+
// implement the Parcelable interface
public int describeContents() {
return 0;
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 74d4ac2..808a882 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -21,8 +21,6 @@
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.util.Log;
import com.android.internal.util.AsyncChannel;
@@ -42,12 +40,20 @@
* @hide
*/
public abstract class NetworkAgent extends Handler {
+ // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown
+ // an exception.
+ public final int netId;
+
private volatile AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private static final boolean DBG = true;
private static final boolean VDBG = false;
private final Context mContext;
private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
+ private volatile long mLastBwRefreshTime = 0;
+ private static final long BW_REFRESH_MIN_WIN_MS = 500;
+ private boolean mPollLceScheduled = false;
+ private AtomicBoolean mPollLcePending = new AtomicBoolean(false);
private static final int BASE = Protocol.BASE_NETWORK_AGENT;
@@ -107,7 +113,7 @@
public static final int EVENT_UID_RANGES_REMOVED = BASE + 6;
/**
- * Sent by ConnectivitySerice to the NetworkAgent to inform the agent of the
+ * 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.
*
@@ -122,9 +128,33 @@
* Sent by the NetworkAgent to ConnectivityService to indicate this network was
* explicitly selected. This should be sent before the NetworkInfo is marked
* CONNECTED so it can be given special treatment at that time.
+ *
+ * obj = boolean indicating whether to use this network even if unvalidated
*/
public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8;
+ /**
+ * Sent by ConnectivityService to the NetworkAgent to inform the agent of
+ * whether the network should in the future be used even if not validated.
+ * This decision is made by the user, but it is the network transport's
+ * responsibility to remember it.
+ *
+ * arg1 = 1 if true, 0 if false
+ */
+ public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
+
+ /** Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
+ * the underlying network connection for updated bandwidth information.
+ */
+ public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
+
+ /**
+ * Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid
+ * automatically reconnecting to this network (e.g. via autojoin). Happens
+ * when user selects "No" option on the "Stay connected?" dialog box.
+ */
+ public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 11;
+
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
this(looper, context, logTag, ni, nc, lp, score, null);
@@ -142,7 +172,7 @@
if (VDBG) log("Registering NetworkAgent");
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
- cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
+ netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
new LinkProperties(lp), new NetworkCapabilities(nc), score, misc);
}
@@ -186,6 +216,27 @@
log("Unhandled Message " + msg);
break;
}
+ case CMD_REQUEST_BANDWIDTH_UPDATE: {
+ long currentTimeMs = System.currentTimeMillis();
+ if (VDBG) {
+ log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
+ }
+ if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
+ mPollLceScheduled = false;
+ if (mPollLcePending.getAndSet(true) == false) {
+ pollLceData();
+ }
+ } else {
+ // deliver the request at a later time rather than discard it completely.
+ if (!mPollLceScheduled) {
+ long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS -
+ currentTimeMs + 1;
+ mPollLceScheduled = sendEmptyMessageDelayed(
+ CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
+ }
+ }
+ break;
+ }
case CMD_REPORT_NETWORK_STATUS: {
if (VDBG) {
log("CMD_REPORT_NETWORK_STATUS(" +
@@ -194,6 +245,14 @@
networkStatus(msg.arg1);
break;
}
+ case CMD_SAVE_ACCEPT_UNVALIDATED: {
+ saveAcceptUnvalidated(msg.arg1 != 0);
+ break;
+ }
+ case CMD_PREVENT_AUTOMATIC_RECONNECT: {
+ preventAutomaticReconnect();
+ break;
+ }
}
}
@@ -228,6 +287,8 @@
* Called by the bearer code when it has new NetworkCapabilities data.
*/
public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
+ mPollLcePending.set(false);
+ mLastBwRefreshTime = System.currentTimeMillis();
queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
new NetworkCapabilities(networkCapabilities));
}
@@ -261,10 +322,16 @@
/**
* Called by the bearer to indicate this network was manually selected by the user.
* This should be called before the NetworkInfo is marked CONNECTED so that this
- * Network can be given special treatment at that time.
+ * Network can be given special treatment at that time. If {@code acceptUnvalidated} is
+ * {@code true}, then the system will switch to this network. If it is {@code false} and the
+ * network cannot be validated, the system will ask the user whether to switch to this network.
+ * If the user confirms and selects "don't ask again", then the system will call
+ * {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
+ * calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
+ * {@link #saveAcceptUnvalidated} to respect the user's choice.
*/
- public void explicitlySelected() {
- queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, 0);
+ public void explicitlySelected(boolean acceptUnvalidated) {
+ queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, acceptUnvalidated);
}
/**
@@ -276,6 +343,13 @@
abstract protected void unwanted();
/**
+ * Called when ConnectivityService request a bandwidth update. The parent factory
+ * shall try to overwrite this method and produce a bandwidth update if capable.
+ */
+ protected void pollLceData() {
+ }
+
+ /**
* Called when the system determines the usefulness of this network.
*
* Networks claiming internet connectivity will have their internet
@@ -293,6 +367,25 @@
protected void networkStatus(int status) {
}
+ /**
+ * Called when the user asks to remember the choice to use this network even if unvalidated.
+ * The transport is responsible for remembering the choice, and the next time the user connects
+ * to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}.
+ * This method will only be called if {@link #explicitlySelected} was called with
+ * {@code acceptUnvalidated} set to {@code false}.
+ */
+ protected void saveAcceptUnvalidated(boolean accept) {
+ }
+
+ /**
+ * Called when the user asks to not stay connected to this network because it was found to not
+ * provide Internet access. Usually followed by call to {@code unwanted}. The transport is
+ * responsible for making sure the device does not automatically reconnect to the same network
+ * after the {@code unwanted} call.
+ */
+ protected void preventAutomaticReconnect() {
+ }
+
protected void log(String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 0720885..d0e0cbe 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -37,6 +37,7 @@
* @hide
*/
public NetworkCapabilities() {
+ clearAll();
mNetworkCapabilities = DEFAULT_CAPABILITIES;
}
@@ -51,6 +52,17 @@
}
/**
+ * Completely clears the contents of this object, removing even the capabilities that are set
+ * by default when the object is constructed.
+ * @hide
+ */
+ public void clearAll() {
+ mNetworkCapabilities = mTransportTypes = 0;
+ mLinkUpBandwidthKbps = mLinkDownBandwidthKbps = 0;
+ mNetworkSpecifier = null;
+ }
+
+ /**
* Represents the network's capabilities. If any are specified they will be satisfied
* by any Network that matches all of them.
*/
@@ -118,7 +130,8 @@
/**
* Indicates this is a network that has the ability to reach a carrier's
- * Emergency IMS servers, used for network signaling during emergency calls.
+ * Emergency IMS servers or other services, used for network signaling
+ * during emergency calls.
*/
public static final int NET_CAPABILITY_EIMS = 10;
@@ -148,9 +161,9 @@
*/
public static final int NET_CAPABILITY_TRUSTED = 14;
- /*
+ /**
* Indicates that this network is not a VPN. This capability is set by default and should be
- * explicitly cleared when creating VPN networks.
+ * explicitly cleared for VPN networks.
*/
public static final int NET_CAPABILITY_NOT_VPN = 15;
@@ -158,12 +171,17 @@
* Indicates that connectivity on this network was successfully validated. For example, for a
* network with NET_CAPABILITY_INTERNET, it means that Internet connectivity was successfully
* detected.
- * @hide
*/
public static final int NET_CAPABILITY_VALIDATED = 16;
+ /**
+ * Indicates that this network was found to have a captive portal in place last time it was
+ * probed.
+ */
+ public static final int NET_CAPABILITY_CAPTIVE_PORTAL = 17;
+
private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
- private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_VALIDATED;
+ private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_CAPTIVE_PORTAL;
/**
* Capabilities that are set by default when the object is constructed.
@@ -646,6 +664,8 @@
case NET_CAPABILITY_NOT_RESTRICTED: capabilities += "NOT_RESTRICTED"; break;
case NET_CAPABILITY_TRUSTED: capabilities += "TRUSTED"; break;
case NET_CAPABILITY_NOT_VPN: capabilities += "NOT_VPN"; break;
+ case NET_CAPABILITY_VALIDATED: capabilities += "VALIDATED"; break;
+ case NET_CAPABILITY_CAPTIVE_PORTAL: capabilities += "CAPTIVE_PORTAL"; break;
}
if (++i < types.length) capabilities += "&";
}
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 393637e..af7a465 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -120,7 +120,6 @@
private String mExtraInfo;
private boolean mIsFailover;
private boolean mIsRoaming;
- private boolean mIsConnectedToProvisioningNetwork;
/**
* Indicates whether network connectivity is possible:
@@ -142,7 +141,6 @@
mState = State.UNKNOWN;
mIsAvailable = false; // until we're told otherwise, assume unavailable
mIsRoaming = false;
- mIsConnectedToProvisioningNetwork = false;
}
/** {@hide} */
@@ -160,7 +158,6 @@
mIsFailover = source.mIsFailover;
mIsRoaming = source.mIsRoaming;
mIsAvailable = source.mIsAvailable;
- mIsConnectedToProvisioningNetwork = source.mIsConnectedToProvisioningNetwork;
}
}
}
@@ -332,22 +329,6 @@
}
}
- /** {@hide} */
- @VisibleForTesting
- public boolean isConnectedToProvisioningNetwork() {
- synchronized (this) {
- return mIsConnectedToProvisioningNetwork;
- }
- }
-
- /** {@hide} */
- @VisibleForTesting
- public void setIsConnectedToProvisioningNetwork(boolean val) {
- synchronized (this) {
- mIsConnectedToProvisioningNetwork = val;
- }
- }
-
/**
* Reports the current coarse-grained state of the network.
* @return the coarse-grained state
@@ -431,8 +412,6 @@
append(", roaming: ").append(mIsRoaming).
append(", failover: ").append(mIsFailover).
append(", isAvailable: ").append(mIsAvailable).
- append(", isConnectedToProvisioningNetwork: ").
- append(mIsConnectedToProvisioningNetwork).
append("]");
return builder.toString();
}
@@ -461,7 +440,6 @@
dest.writeInt(mIsFailover ? 1 : 0);
dest.writeInt(mIsAvailable ? 1 : 0);
dest.writeInt(mIsRoaming ? 1 : 0);
- dest.writeInt(mIsConnectedToProvisioningNetwork ? 1 : 0);
dest.writeString(mReason);
dest.writeString(mExtraInfo);
}
@@ -484,7 +462,6 @@
netInfo.mIsFailover = in.readInt() != 0;
netInfo.mIsAvailable = in.readInt() != 0;
netInfo.mIsRoaming = in.readInt() != 0;
- netInfo.mIsConnectedToProvisioningNetwork = in.readInt() != 0;
netInfo.mReason = in.readString();
netInfo.mExtraInfo = in.readString();
return netInfo;
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index b92c9e3..5511a24 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -45,6 +45,13 @@
public boolean explicitlySelected;
/**
+ * Set if the user desires to use this network even if it is unvalidated. This field has meaning
+ * only if {#link explicitlySelected} is true. If it is, this field must also be set to the
+ * appropriate value based on previous user choice.
+ */
+ public boolean acceptUnvalidated;
+
+ /**
* For mobile networks, this is the subscriber ID (such as IMSI).
*/
public String subscriberId;
@@ -56,6 +63,7 @@
if (nm != null) {
allowBypass = nm.allowBypass;
explicitlySelected = nm.explicitlySelected;
+ acceptUnvalidated = nm.acceptUnvalidated;
subscriberId = nm.subscriberId;
}
}
@@ -69,6 +77,7 @@
public void writeToParcel(Parcel out, int flags) {
out.writeInt(allowBypass ? 1 : 0);
out.writeInt(explicitlySelected ? 1 : 0);
+ out.writeInt(acceptUnvalidated ? 1 : 0);
out.writeString(subscriberId);
}
@@ -78,6 +87,7 @@
NetworkMisc networkMisc = new NetworkMisc();
networkMisc.allowBypass = in.readInt() != 0;
networkMisc.explicitlySelected = in.readInt() != 0;
+ networkMisc.acceptUnvalidated = in.readInt() != 0;
networkMisc.subscriberId = in.readString();
return networkMisc;
}
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 4c8e080..e184ec4 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -19,8 +19,6 @@
import android.os.Parcel;
import android.os.Parcelable;
-import java.util.concurrent.atomic.AtomicInteger;
-
/**
* Defines a request for a network, made through {@link NetworkRequest.Builder} and used
* to request a network via {@link ConnectivityManager#requestNetwork} or listen for changes
@@ -123,6 +121,18 @@
}
/**
+ * Completely clears all the {@code NetworkCapabilities} from this builder instance,
+ * removing even the capabilities that are set by default when the object is constructed.
+ *
+ * @return The builder to facilitate chaining.
+ * @hide
+ */
+ public Builder clearCapabilities() {
+ mNetworkCapabilities.clearAll();
+ return this;
+ }
+
+ /**
* Adds the given transport requirement to this builder. These represent
* the set of allowed transports for the request. Only networks using one
* of these transports will satisfy the request. If no particular transports
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index d2a2997..4487cab 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -16,9 +16,11 @@
package android.net;
+import java.io.FileDescriptor;
import java.net.InetAddress;
import java.net.Inet4Address;
import java.net.Inet6Address;
+import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Locale;
@@ -56,6 +58,30 @@
/**
* Start the DHCP client daemon, in order to have it request addresses
+ * for the named interface. This returns {@code true} if the DHCPv4 daemon
+ * starts, {@code false} otherwise. This call blocks until such time as a
+ * result is available or the default discovery timeout has been reached.
+ * Callers should check {@link #getDhcpResults} to determine whether DHCP
+ * succeeded or failed, and if it succeeded, to fetch the {@link DhcpResults}.
+ * @param interfaceName the name of the interface to configure
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean startDhcp(String interfaceName);
+
+ /**
+ * Initiate renewal on the DHCP client daemon for the named interface. This
+ * returns {@code true} if the DHCPv4 daemon has been notified, {@code false}
+ * otherwise. This call blocks until such time as a result is available or
+ * the default renew timeout has been reached. Callers should check
+ * {@link #getDhcpResults} to determine whether DHCP succeeded or failed,
+ * and if it succeeded, to fetch the {@link DhcpResults}.
+ * @param interfaceName the name of the interface to configure
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean startDhcpRenew(String interfaceName);
+
+ /**
+ * Start the DHCP client daemon, in order to have it request addresses
* for the named interface, and then configure the interface with those
* addresses. This call blocks until it obtains a result (either success
* or failure) from the daemon.
@@ -64,17 +90,31 @@
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
- public native static boolean runDhcp(String interfaceName, DhcpResults dhcpResults);
+ public static boolean runDhcp(String interfaceName, DhcpResults dhcpResults) {
+ return startDhcp(interfaceName) && getDhcpResults(interfaceName, dhcpResults);
+ }
/**
- * Initiate renewal on the Dhcp client daemon. This call blocks until it obtains
+ * Initiate renewal on the DHCP client daemon. This call blocks until it obtains
* a result (either success or failure) from the daemon.
* @param interfaceName the name of the interface to configure
* @param dhcpResults if the request succeeds, this object is filled in with
* the IP address information.
* @return {@code true} for success, {@code false} for failure
*/
- public native static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults);
+ public static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults) {
+ return startDhcpRenew(interfaceName) && getDhcpResults(interfaceName, dhcpResults);
+ }
+
+ /**
+ * Fetch results from the DHCP client daemon. This call returns {@code true} if
+ * if there are results available to be read, {@code false} otherwise.
+ * @param interfaceName the name of the interface to configure
+ * @param dhcpResults if the request succeeds, this object is filled in with
+ * the IP address information.
+ * @return {@code true} for success, {@code false} for failure
+ */
+ public native static boolean getDhcpResults(String interfaceName, DhcpResults dhcpResults);
/**
* Shut down the DHCP client daemon.
@@ -101,6 +141,11 @@
public native static String getDhcpError();
/**
+ * Attaches a socket filter that accepts DHCP packets to the given socket.
+ */
+ public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException;
+
+ /**
* Binds the current process to the network designated by {@code netId}. All sockets created
* in the future (and not explicitly bound via a bound {@link SocketFactory} (see
* {@link Network#getSocketFactory}) will be bound to this network. Note that if this
@@ -114,7 +159,7 @@
* Return the netId last passed to {@link #bindProcessToNetwork}, or NETID_UNSET if
* {@link #unbindProcessToNetwork} has been called since {@link #bindProcessToNetwork}.
*/
- public native static int getNetworkBoundToProcess();
+ public native static int getBoundNetworkForProcess();
/**
* Binds host resolutions performed by this process to the network designated by {@code netId}.
@@ -133,6 +178,15 @@
public native static int bindSocketToNetwork(int socketfd, int netId);
/**
+ * Protect {@code fd} from VPN connections. After protecting, data sent through
+ * this socket will go directly to the underlying network, so its traffic will not be
+ * forwarded through the VPN.
+ */
+ public static boolean protectFromVpn(FileDescriptor fd) {
+ return protectFromVpn(fd.getInt$());
+ }
+
+ /**
* Protect {@code socketfd} from VPN connections. After protecting, data sent through
* this socket will go directly to the underlying network, so its traffic will not be
* forwarded through the VPN.
@@ -140,6 +194,12 @@
public native static boolean protectFromVpn(int socketfd);
/**
+ * 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);
+
+ /**
* Convert a IPv4 address from an integer to an InetAddress.
* @param hostAddress an int corresponding to the IPv4 address in network byte order
*/
@@ -192,6 +252,25 @@
}
/**
+ * Convert an IPv4 netmask to a prefix length, checking that the netmask is contiguous.
+ * @param netmask as a {@code Inet4Address}.
+ * @return the network prefix length
+ * @throws IllegalArgumentException the specified netmask was not contiguous.
+ * @hide
+ */
+ public static int netmaskToPrefixLength(Inet4Address netmask) {
+ // inetAddressToInt returns an int in *network* byte order.
+ int i = Integer.reverseBytes(inetAddressToInt(netmask));
+ int prefixLength = Integer.bitCount(i);
+ int trailingZeros = Integer.numberOfTrailingZeros(i);
+ if (trailingZeros != 32 - prefixLength) {
+ throw new IllegalArgumentException("Non-contiguous netmask: " + Integer.toHexString(i));
+ }
+ return prefixLength;
+ }
+
+
+ /**
* Create an InetAddress from a string where the string must be a standard
* representation of a V4 or V6 address. Avoids doing a DNS lookup on failure
* but it will throw an IllegalArgumentException in that case.
@@ -271,6 +350,22 @@
}
/**
+ * Returns the implicit netmask of an IPv4 address, as was the custom before 1993.
+ */
+ public static int getImplicitNetmask(Inet4Address address) {
+ int firstByte = address.getAddress()[0] & 0xff; // Convert to an unsigned value.
+ if (firstByte < 128) {
+ return 8;
+ } else if (firstByte < 192) {
+ return 16;
+ } else if (firstByte < 224) {
+ return 24;
+ } else {
+ return 32; // Will likely not end well for other reasons.
+ }
+ }
+
+ /**
* Utility method to parse strings such as "192.0.2.5/24" or "2001:db8::cafe:d00d/64".
* @hide
*/
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index a3cad77..5f5e623 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -21,8 +21,6 @@
import android.os.Parcelable;
import android.text.TextUtils;
-import org.apache.http.client.HttpClient;
-
import java.net.InetSocketAddress;
import java.net.URLConnection;
import java.util.List;
@@ -31,18 +29,13 @@
/**
* Describes a proxy configuration.
*
- * Proxy configurations are already integrated within the Apache HTTP stack.
- * So {@link URLConnection} and {@link HttpClient} will use them automatically.
+ * Proxy configurations are already integrated within the {@code java.net} and
+ * Apache HTTP stack. So {@link URLConnection} and Apache's {@code HttpClient} will use
+ * them automatically.
*
* Other HTTP stacks will need to obtain the proxy info from
* {@link Proxy#PROXY_CHANGE_ACTION} broadcast as the extra {@link Proxy#EXTRA_PROXY_INFO}.
- *
- * @deprecated Please use {@link java.net.URL#openConnection}, {@link java.net.Proxy} and
- * friends. The Apache HTTP client is no longer maintained and may be removed in a future
- * release. Please visit <a href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html">this webpage</a>
- * for further details.
*/
-@Deprecated
public class ProxyInfo implements Parcelable {
private String mHost;
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index cfd20a0..90a2460 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -367,13 +367,7 @@
* @return {@code true} if the destination and prefix length cover the given address.
*/
public boolean matches(InetAddress destination) {
- if (destination == null) return false;
-
- // match the route destination and destination with prefix length
- InetAddress dstNet = NetworkUtils.getNetworkPart(destination,
- mDestination.getPrefixLength());
-
- return mDestination.getAddress().equals(dstNet);
+ return mDestination.contains(destination);
}
/**
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index 37ee961..7f1b179 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -21,7 +21,6 @@
import android.os.Parcel;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 9092512..fada7ac 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -23,6 +23,14 @@
#include <android_runtime/AndroidRuntime.h>
#include <utils/Log.h>
#include <arpa/inet.h>
+#include <net/if.h>
+#include <linux/filter.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <net/if_ether.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
#include <cutils/properties.h>
#include "core_jni_helpers.h"
@@ -32,27 +40,18 @@
int ifc_disable(const char *ifname);
int ifc_reset_connections(const char *ifname, int reset_mask);
-int dhcp_do_request(const char * const ifname,
- const char *ipaddr,
- const char *gateway,
- uint32_t *prefixLength,
- const char *dns[],
- const char *server,
- uint32_t *lease,
- const char *vendorInfo,
- const char *domains,
- const char *mtu);
-
-int dhcp_do_request_renew(const char * const ifname,
- const char *ipaddr,
- const char *gateway,
- uint32_t *prefixLength,
- const char *dns[],
- const char *server,
- uint32_t *lease,
- const char *vendorInfo,
- const char *domains,
- const char *mtu);
+int dhcp_start(const char * const ifname);
+int dhcp_start_renew(const char * const ifname);
+int dhcp_get_results(const char * const ifname,
+ const char *ipaddr,
+ const char *gateway,
+ uint32_t *prefixLength,
+ const char *dns[],
+ const char *server,
+ uint32_t *lease,
+ const char *vendorInfo,
+ const char *domains,
+ const char *mtu);
int dhcp_stop(const char *ifname);
int dhcp_release_lease(const char *ifname);
@@ -63,6 +62,8 @@
namespace android {
+static const uint16_t kDhcpClientPort = 68;
+
/*
* The following remembers the jfieldID's of the fields
* of the DhcpInfo Java object, so that we don't have
@@ -94,8 +95,8 @@
return (jint)result;
}
-static jboolean android_net_utils_runDhcpCommon(JNIEnv* env, jobject clazz, jstring ifname,
- jobject dhcpResults, bool renew)
+static jboolean android_net_utils_getDhcpResults(JNIEnv* env, jobject clazz, jstring ifname,
+ jobject dhcpResults)
{
int result;
char ipaddr[PROPERTY_VALUE_MAX];
@@ -115,15 +116,10 @@
const char *nameStr = env->GetStringUTFChars(ifname, NULL);
if (nameStr == NULL) return (jboolean)false;
- if (renew) {
- result = ::dhcp_do_request_renew(nameStr, ipaddr, gateway, &prefixLength,
- dns, server, &lease, vendorInfo, domains, mtu);
- } else {
- result = ::dhcp_do_request(nameStr, ipaddr, gateway, &prefixLength,
- dns, server, &lease, vendorInfo, domains, mtu);
- }
+ result = ::dhcp_get_results(nameStr, ipaddr, gateway, &prefixLength,
+ dns, server, &lease, vendorInfo, domains, mtu);
if (result != 0) {
- ALOGD("dhcp_do_request failed : %s (%s)", nameStr, renew ? "renew" : "new");
+ ALOGD("dhcp_get_results failed : %s (%s)", nameStr, ::dhcp_get_errmsg());
}
env->ReleaseStringUTFChars(ifname, nameStr);
@@ -183,19 +179,28 @@
return (jboolean)(result == 0);
}
-
-static jboolean android_net_utils_runDhcp(JNIEnv* env, jobject clazz, jstring ifname, jobject info)
+static jboolean android_net_utils_startDhcp(JNIEnv* env, jobject clazz, jstring ifname)
{
- return android_net_utils_runDhcpCommon(env, clazz, ifname, info, false);
+ const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+ if (nameStr == NULL) return (jboolean)false;
+ if (::dhcp_start(nameStr) != 0) {
+ ALOGD("dhcp_start failed : %s", nameStr);
+ return (jboolean)false;
+ }
+ return (jboolean)true;
}
-static jboolean android_net_utils_runDhcpRenew(JNIEnv* env, jobject clazz, jstring ifname,
- jobject info)
+static jboolean android_net_utils_startDhcpRenew(JNIEnv* env, jobject clazz, jstring ifname)
{
- return android_net_utils_runDhcpCommon(env, clazz, ifname, info, true);
+ const char *nameStr = env->GetStringUTFChars(ifname, NULL);
+ if (nameStr == NULL) return (jboolean)false;
+ if (::dhcp_start_renew(nameStr) != 0) {
+ ALOGD("dhcp_start_renew failed : %s", nameStr);
+ return (jboolean)false;
+ }
+ return (jboolean)true;
}
-
static jboolean android_net_utils_stopDhcp(JNIEnv* env, jobject clazz, jstring ifname)
{
int result;
@@ -221,12 +226,50 @@
return env->NewStringUTF(::dhcp_get_errmsg());
}
+static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
+{
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ uint32_t ip_offset = sizeof(ether_header);
+ uint32_t proto_offset = ip_offset + offsetof(iphdr, protocol);
+ uint32_t flags_offset = ip_offset + offsetof(iphdr, frag_off);
+ uint32_t dport_indirect_offset = ip_offset + offsetof(udphdr, dest);
+ struct sock_filter filter_code[] = {
+ // Check the protocol is UDP.
+ BPF_STMT(BPF_LD | BPF_B | BPF_ABS, proto_offset),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, IPPROTO_UDP, 0, 6),
+
+ // Check this is not a fragment.
+ BPF_STMT(BPF_LD | BPF_H | BPF_ABS, flags_offset),
+ BPF_JUMP(BPF_JMP | BPF_JSET | BPF_K, 0x1fff, 4, 0),
+
+ // Get the IP header length.
+ BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, ip_offset),
+
+ // Check the destination port.
+ BPF_STMT(BPF_LD | BPF_H | BPF_IND, dport_indirect_offset),
+ BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, kDhcpClientPort, 0, 1),
+
+ // Accept or reject.
+ BPF_STMT(BPF_RET | BPF_K, 0xffff),
+ BPF_STMT(BPF_RET | BPF_K, 0)
+ };
+ struct sock_fprog filter = {
+ sizeof(filter_code) / sizeof(filter_code[0]),
+ filter_code,
+ };
+
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
+ }
+}
+
static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
{
return (jboolean) !setNetworkForProcess(netId);
}
-static jint android_net_utils_getNetworkBoundToProcess(JNIEnv *env, jobject thiz)
+static jint android_net_utils_getBoundNetworkForProcess(JNIEnv *env, jobject thiz)
{
return getNetworkForProcess();
}
@@ -248,6 +291,12 @@
return (jboolean) !protectFromVpn(socket);
}
+static jboolean android_net_utils_queryUserAccess(JNIEnv *env, jobject thiz, jint uid, jint netId)
+{
+ return (jboolean) !queryUserAccess(uid, netId);
+}
+
+
// ----------------------------------------------------------------------------
/*
@@ -256,16 +305,19 @@
static JNINativeMethod gNetworkUtilMethods[] = {
/* name, signature, funcPtr */
{ "resetConnections", "(Ljava/lang/String;I)I", (void *)android_net_utils_resetConnections },
- { "runDhcp", "(Ljava/lang/String;Landroid/net/DhcpResults;)Z", (void *)android_net_utils_runDhcp },
- { "runDhcpRenew", "(Ljava/lang/String;Landroid/net/DhcpResults;)Z", (void *)android_net_utils_runDhcpRenew },
+ { "startDhcp", "(Ljava/lang/String;)Z", (void *)android_net_utils_startDhcp },
+ { "startDhcpRenew", "(Ljava/lang/String;)Z", (void *)android_net_utils_startDhcpRenew },
+ { "getDhcpResults", "(Ljava/lang/String;Landroid/net/DhcpResults;)Z", (void *)android_net_utils_getDhcpResults },
{ "stopDhcp", "(Ljava/lang/String;)Z", (void *)android_net_utils_stopDhcp },
{ "releaseDhcpLease", "(Ljava/lang/String;)Z", (void *)android_net_utils_releaseDhcpLease },
{ "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
{ "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
- { "getNetworkBoundToProcess", "()I", (void*) android_net_utils_getNetworkBoundToProcess },
+ { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
{ "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
{ "bindSocketToNetwork", "(II)I", (void*) android_net_utils_bindSocketToNetwork },
{ "protectFromVpn", "(I)Z", (void*)android_net_utils_protectFromVpn },
+ { "queryUserAccess", "(II)Z", (void*)android_net_utils_queryUserAccess },
+ { "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
};
int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/tests/coretests/src/android/net/IpPrefixTest.java b/core/tests/coretests/src/android/net/IpPrefixTest.java
index cf278fb..fcc6389 100644
--- a/core/tests/coretests/src/android/net/IpPrefixTest.java
+++ b/core/tests/coretests/src/android/net/IpPrefixTest.java
@@ -29,6 +29,10 @@
public class IpPrefixTest extends TestCase {
+ private static InetAddress Address(String addr) {
+ return InetAddress.parseNumericAddress(addr);
+ }
+
// Explicitly cast everything to byte because "error: possible loss of precision".
private static final byte[] IPV4_BYTES = { (byte) 192, (byte) 0, (byte) 2, (byte) 4};
private static final byte[] IPV6_BYTES = {
@@ -209,6 +213,34 @@
}
@SmallTest
+ public void testContains() {
+ IpPrefix p = new IpPrefix("2001:db8:f00::ace:d00d/127");
+ assertTrue(p.contains(Address("2001:db8:f00::ace:d00c")));
+ assertTrue(p.contains(Address("2001:db8:f00::ace:d00d")));
+ assertFalse(p.contains(Address("2001:db8:f00::ace:d00e")));
+ assertFalse(p.contains(Address("2001:db8:f00::bad:d00d")));
+ assertFalse(p.contains(Address("2001:4868:4860::8888")));
+ assertFalse(p.contains(null));
+ assertFalse(p.contains(Address("8.8.8.8")));
+
+ p = new IpPrefix("192.0.2.0/23");
+ assertTrue(p.contains(Address("192.0.2.43")));
+ assertTrue(p.contains(Address("192.0.3.21")));
+ assertFalse(p.contains(Address("192.0.0.21")));
+ assertFalse(p.contains(Address("8.8.8.8")));
+ assertFalse(p.contains(Address("2001:4868:4860::8888")));
+
+ IpPrefix ipv6Default = new IpPrefix("::/0");
+ assertTrue(ipv6Default.contains(Address("2001:db8::f00")));
+ assertFalse(ipv6Default.contains(Address("192.0.2.1")));
+
+ IpPrefix ipv4Default = new IpPrefix("0.0.0.0/0");
+ assertTrue(ipv4Default.contains(Address("255.255.255.255")));
+ assertTrue(ipv4Default.contains(Address("192.0.2.1")));
+ assertFalse(ipv4Default.contains(Address("2001:db8::f00")));
+ }
+
+ @SmallTest
public void testHashCode() {
IpPrefix p;
int oldCode = -1;
diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
index abfed6e..ea444a4 100644
--- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java
+++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java
@@ -16,7 +16,10 @@
package android.net;
+import android.net.IpPrefix;
+import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.LinkProperties.ProvisioningChange;
import android.net.RouteInfo;
import android.system.OsConstants;
import android.test.suitebuilder.annotation.SmallTest;
@@ -25,6 +28,7 @@
import java.net.InetAddress;
import java.util.ArrayList;
+
public class LinkPropertiesTest extends TestCase {
private static InetAddress ADDRV4 = NetworkUtils.numericToInetAddress("75.208.6.1");
private static InetAddress ADDRV6 = NetworkUtils.numericToInetAddress(
@@ -34,7 +38,8 @@
private static InetAddress DNS6 = NetworkUtils.numericToInetAddress("2001:4860:4860::8888");
private static InetAddress GATEWAY1 = NetworkUtils.numericToInetAddress("75.208.8.1");
private static InetAddress GATEWAY2 = NetworkUtils.numericToInetAddress("69.78.8.1");
- private static InetAddress GATEWAY6 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
+ private static InetAddress GATEWAY61 = NetworkUtils.numericToInetAddress("fe80::6:0000:613");
+ private static InetAddress GATEWAY62 = NetworkUtils.numericToInetAddress("fe80::6:2222");
private static String NAME = "qmi0";
private static int MTU = 1500;
@@ -466,6 +471,8 @@
assertFalse("v4only:addr+dns", lp4.isProvisioned());
lp4.addRoute(new RouteInfo(GATEWAY1));
assertTrue("v4only:addr+dns+route", lp4.isProvisioned());
+ assertTrue("v4only:addr+dns+route", lp4.isIPv4Provisioned());
+ assertFalse("v4only:addr+dns+route", lp4.isIPv6Provisioned());
LinkProperties lp6 = new LinkProperties();
assertFalse("v6only:empty", lp6.isProvisioned());
@@ -473,11 +480,14 @@
assertFalse("v6only:fe80-only", lp6.isProvisioned());
lp6.addDnsServer(DNS6);
assertFalse("v6only:fe80+dns", lp6.isProvisioned());
- lp6.addRoute(new RouteInfo(GATEWAY6));
+ lp6.addRoute(new RouteInfo(GATEWAY61));
assertFalse("v6only:fe80+dns+route", lp6.isProvisioned());
lp6.addLinkAddress(LINKADDRV6);
+ assertTrue("v6only:fe80+global+dns+route", lp6.isIPv6Provisioned());
assertTrue("v6only:fe80+global+dns+route", lp6.isProvisioned());
lp6.removeLinkAddress(LINKADDRV6LINKLOCAL);
+ assertFalse("v6only:global+dns+route", lp6.isIPv4Provisioned());
+ assertTrue("v6only:global+dns+route", lp6.isIPv6Provisioned());
assertTrue("v6only:global+dns+route", lp6.isProvisioned());
LinkProperties lp46 = new LinkProperties();
@@ -487,15 +497,153 @@
lp46.addDnsServer(DNS6);
assertFalse("dualstack:missing-routes", lp46.isProvisioned());
lp46.addRoute(new RouteInfo(GATEWAY1));
+ assertTrue("dualstack:v4-provisioned", lp46.isIPv4Provisioned());
+ assertFalse("dualstack:v4-provisioned", lp46.isIPv6Provisioned());
assertTrue("dualstack:v4-provisioned", lp46.isProvisioned());
- lp6.addRoute(new RouteInfo(GATEWAY6));
+ lp46.addRoute(new RouteInfo(GATEWAY61));
+ assertTrue("dualstack:both-provisioned", lp46.isIPv4Provisioned());
+ assertTrue("dualstack:both-provisioned", lp46.isIPv6Provisioned());
assertTrue("dualstack:both-provisioned", lp46.isProvisioned());
// A link with an IPv6 address and default route, but IPv4 DNS server.
LinkProperties mixed = new LinkProperties();
mixed.addLinkAddress(LINKADDRV6);
mixed.addDnsServer(DNS1);
- mixed.addRoute(new RouteInfo(GATEWAY6));
+ mixed.addRoute(new RouteInfo(GATEWAY61));
+ assertFalse("mixed:addr6+route6+dns4", mixed.isIPv4Provisioned());
+ assertFalse("mixed:addr6+route6+dns4", mixed.isIPv6Provisioned());
assertFalse("mixed:addr6+route6+dns4", mixed.isProvisioned());
}
+
+ @SmallTest
+ public void testCompareProvisioning() {
+ LinkProperties v4lp = new LinkProperties();
+ v4lp.addLinkAddress(LINKADDRV4);
+ v4lp.addRoute(new RouteInfo(GATEWAY1));
+ v4lp.addDnsServer(DNS1);
+ assertTrue(v4lp.isProvisioned());
+
+ LinkProperties v4r = new LinkProperties(v4lp);
+ v4r.removeDnsServer(DNS1);
+ assertFalse(v4r.isProvisioned());
+
+ assertEquals(ProvisioningChange.STILL_NOT_PROVISIONED,
+ LinkProperties.compareProvisioning(v4r, v4r));
+ assertEquals(ProvisioningChange.LOST_PROVISIONING,
+ LinkProperties.compareProvisioning(v4lp, v4r));
+ assertEquals(ProvisioningChange.GAINED_PROVISIONING,
+ LinkProperties.compareProvisioning(v4r, v4lp));
+ assertEquals(ProvisioningChange.STILL_PROVISIONED,
+ LinkProperties.compareProvisioning(v4lp, v4lp));
+
+ // Check that losing IPv4 provisioning on a dualstack network is
+ // seen as a total loss of provisioning.
+ LinkProperties v6lp = new LinkProperties();
+ v6lp.addLinkAddress(LINKADDRV6);
+ v6lp.addRoute(new RouteInfo(GATEWAY61));
+ v6lp.addDnsServer(DNS6);
+ assertFalse(v6lp.isIPv4Provisioned());
+ assertTrue(v6lp.isIPv6Provisioned());
+ assertTrue(v6lp.isProvisioned());
+
+ LinkProperties v46lp = new LinkProperties(v6lp);
+ v46lp.addLinkAddress(LINKADDRV4);
+ v46lp.addRoute(new RouteInfo(GATEWAY1));
+ v46lp.addDnsServer(DNS1);
+ assertTrue(v46lp.isIPv4Provisioned());
+ assertTrue(v46lp.isIPv6Provisioned());
+ assertTrue(v46lp.isProvisioned());
+
+ assertEquals(ProvisioningChange.STILL_PROVISIONED,
+ LinkProperties.compareProvisioning(v6lp, v46lp));
+ assertEquals(ProvisioningChange.LOST_PROVISIONING,
+ LinkProperties.compareProvisioning(v46lp, v6lp));
+
+ // Check that losing and gaining a secondary router does not change
+ // the provisioning status.
+ LinkProperties v6lp2 = new LinkProperties(v6lp);
+ v6lp2.addRoute(new RouteInfo(GATEWAY62));
+ assertTrue(v6lp2.isProvisioned());
+
+ assertEquals(ProvisioningChange.STILL_PROVISIONED,
+ LinkProperties.compareProvisioning(v6lp2, v6lp));
+ assertEquals(ProvisioningChange.STILL_PROVISIONED,
+ LinkProperties.compareProvisioning(v6lp, v6lp2));
+ }
+
+ @SmallTest
+ public void testIsReachable() {
+ final LinkProperties v4lp = new LinkProperties();
+ assertFalse(v4lp.isReachable(DNS1));
+ assertFalse(v4lp.isReachable(DNS2));
+
+ // Add an on-link route, making the on-link DNS server reachable,
+ // but there is still no IPv4 address.
+ assertTrue(v4lp.addRoute(new RouteInfo(
+ new IpPrefix(NetworkUtils.numericToInetAddress("75.208.0.0"), 16))));
+ assertFalse(v4lp.isReachable(DNS1));
+ assertFalse(v4lp.isReachable(DNS2));
+
+ // Adding an IPv4 address (right now, any IPv4 address) means we use
+ // the routes to compute likely reachability.
+ assertTrue(v4lp.addLinkAddress(new LinkAddress(ADDRV4, 16)));
+ assertTrue(v4lp.isReachable(DNS1));
+ assertFalse(v4lp.isReachable(DNS2));
+
+ // Adding a default route makes the off-link DNS server reachable.
+ assertTrue(v4lp.addRoute(new RouteInfo(GATEWAY1)));
+ assertTrue(v4lp.isReachable(DNS1));
+ assertTrue(v4lp.isReachable(DNS2));
+
+ final LinkProperties v6lp = new LinkProperties();
+ final InetAddress kLinkLocalDns = NetworkUtils.numericToInetAddress("fe80::6:1");
+ final InetAddress kLinkLocalDnsWithScope = NetworkUtils.numericToInetAddress("fe80::6:2%43");
+ final InetAddress kOnLinkDns = NetworkUtils.numericToInetAddress("2001:db8:85a3::53");
+ assertFalse(v6lp.isReachable(kLinkLocalDns));
+ assertFalse(v6lp.isReachable(kLinkLocalDnsWithScope));
+ assertFalse(v6lp.isReachable(kOnLinkDns));
+ assertFalse(v6lp.isReachable(DNS6));
+
+ // Add a link-local route, making the link-local DNS servers reachable. Because
+ // we assume the presence of an IPv6 link-local address, link-local DNS servers
+ // are considered reachable, but only those with a non-zero scope identifier.
+ assertTrue(v6lp.addRoute(new RouteInfo(
+ new IpPrefix(NetworkUtils.numericToInetAddress("fe80::"), 64))));
+ assertFalse(v6lp.isReachable(kLinkLocalDns));
+ assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
+ assertFalse(v6lp.isReachable(kOnLinkDns));
+ assertFalse(v6lp.isReachable(DNS6));
+
+ // Add a link-local address--nothing changes.
+ assertTrue(v6lp.addLinkAddress(LINKADDRV6LINKLOCAL));
+ assertFalse(v6lp.isReachable(kLinkLocalDns));
+ assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
+ assertFalse(v6lp.isReachable(kOnLinkDns));
+ assertFalse(v6lp.isReachable(DNS6));
+
+ // Add a global route on link, but no global address yet. DNS servers reachable
+ // via a route that doesn't require a gateway: give them the benefit of the
+ // doubt and hope the link-local source address suffices for communication.
+ assertTrue(v6lp.addRoute(new RouteInfo(
+ new IpPrefix(NetworkUtils.numericToInetAddress("2001:db8:85a3::"), 64))));
+ assertFalse(v6lp.isReachable(kLinkLocalDns));
+ assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
+ assertTrue(v6lp.isReachable(kOnLinkDns));
+ assertFalse(v6lp.isReachable(DNS6));
+
+ // Add a global address; the on-link global address DNS server is (still)
+ // presumed reachable.
+ assertTrue(v6lp.addLinkAddress(new LinkAddress(ADDRV6, 64)));
+ assertFalse(v6lp.isReachable(kLinkLocalDns));
+ assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
+ assertTrue(v6lp.isReachable(kOnLinkDns));
+ assertFalse(v6lp.isReachable(DNS6));
+
+ // Adding a default route makes the off-link DNS server reachable.
+ assertTrue(v6lp.addRoute(new RouteInfo(GATEWAY62)));
+ assertFalse(v6lp.isReachable(kLinkLocalDns));
+ assertTrue(v6lp.isReachable(kLinkLocalDnsWithScope));
+ assertTrue(v6lp.isReachable(kOnLinkDns));
+ assertTrue(v6lp.isReachable(DNS6));
+ }
}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 9ee4e20..a470de1 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -18,6 +18,8 @@
import static android.net.NetworkStats.SET_DEFAULT;
import static android.net.NetworkStats.SET_FOREGROUND;
+import static android.net.NetworkStats.SET_DBG_VPN_IN;
+import static android.net.NetworkStats.SET_DBG_VPN_OUT;
import static android.net.NetworkStats.SET_ALL;
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.TAG_NONE;
@@ -320,6 +322,92 @@
red.combineAllValues(blue);
}
+ public void testMigrateTun() throws Exception {
+ final int tunUid = 10030;
+ final String tunIface = "tun0";
+ final String underlyingIface = "wlan0";
+ final int testTag1 = 8888;
+ NetworkStats delta = new NetworkStats(TEST_START, 17)
+ .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, 39605L, 46L, 12259L, 55L, 0L)
+ .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+ .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, 72667L, 197L, 43909L, 241L, 0L)
+ .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, 9297L, 17L, 4128L, 21L, 0L)
+ // VPN package also uses some traffic through unprotected network.
+ .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, 4983L, 10L, 1801L, 12L, 0L)
+ .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+ // Tag entries
+ .addValues(tunIface, 10120, SET_DEFAULT, testTag1, 21691L, 41L, 13820L, 51L, 0L)
+ .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, 1281L, 2L, 665L, 2L, 0L)
+ // Irrelevant entries
+ .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, 1685L, 5L, 2070L, 6L, 0L)
+ // Underlying Iface entries
+ .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, 5178L, 8L, 2139L, 11L, 0L)
+ .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L)
+ .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, 149873L, 287L,
+ 59217L /* smaller than sum(tun0) */, 299L /* smaller than sum(tun0) */, 0L)
+ .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+
+ assertTrue(delta.migrateTun(tunUid, tunIface, underlyingIface));
+ assertEquals(21, delta.size());
+
+ // tunIface and TEST_IFACE entries are not changed.
+ assertValues(delta, 0, tunIface, 10100, SET_DEFAULT, TAG_NONE,
+ 39605L, 46L, 12259L, 55L, 0L);
+ assertValues(delta, 1, tunIface, 10100, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+ assertValues(delta, 2, tunIface, 10120, SET_DEFAULT, TAG_NONE,
+ 72667L, 197L, 43909L, 241L, 0L);
+ assertValues(delta, 3, tunIface, 10120, SET_FOREGROUND, TAG_NONE,
+ 9297L, 17L, 4128L, 21L, 0L);
+ assertValues(delta, 4, tunIface, tunUid, SET_DEFAULT, TAG_NONE,
+ 4983L, 10L, 1801L, 12L, 0L);
+ assertValues(delta, 5, tunIface, tunUid, SET_FOREGROUND, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
+ assertValues(delta, 6, tunIface, 10120, SET_DEFAULT, testTag1,
+ 21691L, 41L, 13820L, 51L, 0L);
+ assertValues(delta, 7, tunIface, 10120, SET_FOREGROUND, testTag1, 1281L, 2L, 665L, 2L, 0L);
+ assertValues(delta, 8, TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, 1685L, 5L, 2070L, 6L, 0L);
+
+ // Existing underlying Iface entries are updated
+ assertValues(delta, 9, underlyingIface, 10100, SET_DEFAULT, TAG_NONE,
+ 44783L, 54L, 13829L, 60L, 0L);
+ assertValues(delta, 10, underlyingIface, 10100, SET_FOREGROUND, TAG_NONE,
+ 0L, 0L, 0L, 0L, 0L);
+
+ // VPN underlying Iface entries are updated
+ assertValues(delta, 11, underlyingIface, tunUid, SET_DEFAULT, TAG_NONE,
+ 28304L, 27L, 1719L, 12L, 0L);
+ assertValues(delta, 12, underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE,
+ 0L, 0L, 0L, 0L, 0L);
+
+ // New entries are added for new application's underlying Iface traffic
+ assertContains(delta, underlyingIface, 10120, SET_DEFAULT, TAG_NONE,
+ 72667L, 197L, 41872l, 219L, 0L);
+ assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, TAG_NONE,
+ 9297L, 17L, 3936, 19L, 0L);
+ assertContains(delta, underlyingIface, 10120, SET_DEFAULT, testTag1,
+ 21691L, 41L, 13179L, 46L, 0L);
+ assertContains(delta, underlyingIface, 10120, SET_FOREGROUND, testTag1,
+ 1281L, 2L, 634L, 1L, 0L);
+
+ // New entries are added for debug purpose
+ assertContains(delta, underlyingIface, 10100, SET_DBG_VPN_IN, TAG_NONE,
+ 39605L, 46L, 11690, 49, 0);
+ assertContains(delta, underlyingIface, 10120, SET_DBG_VPN_IN, TAG_NONE,
+ 81964, 214, 45808, 238, 0);
+ assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_IN, TAG_NONE,
+ 4983, 10, 1717, 10, 0);
+ assertContains(delta, underlyingIface, tunUid, SET_DBG_VPN_OUT, TAG_NONE,
+ 126552, 270, 59215, 297, 0);
+
+ }
+
+ private static void assertContains(NetworkStats stats, String iface, int uid, int set,
+ int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
+ int index = stats.findIndex(iface, uid, set, tag);
+ assertTrue(index != -1);
+ assertValues(stats, index, iface, uid, set, tag,
+ rxBytes, rxPackets, txBytes, txPackets, operations);
+ }
+
private static void assertValues(NetworkStats stats, int index, String iface, int uid, int set,
int tag, long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
final NetworkStats.Entry entry = stats.getValues(index, null);
diff --git a/core/tests/coretests/src/android/net/NetworkTest.java b/core/tests/coretests/src/android/net/NetworkTest.java
new file mode 100644
index 0000000..74b6d98
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import static android.test.MoreAsserts.assertNotEqual;
+
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.net.Network;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Inet6Address;
+import java.net.SocketException;
+
+import junit.framework.TestCase;
+
+public class NetworkTest extends TestCase {
+ final Network mNetwork = new Network(99);
+
+ @SmallTest
+ public void testBindSocketOfInvalidFdThrows() throws Exception {
+
+ final FileDescriptor fd = new FileDescriptor();
+ assertFalse(fd.valid());
+
+ try {
+ mNetwork.bindSocket(fd);
+ fail("SocketException not thrown");
+ } catch (SocketException expected) {}
+ }
+
+ @SmallTest
+ public void testBindSocketOfNonSocketFdThrows() throws Exception {
+ final File devNull = new File("/dev/null");
+ assertTrue(devNull.canRead());
+
+ final FileInputStream fis = new FileInputStream(devNull);
+ assertTrue(null != fis.getFD());
+ assertTrue(fis.getFD().valid());
+
+ try {
+ mNetwork.bindSocket(fis.getFD());
+ fail("SocketException not thrown");
+ } catch (SocketException expected) {}
+ }
+
+ @SmallTest
+ public void testBindSocketOfConnectedDatagramSocketThrows() throws Exception {
+ final DatagramSocket mDgramSocket = new DatagramSocket(0, (InetAddress) Inet6Address.ANY);
+ mDgramSocket.connect((InetAddress) Inet6Address.LOOPBACK, 53);
+ assertTrue(mDgramSocket.isConnected());
+
+ try {
+ mNetwork.bindSocket(mDgramSocket);
+ fail("SocketException not thrown");
+ } catch (SocketException expected) {}
+ }
+
+ @SmallTest
+ public void testBindSocketOfLocalSocketThrows() throws Exception {
+ final LocalSocket mLocalClient = new LocalSocket();
+ mLocalClient.bind(new LocalSocketAddress("testClient"));
+ assertTrue(mLocalClient.getFileDescriptor().valid());
+
+ try {
+ mNetwork.bindSocket(mLocalClient.getFileDescriptor());
+ fail("SocketException not thrown");
+ } catch (SocketException expected) {}
+
+ final LocalServerSocket mLocalServer = new LocalServerSocket("testServer");
+ mLocalClient.connect(mLocalServer.getLocalSocketAddress());
+ assertTrue(mLocalClient.isConnected());
+
+ try {
+ mNetwork.bindSocket(mLocalClient.getFileDescriptor());
+ fail("SocketException not thrown");
+ } catch (SocketException expected) {}
+ }
+
+ @SmallTest
+ public void testZeroIsObviousForDebugging() {
+ Network zero = new Network(0);
+ assertEquals(0, zero.hashCode());
+ assertEquals(0, zero.getNetworkHandle());
+ assertEquals("0", zero.toString());
+ }
+
+ @SmallTest
+ public void testGetNetworkHandle() {
+ Network one = new Network(1);
+ Network two = new Network(2);
+ Network three = new Network(3);
+
+ // None of the hashcodes are zero.
+ assertNotEqual(0, one.hashCode());
+ assertNotEqual(0, two.hashCode());
+ assertNotEqual(0, three.hashCode());
+
+ // All the hashcodes are distinct.
+ assertNotEqual(one.hashCode(), two.hashCode());
+ assertNotEqual(one.hashCode(), three.hashCode());
+ assertNotEqual(two.hashCode(), three.hashCode());
+
+ // None of the handles are zero.
+ assertNotEqual(0, one.getNetworkHandle());
+ assertNotEqual(0, two.getNetworkHandle());
+ assertNotEqual(0, three.getNetworkHandle());
+
+ // All the handles are distinct.
+ assertNotEqual(one.getNetworkHandle(), two.getNetworkHandle());
+ assertNotEqual(one.getNetworkHandle(), three.getNetworkHandle());
+ assertNotEqual(two.getNetworkHandle(), three.getNetworkHandle());
+
+ // The handles are not equal to the hashcodes.
+ assertNotEqual(one.hashCode(), one.getNetworkHandle());
+ assertNotEqual(two.hashCode(), two.getNetworkHandle());
+ assertNotEqual(three.hashCode(), three.getNetworkHandle());
+
+ // Adjust as necessary to test an implementation's specific constants.
+ // When running with runtest, "adb logcat -s TestRunner" can be useful.
+ assertEquals(4311403230L, one.getNetworkHandle());
+ assertEquals(8606370526L, two.getNetworkHandle());
+ assertEquals(12901337822L, three.getNetworkHandle());
+ }
+}
diff --git a/core/tests/coretests/src/android/net/NetworkUtilsTest.java b/core/tests/coretests/src/android/net/NetworkUtilsTest.java
new file mode 100644
index 0000000..8d51c3b
--- /dev/null
+++ b/core/tests/coretests/src/android/net/NetworkUtilsTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.net.NetworkUtils;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+
+import junit.framework.TestCase;
+
+public class NetworkUtilsTest extends TestCase {
+
+ private InetAddress Address(String addr) {
+ return InetAddress.parseNumericAddress(addr);
+ }
+
+ private Inet4Address IPv4Address(String addr) {
+ return (Inet4Address) Address(addr);
+ }
+
+ @SmallTest
+ public void testGetImplicitNetmask() {
+ assertEquals(8, NetworkUtils.getImplicitNetmask(IPv4Address("4.2.2.2")));
+ assertEquals(8, NetworkUtils.getImplicitNetmask(IPv4Address("10.5.6.7")));
+ assertEquals(16, NetworkUtils.getImplicitNetmask(IPv4Address("173.194.72.105")));
+ assertEquals(16, NetworkUtils.getImplicitNetmask(IPv4Address("172.23.68.145")));
+ assertEquals(24, NetworkUtils.getImplicitNetmask(IPv4Address("192.0.2.1")));
+ assertEquals(24, NetworkUtils.getImplicitNetmask(IPv4Address("192.168.5.1")));
+ assertEquals(32, NetworkUtils.getImplicitNetmask(IPv4Address("224.0.0.1")));
+ assertEquals(32, NetworkUtils.getImplicitNetmask(IPv4Address("255.6.7.8")));
+ }
+
+ private void assertInvalidNetworkMask(Inet4Address addr) {
+ try {
+ NetworkUtils.netmaskToPrefixLength(addr);
+ fail("Invalid netmask " + addr.getHostAddress() + " did not cause exception");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ @SmallTest
+ public void testNetmaskToPrefixLength() {
+ assertEquals(0, NetworkUtils.netmaskToPrefixLength(IPv4Address("0.0.0.0")));
+ assertEquals(9, NetworkUtils.netmaskToPrefixLength(IPv4Address("255.128.0.0")));
+ assertEquals(17, NetworkUtils.netmaskToPrefixLength(IPv4Address("255.255.128.0")));
+ assertEquals(23, NetworkUtils.netmaskToPrefixLength(IPv4Address("255.255.254.0")));
+ assertEquals(31, NetworkUtils.netmaskToPrefixLength(IPv4Address("255.255.255.254")));
+ assertEquals(32, NetworkUtils.netmaskToPrefixLength(IPv4Address("255.255.255.255")));
+
+ assertInvalidNetworkMask(IPv4Address("0.0.0.1"));
+ assertInvalidNetworkMask(IPv4Address("255.255.255.253"));
+ assertInvalidNetworkMask(IPv4Address("255.255.0.255"));
+ }
+}
diff --git a/core/tests/coretests/src/android/net/RouteInfoTest.java b/core/tests/coretests/src/android/net/RouteInfoTest.java
index 0b88bc7..831fefd 100644
--- a/core/tests/coretests/src/android/net/RouteInfoTest.java
+++ b/core/tests/coretests/src/android/net/RouteInfoTest.java
@@ -90,6 +90,7 @@
assertFalse(r.matches(Address("2001:db8:f00::ace:d00e")));
assertFalse(r.matches(Address("2001:db8:f00::bad:d00d")));
assertFalse(r.matches(Address("2001:4868:4860::8888")));
+ assertFalse(r.matches(Address("8.8.8.8")));
r = new PatchedRouteInfo(Prefix("192.0.2.0/23"), null, "wlan0");
assertTrue(r.matches(Address("192.0.2.43")));
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 7ffa0b6..4919bed 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -16,18 +16,23 @@
package com.android.server;
-import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
-import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_VPN;
import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -88,17 +93,23 @@
import android.security.KeyStore;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.LocalLog.ReadOnlyLocalLog;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
import android.util.Xml;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.NetworkStatsFactory;
import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnInfo;
import com.android.internal.net.VpnProfile;
import com.android.internal.telephony.DctConstants;
import com.android.internal.util.AsyncChannel;
@@ -106,6 +117,7 @@
import com.android.internal.util.XmlUtils;
import com.android.server.am.BatteryStatsService;
import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.NetworkDiagnostics;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkAgentInfo;
import com.android.server.connectivity.NetworkMonitor;
@@ -130,6 +142,7 @@
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -161,6 +174,10 @@
private static final String NETWORK_RESTORE_DELAY_PROP_NAME =
"android.telephony.apn-restore";
+ // How long to wait before putting up a "This network doesn't have an Internet connection,
+ // connect anyway?" dialog after the user selects a network that doesn't validate.
+ private static final int PROMPT_UNVALIDATED_DELAY_MS = 8 * 1000;
+
// How long to delay to removal of a pending intent based request.
// See Settings.Secure.CONNECTIVITY_RELEASE_PENDING_INTENT_DELAY_MS
private final int mReleasePendingIntentDelayMs;
@@ -184,12 +201,11 @@
/** Set of ifaces that are costly. */
private HashSet<String> mMeteredIfaces = Sets.newHashSet();
- private Context mContext;
+ final private Context mContext;
private int mNetworkPreference;
// 0 is full bad, 100 is full good
private int mDefaultInetConditionPublished = 0;
- private Object mDnsLock = new Object();
private int mNumDnsEntries;
private boolean mTestMode;
@@ -204,23 +220,13 @@
private static final int ENABLED = 1;
private static final int DISABLED = 0;
- // Arguments to rematchNetworkAndRequests()
- private enum NascentState {
- // Indicates a network was just validated for the first time. If the network is found to
- // be unwanted (i.e. not satisfy any NetworkRequests) it is torn down.
- JUST_VALIDATED,
- // Indicates a network was not validated for the first time immediately prior to this call.
- NOT_JUST_VALIDATED
- };
private enum ReapUnvalidatedNetworks {
- // Tear down unvalidated networks that have no chance (i.e. even if validated) of becoming
- // the highest scoring network satisfying a NetworkRequest. This should be passed when it's
- // known that there may be unvalidated networks that could potentially be reaped, and when
+ // Tear down networks that have no chance (e.g. even if validated) of becoming
+ // the highest scoring network satisfying a NetworkRequest. This should be passed when
// all networks have been rematched against all NetworkRequests.
REAP,
- // Don't reap unvalidated networks. This should be passed when it's known that there are
- // no unvalidated networks that could potentially be reaped, and when some networks have
- // not yet been rematched against all NetworkRequests.
+ // Don't reap networks. This should be passed when some networks have not yet been
+ // rematched against all NetworkRequests.
DONT_REAP
};
@@ -313,7 +319,7 @@
/**
* used to add a network request with a pending intent
- * includes a NetworkRequestInfo
+ * obj = NetworkRequestInfo
*/
private static final int EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT = 26;
@@ -324,6 +330,30 @@
*/
private static final int EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT = 27;
+ /**
+ * used to specify whether a network should be used even if unvalidated.
+ * arg1 = whether to accept the network if it's unvalidated (1 or 0)
+ * arg2 = whether to remember this choice in the future (1 or 0)
+ * obj = network
+ */
+ private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
+
+ /**
+ * used to ask the user to confirm a connection to an unvalidated network.
+ * obj = network
+ */
+ private static final int EVENT_PROMPT_UNVALIDATED = 29;
+
+ /**
+ * used internally to (re)configure mobile data always-on settings.
+ */
+ private static final int EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON = 30;
+
+ /**
+ * used to add a network listener with a pending intent
+ * obj = NetworkRequestInfo
+ */
+ private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31;
/** Handler used for internal events. */
final private InternalHandler mHandler;
@@ -355,7 +385,7 @@
private PacManager mPacManager = null;
- private SettingsObserver mSettingsObserver;
+ final private SettingsObserver mSettingsObserver;
private UserManager mUserManager;
@@ -377,6 +407,24 @@
// sequence number of NetworkRequests
private int mNextNetworkRequestId = 1;
+ // NetworkRequest activity String log entries.
+ private static final int MAX_NETWORK_REQUEST_LOGS = 20;
+ private final LocalLog mNetworkRequestInfoLogs = new LocalLog(MAX_NETWORK_REQUEST_LOGS);
+
+ // Array of <Network,ReadOnlyLocalLogs> tracking network validation and results
+ private static final int MAX_VALIDATION_LOGS = 10;
+ private final ArrayDeque<Pair<Network,ReadOnlyLocalLog>> mValidationLogs =
+ new ArrayDeque<Pair<Network,ReadOnlyLocalLog>>(MAX_VALIDATION_LOGS);
+
+ private void addValidationLogs(ReadOnlyLocalLog log, Network network) {
+ synchronized(mValidationLogs) {
+ while (mValidationLogs.size() >= MAX_VALIDATION_LOGS) {
+ mValidationLogs.removeLast();
+ }
+ mValidationLogs.addFirst(new Pair(network, log));
+ }
+ }
+
/**
* Implements support for the legacy "one network per network type" model.
*
@@ -439,11 +487,12 @@
}
}
- private void maybeLogBroadcast(NetworkAgentInfo nai, boolean connected, int type) {
+ private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type,
+ boolean isDefaultNetwork) {
if (DBG) {
- log("Sending " + (connected ? "connected" : "disconnected") +
+ log("Sending " + state +
" broadcast for type " + type + " " + nai.name() +
- " isDefaultNetwork=" + isDefaultNetwork(nai));
+ " isDefaultNetwork=" + isDefaultNetwork);
}
}
@@ -463,43 +512,63 @@
list.add(nai);
// Send a broadcast if this is the first network of its type or if it's the default.
- if (list.size() == 1 || isDefaultNetwork(nai)) {
- maybeLogBroadcast(nai, true, type);
- sendLegacyNetworkBroadcast(nai, true, type);
+ final boolean isDefaultNetwork = isDefaultNetwork(nai);
+ if (list.size() == 1 || isDefaultNetwork) {
+ maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork);
+ sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type);
}
}
/** Removes the given network from the specified legacy type list. */
- public void remove(int type, NetworkAgentInfo nai) {
+ public void remove(int type, NetworkAgentInfo nai, boolean wasDefault) {
ArrayList<NetworkAgentInfo> list = mTypeLists[type];
if (list == null || list.isEmpty()) {
return;
}
- boolean wasFirstNetwork = list.get(0).equals(nai);
+ final boolean wasFirstNetwork = list.get(0).equals(nai);
if (!list.remove(nai)) {
return;
}
- if (wasFirstNetwork || isDefaultNetwork(nai)) {
- maybeLogBroadcast(nai, false, type);
- sendLegacyNetworkBroadcast(nai, false, type);
+ final DetailedState state = DetailedState.DISCONNECTED;
+
+ if (wasFirstNetwork || wasDefault) {
+ maybeLogBroadcast(nai, state, type, wasDefault);
+ sendLegacyNetworkBroadcast(nai, state, type);
}
if (!list.isEmpty() && wasFirstNetwork) {
if (DBG) log("Other network available for type " + type +
", sending connected broadcast");
- maybeLogBroadcast(list.get(0), false, type);
- sendLegacyNetworkBroadcast(list.get(0), false, type);
+ final NetworkAgentInfo replacement = list.get(0);
+ maybeLogBroadcast(replacement, state, type, isDefaultNetwork(replacement));
+ sendLegacyNetworkBroadcast(replacement, state, type);
}
}
/** Removes the given network from all legacy type lists. */
- public void remove(NetworkAgentInfo nai) {
- if (VDBG) log("Removing agent " + nai);
+ public void remove(NetworkAgentInfo nai, boolean wasDefault) {
+ if (VDBG) log("Removing agent " + nai + " wasDefault=" + wasDefault);
for (int type = 0; type < mTypeLists.length; type++) {
- remove(type, nai);
+ remove(type, nai, wasDefault);
+ }
+ }
+
+ // send out another legacy broadcast - currently only used for suspend/unsuspend
+ // toggle
+ public void update(NetworkAgentInfo nai) {
+ final boolean isDefault = isDefaultNetwork(nai);
+ final DetailedState state = nai.networkInfo.getDetailedState();
+ for (int type = 0; type < mTypeLists.length; type++) {
+ final ArrayList<NetworkAgentInfo> list = mTypeLists[type];
+ final boolean contains = (list != null && list.contains(nai));
+ final boolean isFirst = (list != null && list.size() > 0 && nai == list.get(0));
+ if (isFirst || (contains && isDefault)) {
+ maybeLogBroadcast(nai, state, type, isDefault);
+ sendLegacyNetworkBroadcast(nai, state, type);
+ }
}
}
@@ -512,16 +581,24 @@
}
public void dump(IndentingPrintWriter pw) {
+ pw.println("mLegacyTypeTracker:");
+ pw.increaseIndent();
+ pw.print("Supported types:");
for (int type = 0; type < mTypeLists.length; type++) {
- if (mTypeLists[type] == null) continue;
- pw.print(type + " ");
- pw.increaseIndent();
- if (mTypeLists[type].size() == 0) pw.println("none");
- for (NetworkAgentInfo nai : mTypeLists[type]) {
- pw.println(naiToString(nai));
- }
- pw.decreaseIndent();
+ if (mTypeLists[type] != null) pw.print(" " + type);
}
+ pw.println();
+ pw.println("Current state:");
+ pw.increaseIndent();
+ for (int type = 0; type < mTypeLists.length; type++) {
+ if (mTypeLists[type] == null|| mTypeLists[type].size() == 0) continue;
+ for (NetworkAgentInfo nai : mTypeLists[type]) {
+ pw.println(type + " " + naiToString(nai));
+ }
+ }
+ pw.decreaseIndent();
+ pw.decreaseIndent();
+ pw.println();
}
// This class needs its own log method because it has a different TAG.
@@ -536,13 +613,14 @@
INetworkStatsService statsService, INetworkPolicyManager policyManager) {
if (DBG) log("ConnectivityService starting up");
- NetworkCapabilities netCap = new NetworkCapabilities();
- netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
- netCap.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
- mDefaultRequest = new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId());
- NetworkRequestInfo nri = new NetworkRequestInfo(null, mDefaultRequest, new Binder(),
- NetworkRequestInfo.REQUEST);
- mNetworkRequests.put(mDefaultRequest, nri);
+ mDefaultRequest = createInternetRequestForTransport(-1);
+ NetworkRequestInfo defaultNRI = new NetworkRequestInfo(null, mDefaultRequest,
+ new Binder(), NetworkRequestInfo.REQUEST);
+ mNetworkRequests.put(mDefaultRequest, defaultNRI);
+ mNetworkRequestInfoLogs.log("REGISTER " + defaultNRI);
+
+ mDefaultMobileDataRequest = createInternetRequestForTransport(
+ NetworkCapabilities.TRANSPORT_CELLULAR);
HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
handlerThread.start();
@@ -677,8 +755,8 @@
mInetLog = new ArrayList();
}
- mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY);
- mSettingsObserver.observe(mContext);
+ mSettingsObserver = new SettingsObserver(mContext, mHandler);
+ registerSettingsCallbacks();
mDataConnectionStats = new DataConnectionStats(mContext);
mDataConnectionStats.startMonitoring();
@@ -688,20 +766,58 @@
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
}
+ private NetworkRequest createInternetRequestForTransport(int transportType) {
+ NetworkCapabilities netCap = new NetworkCapabilities();
+ netCap.addCapability(NET_CAPABILITY_INTERNET);
+ netCap.addCapability(NET_CAPABILITY_NOT_RESTRICTED);
+ if (transportType > -1) {
+ netCap.addTransportType(transportType);
+ }
+ return new NetworkRequest(netCap, TYPE_NONE, nextNetworkRequestId());
+ }
+
+ private void handleMobileDataAlwaysOn() {
+ final boolean enable = (Settings.Global.getInt(
+ mContext.getContentResolver(), Settings.Global.MOBILE_DATA_ALWAYS_ON, 0) == 1);
+ final boolean isEnabled = (mNetworkRequests.get(mDefaultMobileDataRequest) != null);
+ if (enable == isEnabled) {
+ return; // Nothing to do.
+ }
+
+ if (enable) {
+ handleRegisterNetworkRequest(new NetworkRequestInfo(
+ null, mDefaultMobileDataRequest, new Binder(), NetworkRequestInfo.REQUEST));
+ } else {
+ handleReleaseNetworkRequest(mDefaultMobileDataRequest, Process.SYSTEM_UID);
+ }
+ }
+
+ private void registerSettingsCallbacks() {
+ // Watch for global HTTP proxy changes.
+ mSettingsObserver.observe(
+ Settings.Global.getUriFor(Settings.Global.HTTP_PROXY),
+ EVENT_APPLY_GLOBAL_HTTP_PROXY);
+
+ // Watch for whether or not to keep mobile data always on.
+ mSettingsObserver.observe(
+ Settings.Global.getUriFor(Settings.Global.MOBILE_DATA_ALWAYS_ON),
+ EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON);
+ }
+
private synchronized int nextNetworkRequestId() {
return mNextNetworkRequestId++;
}
- private void assignNextNetId(NetworkAgentInfo nai) {
+ @VisibleForTesting
+ protected int reserveNetId() {
synchronized (mNetworkForNetId) {
for (int i = MIN_NET_ID; i <= MAX_NET_ID; i++) {
int netId = mNextNetId;
if (++mNextNetId > MAX_NET_ID) mNextNetId = MIN_NET_ID;
// Make sure NetID unused. http://b/16815182
- if (mNetworkForNetId.get(netId) == null) {
- nai.network = new Network(netId);
- mNetworkForNetId.put(netId, nai);
- return;
+ if (!mNetIdInUse.get(netId)) {
+ mNetIdInUse.put(netId, true);
+ return netId;
}
}
}
@@ -722,7 +838,9 @@
info = new NetworkInfo(nai.networkInfo);
lp = new LinkProperties(nai.linkProperties);
nc = new NetworkCapabilities(nai.networkCapabilities);
- network = new Network(nai.network);
+ // Network objects are outwardly immutable so there is no point to duplicating.
+ // Duplicating also precludes sharing socket factories and connection pools.
+ network = nai.network;
subscriberId = (nai.networkMisc != null) ? nai.networkMisc.subscriberId : null;
}
info.setType(networkType);
@@ -769,7 +887,7 @@
Network network = null;
String subscriberId = null;
- NetworkAgentInfo nai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ NetworkAgentInfo nai = getDefaultNetwork();
final Network[] networks = getVpnUnderlyingNetworks(uid);
if (networks != null) {
@@ -790,7 +908,9 @@
info = new NetworkInfo(nai.networkInfo);
lp = new LinkProperties(nai.linkProperties);
nc = new NetworkCapabilities(nai.networkCapabilities);
- network = new Network(nai.network);
+ // Network objects are outwardly immutable so there is no point to duplicating.
+ // Duplicating also precludes sharing socket factories and connection pools.
+ network = nai.network;
subscriberId = (nai.networkMisc != null) ? nai.networkMisc.subscriberId : null;
}
}
@@ -811,7 +931,8 @@
uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
}
- if (networkCostly && (uidRules & RULE_REJECT_METERED) != 0) {
+ if ((uidRules & RULE_REJECT_ALL) != 0
+ || (networkCostly && (uidRules & RULE_REJECT_METERED) != 0)) {
return true;
}
@@ -829,14 +950,14 @@
// network is blocked; clone and override state
info = new NetworkInfo(info);
info.setDetailedState(DetailedState.BLOCKED, null, null);
- if (DBG) {
+ if (VDBG) {
log("returning Blocked NetworkInfo for ifname=" +
lp.getInterfaceName() + ", uid=" + uid);
}
}
if (info != null && mLockdownTracker != null) {
info = mLockdownTracker.augmentNetworkInfo(info);
- if (DBG) log("returning Locked NetworkInfo");
+ if (VDBG) log("returning Locked NetworkInfo");
}
return info;
}
@@ -856,42 +977,26 @@
return getFilteredNetworkInfo(state.networkInfo, state.linkProperties, uid);
}
- /**
- * Find the first Provisioning network.
- *
- * @return NetworkInfo or null if none.
- */
- private NetworkInfo getProvisioningNetworkInfo() {
- enforceAccessPermission();
-
- // Find the first Provisioning Network
- NetworkInfo provNi = null;
- for (NetworkInfo ni : getAllNetworkInfo()) {
- if (ni.isConnectedToProvisioningNetwork()) {
- provNi = ni;
- break;
- }
- }
- if (DBG) log("getProvisioningNetworkInfo: X provNi=" + provNi);
- return provNi;
- }
-
- /**
- * Find the first Provisioning network or the ActiveDefaultNetwork
- * if there is no Provisioning network
- *
- * @return NetworkInfo or null if none.
- */
@Override
- public NetworkInfo getProvisioningOrActiveNetworkInfo() {
+ public Network getActiveNetwork() {
enforceAccessPermission();
-
- NetworkInfo provNi = getProvisioningNetworkInfo();
- if (provNi == null) {
- provNi = getActiveNetworkInfo();
+ final int uid = Binder.getCallingUid();
+ final int user = UserHandle.getUserId(uid);
+ int vpnNetId = NETID_UNSET;
+ synchronized (mVpns) {
+ final Vpn vpn = mVpns.get(user);
+ if (vpn != null && vpn.appliesToUid(uid)) vpnNetId = vpn.getNetId();
}
- if (DBG) log("getProvisioningOrActiveNetworkInfo: X provNi=" + provNi);
- return provNi;
+ NetworkAgentInfo nai;
+ if (vpnNetId != NETID_UNSET) {
+ synchronized (mNetworkForNetId) {
+ nai = mNetworkForNetId.get(vpnNetId);
+ }
+ if (nai != null) return nai.network;
+ }
+ nai = getDefaultNetwork();
+ if (nai != null && isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid)) nai = null;
+ return nai != null ? nai.network : null;
}
public NetworkInfo getActiveNetworkInfoUnfiltered() {
@@ -968,30 +1073,13 @@
@Override
public Network[] getAllNetworks() {
enforceAccessPermission();
- final ArrayList<Network> result = new ArrayList();
synchronized (mNetworkForNetId) {
+ final Network[] result = new Network[mNetworkForNetId.size()];
for (int i = 0; i < mNetworkForNetId.size(); i++) {
- result.add(new Network(mNetworkForNetId.valueAt(i).network));
+ result[i] = mNetworkForNetId.valueAt(i).network;
}
+ return result;
}
- return result.toArray(new Network[result.size()]);
- }
-
- private NetworkCapabilities getNetworkCapabilitiesAndValidation(NetworkAgentInfo nai) {
- if (nai != null) {
- synchronized (nai) {
- if (nai.created) {
- NetworkCapabilities nc = new NetworkCapabilities(nai.networkCapabilities);
- if (nai.lastValidated) {
- nc.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
- } else {
- nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
- }
- return nc;
- }
- }
- }
- return null;
}
@Override
@@ -1015,7 +1103,7 @@
HashMap<Network, NetworkCapabilities> result = new HashMap<Network, NetworkCapabilities>();
NetworkAgentInfo nai = getDefaultNetwork();
- NetworkCapabilities nc = getNetworkCapabilitiesAndValidation(getDefaultNetwork());
+ NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai);
if (nc != null) {
result.put(nai.network, nc);
}
@@ -1028,9 +1116,9 @@
if (networks != null) {
for (Network network : networks) {
nai = getNetworkAgentInfoForNetwork(network);
- nc = getNetworkCapabilitiesAndValidation(nai);
+ nc = getNetworkCapabilitiesInternal(nai);
if (nc != null) {
- result.put(nai.network, nc);
+ result.put(network, nc);
}
}
}
@@ -1090,19 +1178,24 @@
return null;
}
- @Override
- public NetworkCapabilities getNetworkCapabilities(Network network) {
- enforceAccessPermission();
- NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) {
if (nai != null) {
synchronized (nai) {
- return new NetworkCapabilities(nai.networkCapabilities);
+ if (nai.networkCapabilities != null) {
+ return new NetworkCapabilities(nai.networkCapabilities);
+ }
}
}
return null;
}
@Override
+ public NetworkCapabilities getNetworkCapabilities(Network network) {
+ enforceAccessPermission();
+ return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network));
+ }
+
+ @Override
public NetworkState[] getAllNetworkState() {
// Require internal since we're handing out IMSI details
enforceConnectivityInternalPermission();
@@ -1310,6 +1403,22 @@
}
};
+ /**
+ * Require that the caller is either in the same user or has appropriate permission to interact
+ * across users.
+ *
+ * @param userId Target user for whatever operation the current IPC is supposed to perform.
+ */
+ private void enforceCrossUserPermission(int userId) {
+ if (userId == UserHandle.getCallingUserId()) {
+ // Not a cross-user call.
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+ "ConnectivityService");
+ }
+
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
@@ -1323,9 +1432,10 @@
}
private void enforceChangePermission() {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.CHANGE_NETWORK_STATE,
- "ConnectivityService");
+ int uid = Binder.getCallingUid();
+ Settings.checkAndNoteChangeNetworkStateOperation(mContext, uid, Settings
+ .getPackageNameForUid(mContext, uid), true);
+
}
private void enforceTetherAccessPermission() {
@@ -1342,7 +1452,6 @@
public void sendConnectedBroadcast(NetworkInfo info) {
enforceConnectivityInternalPermission();
- sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
}
@@ -1441,6 +1550,9 @@
mContext.registerReceiver(mUserPresentReceiver, filter);
}
+ // Configure whether mobile data is always on.
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON));
+
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SYSTEM_READY));
mPermissionMonitor.startMonitoring();
@@ -1457,14 +1569,6 @@
}
};
- /** @hide */
- @Override
- public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
- enforceConnectivityInternalPermission();
- if (DBG) log("captivePortalCheckCompleted: ni=" + info + " captive=" + isCaptivePortal);
-// mNetTrackers[info.getType()].captivePortalCheckCompleted(isCaptivePortal);
- }
-
/**
* Setup data activity tracking for the given network.
*
@@ -1481,13 +1585,13 @@
NetworkCapabilities.TRANSPORT_CELLULAR)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
- 5);
+ 10);
type = ConnectivityManager.TYPE_MOBILE;
} else if (networkAgent.networkCapabilities.hasTransport(
NetworkCapabilities.TRANSPORT_WIFI)) {
timeout = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
- 0);
+ 15);
type = ConnectivityManager.TYPE_WIFI;
} else {
// do not track any other networks
@@ -1554,6 +1658,13 @@
}
private static final String DEFAULT_TCP_BUFFER_SIZES = "4096,87380,110208,4096,16384,110208";
+ private static final String DEFAULT_TCP_RWND_KEY = "net.tcp.default_init_rwnd";
+
+ // Overridden for testing purposes to avoid writing to SystemProperties.
+ @VisibleForTesting
+ protected int getDefaultTcpRwnd() {
+ return SystemProperties.getInt(DEFAULT_TCP_RWND_KEY, 0);
+ }
private void updateTcpBufferSizes(NetworkAgentInfo nai) {
if (isDefaultNetwork(nai) == false) {
@@ -1589,10 +1700,8 @@
loge("Can't set TCP buffer sizes:" + e);
}
- final String defaultRwndKey = "net.tcp.default_init_rwnd";
- int defaultRwndValue = SystemProperties.getInt(defaultRwndKey, 0);
Integer rwndValue = Settings.Global.getInt(mContext.getContentResolver(),
- Settings.Global.TCP_DEFAULT_INIT_RWND, defaultRwndValue);
+ Settings.Global.TCP_DEFAULT_INIT_RWND, getDefaultTcpRwnd());
final String sysctlKey = "sys.sysctl.tcp_def_init_rwnd";
if (rwndValue != 0) {
SystemProperties.set(sysctlKey, rwndValue.toString());
@@ -1638,6 +1747,13 @@
return ret;
}
+ private boolean argsContain(String[] args, String target) {
+ for (String arg : args) {
+ if (arg.equals(target)) return true;
+ }
+ return false;
+ }
+
@Override
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -1650,15 +1766,34 @@
return;
}
- pw.println("NetworkFactories for:");
- pw.increaseIndent();
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- pw.println(nfi.name);
+ final List<NetworkDiagnostics> netDiags = new ArrayList<NetworkDiagnostics>();
+ if (argsContain(args, "--diag")) {
+ final long DIAG_TIME_MS = 5000;
+ for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
+ // Start gathering diagnostic information.
+ netDiags.add(new NetworkDiagnostics(
+ nai.network,
+ new LinkProperties(nai.linkProperties), // Must be a copy.
+ DIAG_TIME_MS));
+ }
+
+ for (NetworkDiagnostics netDiag : netDiags) {
+ pw.println();
+ netDiag.waitForMeasurements();
+ netDiag.dump(pw);
+ }
+
+ return;
}
- pw.decreaseIndent();
+
+ pw.print("NetworkFactories for:");
+ for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
+ pw.print(" " + nfi.name);
+ }
+ pw.println();
pw.println();
- NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ final NetworkAgentInfo defaultNai = getDefaultNetwork();
pw.print("Active default network: ");
if (defaultNai == null) {
pw.println("none");
@@ -1695,22 +1830,22 @@
pw.println();
pw.decreaseIndent();
- pw.println("mLegacyTypeTracker:");
- pw.increaseIndent();
mLegacyTypeTracker.dump(pw);
- pw.decreaseIndent();
- pw.println();
synchronized (this) {
- pw.println("NetworkTransitionWakeLock is currently " +
- (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held.");
- pw.println("It was last requested for "+mNetTransitionWakeLockCausedBy);
+ pw.print("mNetTransitionWakeLock: currently " +
+ (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held");
+ if (!TextUtils.isEmpty(mNetTransitionWakeLockCausedBy)) {
+ pw.println(", last requested for " + mNetTransitionWakeLockCausedBy);
+ } else {
+ pw.println(", last requested never");
+ }
}
pw.println();
mTethering.dump(fd, pw, args);
- if (mInetLog != null) {
+ if (mInetLog != null && mInetLog.size() > 0) {
pw.println();
pw.println("Inet condition reports:");
pw.increaseIndent();
@@ -1719,6 +1854,25 @@
}
pw.decreaseIndent();
}
+
+ if (argsContain(args, "--short") == false) {
+ pw.println();
+ synchronized (mValidationLogs) {
+ pw.println("mValidationLogs (most recent first):");
+ for (Pair<Network,ReadOnlyLocalLog> p : mValidationLogs) {
+ pw.println(p.first);
+ pw.increaseIndent();
+ p.second.dump(fd, pw, args);
+ pw.decreaseIndent();
+ }
+ }
+
+ pw.println();
+ pw.println("mNetworkRequestInfoLogs (most recent first):");
+ pw.increaseIndent();
+ mNetworkRequestInfoLogs.reverseDump(fd, pw, args);
+ pw.decreaseIndent();
+ }
}
private boolean isLiveNetworkAgent(NetworkAgentInfo nai, String msg) {
@@ -1764,7 +1918,13 @@
if (nai == null) {
loge("EVENT_NETWORK_CAPABILITIES_CHANGED from unknown NetworkAgent");
} else {
- updateCapabilities(nai, (NetworkCapabilities)msg.obj);
+ final NetworkCapabilities networkCapabilities =
+ (NetworkCapabilities)msg.obj;
+ if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL) ||
+ networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
+ Slog.wtf(TAG, "BUG: " + nai + " has stateful capability.");
+ }
+ updateCapabilities(nai, networkCapabilities);
}
break;
}
@@ -1843,22 +2003,22 @@
loge("ERROR: created network explicitly selected.");
}
nai.networkMisc.explicitlySelected = true;
+ nai.networkMisc.acceptUnvalidated = (boolean) msg.obj;
break;
}
case NetworkMonitor.EVENT_NETWORK_TESTED: {
NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
- if (isLiveNetworkAgent(nai, "EVENT_NETWORK_VALIDATED")) {
- boolean valid = (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
- nai.lastValidated = valid;
- if (valid) {
- if (DBG) log("Validated " + nai.name());
- if (!nai.everValidated) {
- nai.everValidated = true;
- rematchNetworkAndRequests(nai, NascentState.JUST_VALIDATED,
- ReapUnvalidatedNetworks.REAP);
- // If score has changed, rebroadcast to NetworkFactories. b/17726566
- sendUpdatedScoreToFactories(nai);
- }
+ if (isLiveNetworkAgent(nai, "EVENT_NETWORK_TESTED")) {
+ final boolean valid =
+ (msg.arg1 == NetworkMonitor.NETWORK_TEST_RESULT_VALID);
+ if (DBG) log(nai.name() + " validation " + (valid ? " passed" : "failed"));
+ if (valid != nai.lastValidated) {
+ final int oldScore = nai.getCurrentScore();
+ nai.lastValidated = valid;
+ nai.everValidated |= valid;
+ updateCapabilities(nai, nai.networkCapabilities);
+ // If score has changed, rebroadcast to NetworkFactories. b/17726566
+ if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
}
updateInetCondition(nai);
// Let the NetworkAgent know the state of its network
@@ -1877,19 +2037,28 @@
break;
}
case NetworkMonitor.EVENT_PROVISIONING_NOTIFICATION: {
- if (msg.arg1 == 0) {
- setProvNotificationVisibleIntent(false, msg.arg2, 0, null, null);
+ final int netId = msg.arg2;
+ final boolean visible = (msg.arg1 != 0);
+ final NetworkAgentInfo nai;
+ synchronized (mNetworkForNetId) {
+ nai = mNetworkForNetId.get(netId);
+ }
+ // If captive portal status has changed, update capabilities.
+ if (nai != null && (visible != nai.lastCaptivePortalDetected)) {
+ nai.lastCaptivePortalDetected = visible;
+ nai.everCaptivePortalDetected |= visible;
+ updateCapabilities(nai, nai.networkCapabilities);
+ }
+ if (!visible) {
+ setProvNotificationVisibleIntent(false, netId, null, 0, null, null, false);
} else {
- NetworkAgentInfo nai = null;
- synchronized (mNetworkForNetId) {
- nai = mNetworkForNetId.get(msg.arg2);
- }
if (nai == null) {
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
break;
}
- setProvNotificationVisibleIntent(true, msg.arg2, nai.networkInfo.getType(),
- nai.networkInfo.getExtraInfo(), (PendingIntent)msg.obj);
+ setProvNotificationVisibleIntent(true, netId, NotificationType.SIGN_IN,
+ nai.networkInfo.getType(), nai.networkInfo.getExtraInfo(),
+ (PendingIntent)msg.obj, nai.networkMisc.explicitlySelected);
}
break;
}
@@ -1897,17 +2066,22 @@
}
}
+ private void linger(NetworkAgentInfo nai) {
+ nai.lingering = true;
+ nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
+ }
+
// Cancel any lingering so the linger timeout doesn't teardown a network.
// This should be called when a network begins satisfying a NetworkRequest.
// Note: depending on what state the NetworkMonitor is in (e.g.,
// if it's awaiting captive portal login, or if validation failed), this
// may trigger a re-evaluation of the network.
private void unlinger(NetworkAgentInfo nai) {
- if (VDBG) log("Canceling linger of " + nai.name());
- // If network has never been validated, it cannot have been lingered, so don't bother
- // needlessly triggering a re-evaluation.
- if (!nai.everValidated) return;
nai.networkLingered.clear();
+ if (!nai.lingering) return;
+ nai.lingering = false;
+ if (VDBG) log("Canceling linger of " + nai.name());
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
}
@@ -1937,11 +2111,13 @@
loge("Error connecting NetworkAgent");
NetworkAgentInfo nai = mNetworkAgentInfos.remove(msg.replyTo);
if (nai != null) {
+ final boolean wasDefault = isDefaultNetwork(nai);
synchronized (mNetworkForNetId) {
mNetworkForNetId.remove(nai.network.netId);
+ mNetIdInUse.delete(nai.network.netId);
}
// Just in case.
- mLegacyTypeTracker.remove(nai);
+ mLegacyTypeTracker.remove(nai, wasDefault);
}
}
}
@@ -1954,15 +2130,6 @@
log(nai.name() + " got DISCONNECTED, was satisfying " + nai.networkRequests.size());
}
// A network agent has disconnected.
- if (nai.created) {
- // Tell netd to clean up the configuration for this network
- // (routing rules, DNS, etc).
- try {
- mNetd.removeNetwork(nai.network.netId);
- } catch (Exception e) {
- loge("Exception removing network: " + e);
- }
- }
// TODO - if we move the logic to the network agent (have them disconnect
// because they lost all their requests or because their score isn't good)
// then they would disconnect organically, report their new state and then
@@ -1971,45 +2138,31 @@
nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED,
null, null);
}
- if (isDefaultNetwork(nai)) {
+ final boolean wasDefault = isDefaultNetwork(nai);
+ if (wasDefault) {
mDefaultInetConditionPublished = 0;
}
notifyIfacesChanged();
+ // TODO - we shouldn't send CALLBACK_LOST to requests that can be satisfied
+ // by other networks that are already connected. Perhaps that can be done by
+ // sending all CALLBACK_LOST messages (for requests, not listens) at the end
+ // of rematchAllNetworksAndRequests
notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOST);
nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_DISCONNECTED);
mNetworkAgentInfos.remove(msg.replyTo);
updateClat(null, nai.linkProperties, nai);
- mLegacyTypeTracker.remove(nai);
synchronized (mNetworkForNetId) {
+ // Remove the NetworkAgent, but don't mark the netId as
+ // available until we've told netd to delete it below.
mNetworkForNetId.remove(nai.network.netId);
}
- // Since we've lost the network, go through all the requests that
- // it was satisfying and see if any other factory can satisfy them.
- // TODO: This logic may be better replaced with a call to rematchAllNetworksAndRequests
- final ArrayList<NetworkAgentInfo> toActivate = new ArrayList<NetworkAgentInfo>();
+ // Remove all previously satisfied requests.
for (int i = 0; i < nai.networkRequests.size(); i++) {
NetworkRequest request = nai.networkRequests.valueAt(i);
NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
- if (DBG) {
- log("Checking for replacement network to handle request " + request );
- }
mNetworkForRequestId.remove(request.requestId);
sendUpdatedScoreToFactories(request, 0);
- NetworkAgentInfo alternative = null;
- for (NetworkAgentInfo existing : mNetworkAgentInfos.values()) {
- if (existing.satisfies(request) &&
- (alternative == null ||
- alternative.getCurrentScore() < existing.getCurrentScore())) {
- alternative = existing;
- }
- }
- if (alternative != null) {
- if (DBG) log(" found replacement in " + alternative.name());
- if (!toActivate.contains(alternative)) {
- toActivate.add(alternative);
- }
- }
}
}
if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
@@ -2017,14 +2170,30 @@
notifyLockdownVpn(nai);
requestNetworkTransitionWakelock(nai.name());
}
- for (NetworkAgentInfo networkToActivate : toActivate) {
- unlinger(networkToActivate);
- rematchNetworkAndRequests(networkToActivate, NascentState.NOT_JUST_VALIDATED,
- ReapUnvalidatedNetworks.DONT_REAP);
+ mLegacyTypeTracker.remove(nai, wasDefault);
+ rematchAllNetworksAndRequests(null, 0);
+ if (nai.created) {
+ // Tell netd to clean up the configuration for this network
+ // (routing rules, DNS, etc).
+ // This may be slow as it requires a lot of netd shelling out to ip and
+ // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
+ // after we've rematched networks with requests which should make a potential
+ // fallback network the default or requested a new network from the
+ // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
+ // long time.
+ try {
+ mNetd.removeNetwork(nai.network.netId);
+ } catch (Exception e) {
+ loge("Exception removing network: " + e);
+ }
}
+ synchronized (mNetworkForNetId) {
+ mNetIdInUse.delete(nai.network.netId);
+ }
+ } else {
+ NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
+ if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
}
- NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
- if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
}
// If this method proves to be too slow then we can maintain a separate
@@ -2051,52 +2220,15 @@
+ nri.request + " because their intents matched.");
handleReleaseNetworkRequest(existingRequest.request, getCallingUid());
}
- handleRegisterNetworkRequest(msg);
+ handleRegisterNetworkRequest(nri);
}
- private void handleRegisterNetworkRequest(Message msg) {
- final NetworkRequestInfo nri = (NetworkRequestInfo) (msg.obj);
-
+ private void handleRegisterNetworkRequest(NetworkRequestInfo nri) {
mNetworkRequests.put(nri.request, nri);
-
- // TODO: This logic may be better replaced with a call to rematchNetworkAndRequests
-
- // Check for the best currently alive network that satisfies this request
- NetworkAgentInfo bestNetwork = null;
- for (NetworkAgentInfo network : mNetworkAgentInfos.values()) {
- if (DBG) log("handleRegisterNetworkRequest checking " + network.name());
- if (network.satisfies(nri.request)) {
- if (DBG) log("apparently satisfied. currentScore=" + network.getCurrentScore());
- if (!nri.isRequest) {
- // Not setting bestNetwork here as a listening NetworkRequest may be
- // satisfied by multiple Networks. Instead the request is added to
- // each satisfying Network and notified about each.
- network.addRequest(nri.request);
- notifyNetworkCallback(network, nri);
- } else if (bestNetwork == null ||
- bestNetwork.getCurrentScore() < network.getCurrentScore()) {
- bestNetwork = network;
- }
- }
- }
- if (bestNetwork != null) {
- if (DBG) log("using " + bestNetwork.name());
- unlinger(bestNetwork);
- bestNetwork.addRequest(nri.request);
- mNetworkForRequestId.put(nri.request.requestId, bestNetwork);
- notifyNetworkCallback(bestNetwork, nri);
- if (nri.request.legacyType != TYPE_NONE) {
- mLegacyTypeTracker.add(nri.request.legacyType, bestNetwork);
- }
- }
-
- if (nri.isRequest) {
- if (DBG) log("sending new NetworkRequest to factories");
- final int score = bestNetwork == null ? 0 : bestNetwork.getCurrentScore();
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
- 0, nri.request);
- }
+ mNetworkRequestInfoLogs.log("REGISTER " + nri);
+ rematchAllNetworksAndRequests(null, 0);
+ if (nri.isRequest && mNetworkForRequestId.get(nri.request.requestId) == null) {
+ sendUpdatedScoreToFactories(nri.request, 0);
}
}
@@ -2109,43 +2241,28 @@
}
// Is nai unneeded by all NetworkRequests (and should be disconnected)?
- // For validated Networks this is simply whether it is satsifying any NetworkRequests.
- // For unvalidated Networks this is whether it is satsifying any NetworkRequests or
- // were it to become validated, would it have a chance of satisfying any NetworkRequests.
+ // This is whether it is satisfying any NetworkRequests or were it to become validated,
+ // would it have a chance of satisfying any NetworkRequests.
private boolean unneeded(NetworkAgentInfo nai) {
- if (!nai.created || nai.isVPN()) return false;
- boolean unneeded = true;
- if (nai.everValidated) {
- for (int i = 0; i < nai.networkRequests.size() && unneeded; i++) {
- final NetworkRequest nr = nai.networkRequests.valueAt(i);
- try {
- if (isRequest(nr)) unneeded = false;
- } catch (Exception e) {
- loge("Request " + nr + " not found in mNetworkRequests.");
- loge(" it came from request list of " + nai.name());
- }
- }
- } else {
- for (NetworkRequestInfo nri : mNetworkRequests.values()) {
- // If this Network is already the highest scoring Network for a request, or if
- // there is hope for it to become one if it validated, then it is needed.
- if (nri.isRequest && nai.satisfies(nri.request) &&
- (nai.networkRequests.get(nri.request.requestId) != null ||
- // Note that this catches two important cases:
- // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
- // is currently satisfying the request. This is desirable when
- // cellular ends up validating but WiFi does not.
- // 2. Unvalidated WiFi will not be reaped when validated cellular
- // is currently satsifying the request. This is desirable when
- // WiFi ends up validating and out scoring cellular.
- mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
- nai.getCurrentScoreAsValidated())) {
- unneeded = false;
- break;
- }
+ if (!nai.created || nai.isVPN() || nai.lingering) return false;
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ // If this Network is already the highest scoring Network for a request, or if
+ // there is hope for it to become one if it validated, then it is needed.
+ if (nri.isRequest && nai.satisfies(nri.request) &&
+ (nai.networkRequests.get(nri.request.requestId) != null ||
+ // Note that this catches two important cases:
+ // 1. Unvalidated cellular will not be reaped when unvalidated WiFi
+ // is currently satisfying the request. This is desirable when
+ // cellular ends up validating but WiFi does not.
+ // 2. Unvalidated WiFi will not be reaped when validated cellular
+ // is currently satisfying the request. This is desirable when
+ // WiFi ends up validating and out scoring cellular.
+ mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
+ nai.getCurrentScoreAsValidated())) {
+ return false;
}
}
- return unneeded;
+ return true;
}
private void handleReleaseNetworkRequest(NetworkRequest request, int callingUid) {
@@ -2158,6 +2275,7 @@
if (DBG) log("releasing NetworkRequest " + request);
nri.unlinkDeathRecipient();
mNetworkRequests.remove(request);
+ mNetworkRequestInfoLogs.log("RELEASE " + nri);
if (nri.isRequest) {
// Find all networks that are satisfying this request and remove the request
// from their request lists.
@@ -2208,7 +2326,7 @@
}
if (doRemove) {
- mLegacyTypeTracker.remove(nri.request.legacyType, nai);
+ mLegacyTypeTracker.remove(nri.request.legacyType, nai, false);
}
}
@@ -2227,6 +2345,83 @@
}
}
+ public void setAcceptUnvalidated(Network network, boolean accept, boolean always) {
+ enforceConnectivityInternalPermission();
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_ACCEPT_UNVALIDATED,
+ accept ? 1 : 0, always ? 1: 0, network));
+ }
+
+ private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
+ if (DBG) log("handleSetAcceptUnvalidated network=" + network +
+ " accept=" + accept + " always=" + always);
+
+ NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) {
+ // Nothing to do.
+ return;
+ }
+
+ if (nai.everValidated) {
+ // The network validated while the dialog box was up. Take no action.
+ return;
+ }
+
+ if (!nai.networkMisc.explicitlySelected) {
+ Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network");
+ }
+
+ if (accept != nai.networkMisc.acceptUnvalidated) {
+ int oldScore = nai.getCurrentScore();
+ nai.networkMisc.acceptUnvalidated = accept;
+ rematchAllNetworksAndRequests(nai, oldScore);
+ sendUpdatedScoreToFactories(nai);
+ }
+
+ if (always) {
+ nai.asyncChannel.sendMessage(
+ NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED, accept ? 1 : 0);
+ }
+
+ if (!accept) {
+ // Tell the NetworkAgent to not automatically reconnect to the network.
+ nai.asyncChannel.sendMessage(NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT);
+ // Teardown the nework.
+ teardownUnneededNetwork(nai);
+ }
+
+ }
+
+ private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
+ if (DBG) log("scheduleUnvalidatedPrompt " + nai.network);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network),
+ PROMPT_UNVALIDATED_DELAY_MS);
+ }
+
+ private void handlePromptUnvalidated(Network network) {
+ if (DBG) log("handlePromptUnvalidated " + network);
+ NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+
+ // Only prompt if the network is unvalidated and was explicitly selected by the user, and if
+ // we haven't already been told to switch to it regardless of whether it validated or not.
+ // Also don't prompt on captive portals because we're already prompting the user to sign in.
+ if (nai == null || nai.everValidated || nai.everCaptivePortalDetected ||
+ !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
+ return;
+ }
+
+ Intent intent = new Intent(ConnectivityManager.ACTION_PROMPT_UNVALIDATED);
+ intent.setData(Uri.fromParts("netId", Integer.toString(network.netId), null));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setClassName("com.android.settings",
+ "com.android.settings.wifi.WifiNoInternetDialog");
+
+ PendingIntent pendingIntent = PendingIntent.getActivityAsUser(
+ mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
+ setProvNotificationVisibleIntent(true, nai.network.netId, NotificationType.NO_INTERNET,
+ nai.networkInfo.getType(), nai.networkInfo.getExtraInfo(), pendingIntent, true);
+ }
+
private class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
@@ -2282,10 +2477,11 @@
}
case EVENT_REGISTER_NETWORK_REQUEST:
case EVENT_REGISTER_NETWORK_LISTENER: {
- handleRegisterNetworkRequest(msg);
+ handleRegisterNetworkRequest((NetworkRequestInfo) msg.obj);
break;
}
- case EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT: {
+ case EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT:
+ case EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT: {
handleRegisterNetworkRequestWithIntent(msg);
break;
}
@@ -2297,6 +2493,18 @@
handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.arg1);
break;
}
+ case EVENT_SET_ACCEPT_UNVALIDATED: {
+ handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0);
+ break;
+ }
+ case EVENT_PROMPT_UNVALIDATED: {
+ handlePromptUnvalidated((Network) msg.obj);
+ break;
+ }
+ case EVENT_CONFIGURE_MOBILE_DATA_ALWAYS_ON: {
+ handleMobileDataAlwaysOn();
+ break;
+ }
case EVENT_SYSTEM_READY: {
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
nai.networkMonitor.systemReady = true;
@@ -2435,25 +2643,30 @@
public void reportInetCondition(int networkType, int percentage) {
NetworkAgentInfo nai = mLegacyTypeTracker.getNetworkForType(networkType);
if (nai == null) return;
- boolean isGood = percentage > 50;
- // Revalidate if the app report does not match our current validated state.
- if (isGood != nai.lastValidated) {
- // Make the message logged by reportBadNetwork below less confusing.
- if (DBG && isGood) log("reportInetCondition: type=" + networkType + " ok, revalidate");
- reportBadNetwork(nai.network);
- }
+ reportNetworkConnectivity(nai.network, percentage > 50);
}
- public void reportBadNetwork(Network network) {
+ public void reportNetworkConnectivity(Network network, boolean hasConnectivity) {
enforceAccessPermission();
enforceInternetPermission();
- if (network == null) return;
-
+ NetworkAgentInfo nai;
+ if (network == null) {
+ nai = getDefaultNetwork();
+ } else {
+ nai = getNetworkAgentInfoForNetwork(network);
+ }
+ if (nai == null || nai.networkInfo.getState() == NetworkInfo.State.DISCONNECTING ||
+ nai.networkInfo.getState() == NetworkInfo.State.DISCONNECTED) {
+ return;
+ }
+ // Revalidate if the app report does not match our current validated state.
+ if (hasConnectivity == nai.lastValidated) return;
final int uid = Binder.getCallingUid();
- NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
- if (nai == null) return;
- if (DBG) log("reportBadNetwork(" + nai.name() + ") by " + uid);
+ if (DBG) {
+ log("reportNetworkConnectivity(" + nai.network.netId + ", " + hasConnectivity +
+ ") by " + uid);
+ }
synchronized (nai) {
// Validating an uncreated network could result in a call to rematchNetworkAndRequests()
// which isn't meant to work on uncreated networks.
@@ -2465,7 +2678,7 @@
}
}
- public ProxyInfo getDefaultProxy() {
+ private ProxyInfo getDefaultProxy() {
// this information is already available as a world read/writable jvm property
// so this API change wouldn't have a benifit. It also breaks the passing
// of proxy info to all the JVMs.
@@ -2477,6 +2690,22 @@
}
}
+ public ProxyInfo getProxyForNetwork(Network network) {
+ if (network == null) return getDefaultProxy();
+ final ProxyInfo globalProxy = getGlobalProxy();
+ if (globalProxy != null) return globalProxy;
+ if (!NetworkUtils.queryUserAccess(Binder.getCallingUid(), network.netId)) return null;
+ // Don't call getLinkProperties() as it requires ACCESS_NETWORK_STATE permission, which
+ // caller may not have.
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) return null;
+ synchronized (nai) {
+ final ProxyInfo proxyInfo = nai.linkProperties.getHttpProxy();
+ if (proxyInfo == null) return null;
+ return new ProxyInfo(proxyInfo);
+ }
+ }
+
// Convert empty ProxyInfo's to null as null-checks are used to determine if proxies are present
// (e.g. if mGlobalProxy==null fall back to network-specific proxy, if network-specific
// proxy is null then there is no proxy in place).
@@ -2676,23 +2905,36 @@
}
private static class SettingsObserver extends ContentObserver {
- private int mWhat;
- private Handler mHandler;
- SettingsObserver(Handler handler, int what) {
- super(handler);
+ final private HashMap<Uri, Integer> mUriEventMap;
+ final private Context mContext;
+ final private Handler mHandler;
+
+ SettingsObserver(Context context, Handler handler) {
+ super(null);
+ mUriEventMap = new HashMap<Uri, Integer>();
+ mContext = context;
mHandler = handler;
- mWhat = what;
}
- void observe(Context context) {
- ContentResolver resolver = context.getContentResolver();
- resolver.registerContentObserver(Settings.Global.getUriFor(
- Settings.Global.HTTP_PROXY), false, this);
+ void observe(Uri uri, int what) {
+ mUriEventMap.put(uri, what);
+ final ContentResolver resolver = mContext.getContentResolver();
+ resolver.registerContentObserver(uri, false, this);
}
@Override
public void onChange(boolean selfChange) {
- mHandler.obtainMessage(mWhat).sendToTarget();
+ Slog.wtf(TAG, "Should never be reached.");
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ final Integer what = mUriEventMap.get(uri);
+ if (what != null) {
+ mHandler.obtainMessage(what.intValue()).sendToTarget();
+ } else {
+ loge("No matching event to send for URI=" + uri);
+ }
}
}
@@ -2713,29 +2955,56 @@
/**
* Prepare for a VPN application.
- * Permissions are checked in Vpn class.
+ * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
+ * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+ *
+ * @param oldPackage Package name of the application which currently controls VPN, which will
+ * be replaced. If there is no such application, this should should either be
+ * {@code null} or {@link VpnConfig.LEGACY_VPN}.
+ * @param newPackage Package name of the application which should gain control of VPN, or
+ * {@code null} to disable.
+ * @param userId User for whom to prepare the new VPN.
+ *
* @hide
*/
@Override
- public boolean prepareVpn(String oldPackage, String newPackage) {
+ public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
+ int userId) {
+ enforceCrossUserPermission(userId);
throwIfLockdownEnabled();
- int user = UserHandle.getUserId(Binder.getCallingUid());
+
synchronized(mVpns) {
- return mVpns.get(user).prepare(oldPackage, newPackage);
+ Vpn vpn = mVpns.get(userId);
+ if (vpn != null) {
+ return vpn.prepare(oldPackage, newPackage);
+ } else {
+ return false;
+ }
}
}
/**
- * Set whether the current VPN package has the ability to launch VPNs without
- * user intervention. This method is used by system-privileged apps.
- * Permissions are checked in Vpn class.
+ * Set whether the VPN package has the ability to launch VPNs without user intervention.
+ * This method is used by system-privileged apps.
+ * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
+ * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
+ *
+ * @param packageName The package for which authorization state should change.
+ * @param userId User for whom {@code packageName} is installed.
+ * @param authorized {@code true} if this app should be able to start a VPN connection without
+ * explicit user approval, {@code false} if not.
+ *
* @hide
*/
@Override
- public void setVpnPackageAuthorization(boolean authorized) {
- int user = UserHandle.getUserId(Binder.getCallingUid());
+ public void setVpnPackageAuthorization(String packageName, int userId, boolean authorized) {
+ enforceCrossUserPermission(userId);
+
synchronized(mVpns) {
- mVpns.get(user).setPackageAuthorization(authorized);
+ Vpn vpn = mVpns.get(userId);
+ if (vpn != null) {
+ vpn.setPackageAuthorization(packageName, authorized);
+ }
}
}
@@ -2776,28 +3045,85 @@
* Return the information of the ongoing legacy VPN. This method is used
* by VpnSettings and not available in ConnectivityManager. Permissions
* are checked in Vpn class.
- * @hide
*/
@Override
- public LegacyVpnInfo getLegacyVpnInfo() {
- throwIfLockdownEnabled();
- int user = UserHandle.getUserId(Binder.getCallingUid());
+ public LegacyVpnInfo getLegacyVpnInfo(int userId) {
+ enforceCrossUserPermission(userId);
+ if (mLockdownEnabled) {
+ return null;
+ }
+
synchronized(mVpns) {
- return mVpns.get(user).getLegacyVpnInfo();
+ return mVpns.get(userId).getLegacyVpnInfo();
}
}
/**
- * Returns the information of the ongoing VPN. This method is used by VpnDialogs and
- * not available in ConnectivityManager.
+ * Return the information of all ongoing VPNs. This method is used by NetworkStatsService
+ * and not available in ConnectivityManager.
+ */
+ @Override
+ public VpnInfo[] getAllVpnInfo() {
+ enforceConnectivityInternalPermission();
+ if (mLockdownEnabled) {
+ return new VpnInfo[0];
+ }
+
+ synchronized(mVpns) {
+ List<VpnInfo> infoList = new ArrayList<>();
+ for (int i = 0; i < mVpns.size(); i++) {
+ VpnInfo info = createVpnInfo(mVpns.valueAt(i));
+ if (info != null) {
+ infoList.add(info);
+ }
+ }
+ return infoList.toArray(new VpnInfo[infoList.size()]);
+ }
+ }
+
+ /**
+ * @return VPN information for accounting, or null if we can't retrieve all required
+ * information, e.g primary underlying iface.
+ */
+ @Nullable
+ private VpnInfo createVpnInfo(Vpn vpn) {
+ VpnInfo info = vpn.getVpnInfo();
+ if (info == null) {
+ return null;
+ }
+ Network[] underlyingNetworks = vpn.getUnderlyingNetworks();
+ // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret
+ // the underlyingNetworks list.
+ if (underlyingNetworks == null) {
+ NetworkAgentInfo defaultNetwork = getDefaultNetwork();
+ if (defaultNetwork != null && defaultNetwork.linkProperties != null) {
+ info.primaryUnderlyingIface = getDefaultNetwork().linkProperties.getInterfaceName();
+ }
+ } else if (underlyingNetworks.length > 0) {
+ LinkProperties linkProperties = getLinkProperties(underlyingNetworks[0]);
+ if (linkProperties != null) {
+ info.primaryUnderlyingIface = linkProperties.getInterfaceName();
+ }
+ }
+ return info.primaryUnderlyingIface == null ? null : info;
+ }
+
+ /**
+ * Returns the information of the ongoing VPN for {@code userId}. This method is used by
+ * VpnDialogs and not available in ConnectivityManager.
* Permissions are checked in Vpn class.
* @hide
*/
@Override
- public VpnConfig getVpnConfig() {
- int user = UserHandle.getUserId(Binder.getCallingUid());
+ public VpnConfig getVpnConfig(int userId) {
+ enforceCrossUserPermission(userId);
synchronized(mVpns) {
- return mVpns.get(user).getVpnConfig();
+ Vpn vpn = mVpns.get(userId);
+ if (vpn != null) {
+ return vpn.getVpnConfig();
+ } else {
+ return null;
+ }
}
}
@@ -2863,23 +3189,6 @@
}
}
- public int findConnectionTypeForIface(String iface) {
- enforceConnectivityInternalPermission();
-
- if (TextUtils.isEmpty(iface)) return ConnectivityManager.TYPE_NONE;
-
- synchronized(mNetworkForNetId) {
- for (int i = 0; i < mNetworkForNetId.size(); i++) {
- NetworkAgentInfo nai = mNetworkForNetId.valueAt(i);
- LinkProperties lp = nai.linkProperties;
- if (lp != null && iface.equals(lp.getInterfaceName()) && nai.networkInfo != null) {
- return nai.networkInfo.getType();
- }
- }
- }
- return ConnectivityManager.TYPE_NONE;
- }
-
@Override
public int checkMobileProvisioning(int suggestedTimeOutMs) {
// TODO: Remove? Any reason to trigger a provisioning check?
@@ -2887,7 +3196,7 @@
}
private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
- private volatile boolean mIsNotificationVisible = false;
+ private static enum NotificationType { SIGN_IN, NO_INTERNET; };
private void setProvNotificationVisible(boolean visible, int networkType, String action) {
if (DBG) {
@@ -2898,21 +3207,32 @@
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
// Concatenate the range of types onto the range of NetIDs.
int id = MAX_NET_ID + 1 + (networkType - ConnectivityManager.TYPE_NONE);
- setProvNotificationVisibleIntent(visible, id, networkType, null, pendingIntent);
+ setProvNotificationVisibleIntent(visible, id, NotificationType.SIGN_IN,
+ networkType, null, pendingIntent, false);
}
/**
- * Show or hide network provisioning notificaitons.
+ * Show or hide network provisioning notifications.
+ *
+ * We use notifications for two purposes: to notify that a network requires sign in
+ * (NotificationType.SIGN_IN), or to notify that a network does not have Internet access
+ * (NotificationType.NO_INTERNET). We display at most one notification per ID, so on a
+ * particular network we can display the notification type that was most recently requested.
+ * So for example if a captive portal fails to reply within a few seconds of connecting, we
+ * might first display NO_INTERNET, and then when the captive portal check completes, display
+ * SIGN_IN.
*
* @param id an identifier that uniquely identifies this notification. This must match
* between show and hide calls. We use the NetID value but for legacy callers
* we concatenate the range of types with the range of NetIDs.
*/
- private void setProvNotificationVisibleIntent(boolean visible, int id, int networkType,
- String extraInfo, PendingIntent intent) {
+ private void setProvNotificationVisibleIntent(boolean visible, int id,
+ NotificationType notifyType, int networkType, String extraInfo, PendingIntent intent,
+ boolean highPriority) {
if (DBG) {
- log("setProvNotificationVisibleIntent: E visible=" + visible + " networkType=" +
- networkType + " extraInfo=" + extraInfo);
+ log("setProvNotificationVisibleIntent " + notifyType + " visible=" + visible
+ + " networkType=" + getNetworkTypeName(networkType)
+ + " extraInfo=" + extraInfo + " highPriority=" + highPriority);
}
Resources r = Resources.getSystem();
@@ -2923,54 +3243,72 @@
CharSequence title;
CharSequence details;
int icon;
- Notification notification = new Notification();
- switch (networkType) {
- case ConnectivityManager.TYPE_WIFI:
- title = r.getString(R.string.wifi_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed,
- extraInfo);
- icon = R.drawable.stat_notify_wifi_in_range;
- break;
- case ConnectivityManager.TYPE_MOBILE:
- case ConnectivityManager.TYPE_MOBILE_HIPRI:
- title = r.getString(R.string.network_available_sign_in, 0);
- // TODO: Change this to pull from NetworkInfo once a printable
- // name has been added to it
- details = mTelephonyManager.getNetworkOperatorName();
- icon = R.drawable.stat_notify_rssi_in_range;
- break;
- default:
- title = r.getString(R.string.network_available_sign_in, 0);
- details = r.getString(R.string.network_available_sign_in_detailed,
- extraInfo);
- icon = R.drawable.stat_notify_rssi_in_range;
- break;
+ if (notifyType == NotificationType.NO_INTERNET &&
+ networkType == ConnectivityManager.TYPE_WIFI) {
+ title = r.getString(R.string.wifi_no_internet, 0);
+ details = r.getString(R.string.wifi_no_internet_detailed);
+ icon = R.drawable.stat_notify_wifi_in_range; // TODO: Need new icon.
+ } else if (notifyType == NotificationType.SIGN_IN) {
+ switch (networkType) {
+ case ConnectivityManager.TYPE_WIFI:
+ title = r.getString(R.string.wifi_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ extraInfo);
+ icon = R.drawable.stat_notify_wifi_in_range;
+ break;
+ case ConnectivityManager.TYPE_MOBILE:
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ title = r.getString(R.string.network_available_sign_in, 0);
+ // TODO: Change this to pull from NetworkInfo once a printable
+ // name has been added to it
+ details = mTelephonyManager.getNetworkOperatorName();
+ icon = R.drawable.stat_notify_rssi_in_range;
+ break;
+ default:
+ title = r.getString(R.string.network_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ extraInfo);
+ icon = R.drawable.stat_notify_rssi_in_range;
+ break;
+ }
+ } else {
+ Slog.wtf(TAG, "Unknown notification type " + notifyType + "on network type "
+ + getNetworkTypeName(networkType));
+ return;
}
- notification.when = 0;
- notification.icon = icon;
- notification.flags = Notification.FLAG_AUTO_CANCEL;
- notification.tickerText = title;
- notification.color = mContext.getResources().getColor(
- com.android.internal.R.color.system_notification_accent_color);
- notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
- notification.contentIntent = intent;
+ Notification notification = new Notification.Builder(mContext)
+ .setWhen(0)
+ .setSmallIcon(icon)
+ .setAutoCancel(true)
+ .setTicker(title)
+ .setColor(mContext.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(details)
+ .setContentIntent(intent)
+ .setLocalOnly(true)
+ .setPriority(highPriority ?
+ Notification.PRIORITY_HIGH :
+ Notification.PRIORITY_DEFAULT)
+ .setDefaults(Notification.DEFAULT_ALL)
+ .setOnlyAlertOnce(true)
+ .build();
try {
notificationManager.notify(NOTIFICATION_ID, id, notification);
} catch (NullPointerException npe) {
- loge("setNotificaitionVisible: visible notificationManager npe=" + npe);
+ loge("setNotificationVisible: visible notificationManager npe=" + npe);
npe.printStackTrace();
}
} else {
try {
notificationManager.cancel(NOTIFICATION_ID, id);
} catch (NullPointerException npe) {
- loge("setNotificaitionVisible: cancel notificationManager npe=" + npe);
+ loge("setNotificationVisible: cancel notificationManager npe=" + npe);
npe.printStackTrace();
}
}
- mIsNotificationVisible = visible;
}
/** Location to an updatable file listing carrier provisioning urls.
@@ -2979,7 +3317,6 @@
* <?xml version="1.0" encoding="utf-8"?>
* <provisioningUrls>
* <provisioningUrl mcc="310" mnc="4">http://myserver.com/foo?mdn=%3$s&iccid=%1$s&imei=%2$s</provisioningUrl>
- * <redirectedUrl mcc="310" mnc="4">http://www.google.com</redirectedUrl>
* </provisioningUrls>
*/
private static final String PROVISIONING_URL_PATH =
@@ -2990,33 +3327,15 @@
private static final String TAG_PROVISIONING_URLS = "provisioningUrls";
/** XML tag for individual url */
private static final String TAG_PROVISIONING_URL = "provisioningUrl";
- /** XML tag for redirected url */
- private static final String TAG_REDIRECTED_URL = "redirectedUrl";
/** XML attribute for mcc */
private static final String ATTR_MCC = "mcc";
/** XML attribute for mnc */
private static final String ATTR_MNC = "mnc";
- private static final int REDIRECTED_PROVISIONING = 1;
- private static final int PROVISIONING = 2;
-
- private String getProvisioningUrlBaseFromFile(int type) {
+ private String getProvisioningUrlBaseFromFile() {
FileReader fileReader = null;
XmlPullParser parser = null;
Configuration config = mContext.getResources().getConfiguration();
- String tagType;
-
- switch (type) {
- case PROVISIONING:
- tagType = TAG_PROVISIONING_URL;
- break;
- case REDIRECTED_PROVISIONING:
- tagType = TAG_REDIRECTED_URL;
- break;
- default:
- throw new RuntimeException("getProvisioningUrlBaseFromFile: Unexpected parameter " +
- type);
- }
try {
fileReader = new FileReader(mProvisioningUrlFile);
@@ -3030,7 +3349,7 @@
String element = parser.getName();
if (element == null) break;
- if (element.equals(tagType)) {
+ if (element.equals(TAG_PROVISIONING_URL)) {
String mcc = parser.getAttributeValue(null, ATTR_MCC);
try {
if (mcc != null && Integer.parseInt(mcc) == config.mcc) {
@@ -3065,19 +3384,9 @@
}
@Override
- public String getMobileRedirectedProvisioningUrl() {
- enforceConnectivityInternalPermission();
- String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING);
- if (TextUtils.isEmpty(url)) {
- url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url);
- }
- return url;
- }
-
- @Override
public String getMobileProvisioningUrl() {
enforceConnectivityInternalPermission();
- String url = getProvisioningUrlBaseFromFile(PROVISIONING);
+ String url = getProvisioningUrlBaseFromFile();
if (TextUtils.isEmpty(url)) {
url = mContext.getResources().getString(R.string.mobile_provisioning_url);
log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url);
@@ -3133,7 +3442,7 @@
loge("Starting user already has a VPN");
return;
}
- userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, this, userId);
+ userVpn = new Vpn(mHandler.getLooper(), mContext, mNetd, userId);
mVpns.put(userId, userVpn);
}
}
@@ -3238,18 +3547,31 @@
}
public String toString() {
- return (isRequest ? "Request" : "Listen") + " from uid/pid:" + mUid + "/" +
- mPid + " for " + request +
+ return (isRequest ? "Request" : "Listen") +
+ " from uid/pid:" + mUid + "/" + mPid +
+ " for " + request +
(mPendingIntent == null ? "" : " to trigger " + mPendingIntent);
}
}
+ private void ensureImmutableCapabilities(NetworkCapabilities networkCapabilities) {
+ if (networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
+ throw new IllegalArgumentException(
+ "Cannot request network with NET_CAPABILITY_VALIDATED");
+ }
+ if (networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) {
+ throw new IllegalArgumentException(
+ "Cannot request network with NET_CAPABILITY_CAPTIVE_PORTAL");
+ }
+ }
+
@Override
public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities);
enforceMeteredApnPolicy(networkCapabilities);
+ ensureImmutableCapabilities(networkCapabilities);
if (timeoutMs < 0 || timeoutMs > ConnectivityManager.MAX_NETWORK_REQUEST_TIMEOUT_MS) {
throw new IllegalArgumentException("Bad timeout specified");
@@ -3257,9 +3579,9 @@
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId());
- if (DBG) log("requestNetwork for " + networkRequest);
NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
NetworkRequestInfo.REQUEST);
+ if (DBG) log("requestNetwork for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST, nri));
if (timeoutMs > 0) {
@@ -3270,27 +3592,43 @@
}
private void enforceNetworkRequestPermissions(NetworkCapabilities networkCapabilities) {
- if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
- == false) {
+ if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) == false) {
enforceConnectivityInternalPermission();
} else {
enforceChangePermission();
}
}
+ @Override
+ public boolean requestBandwidthUpdate(Network network) {
+ enforceAccessPermission();
+ NetworkAgentInfo nai = null;
+ if (network == null) {
+ return false;
+ }
+ synchronized (mNetworkForNetId) {
+ nai = mNetworkForNetId.get(network.netId);
+ }
+ if (nai != null) {
+ nai.asyncChannel.sendMessage(android.net.NetworkAgent.CMD_REQUEST_BANDWIDTH_UPDATE);
+ return true;
+ }
+ return false;
+ }
+
+
private void enforceMeteredApnPolicy(NetworkCapabilities networkCapabilities) {
// if UID is restricted, don't allow them to bring up metered APNs
- if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
- == false) {
+ if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED) == false) {
final int uidRules;
final int uid = Binder.getCallingUid();
synchronized(mRulesLock) {
uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
}
- if ((uidRules & RULE_REJECT_METERED) != 0) {
+ if ((uidRules & (RULE_REJECT_METERED | RULE_REJECT_ALL)) != 0) {
// we could silently fail or we can filter the available nets to only give
// them those they have access to. Chose the more useful
- networkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
+ networkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED);
}
}
}
@@ -3302,12 +3640,13 @@
networkCapabilities = new NetworkCapabilities(networkCapabilities);
enforceNetworkRequestPermissions(networkCapabilities);
enforceMeteredApnPolicy(networkCapabilities);
+ ensureImmutableCapabilities(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, TYPE_NONE,
nextNetworkRequestId());
- if (DBG) log("pendingRequest for " + networkRequest + " to trigger " + operation);
NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
NetworkRequestInfo.REQUEST);
+ if (DBG) log("pendingRequest for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_REQUEST_WITH_INTENT,
nri));
return networkRequest;
@@ -3321,20 +3660,45 @@
@Override
public void releasePendingNetworkRequest(PendingIntent operation) {
+ checkNotNull(operation, "PendingIntent cannot be null.");
mHandler.sendMessage(mHandler.obtainMessage(EVENT_RELEASE_NETWORK_REQUEST_WITH_INTENT,
getCallingUid(), 0, operation));
}
+ // In order to implement the compatibility measure for pre-M apps that call
+ // WifiManager.enableNetwork(..., true) without also binding to that network explicitly,
+ // WifiManager registers a network listen for the purpose of calling setProcessDefaultNetwork.
+ // This ensures it has permission to do so.
+ private boolean hasWifiNetworkListenPermission(NetworkCapabilities nc) {
+ if (nc == null) {
+ return false;
+ }
+ int[] transportTypes = nc.getTransportTypes();
+ if (transportTypes.length != 1 || transportTypes[0] != NetworkCapabilities.TRANSPORT_WIFI) {
+ return false;
+ }
+ try {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_WIFI_STATE,
+ "ConnectivityService");
+ } catch (SecurityException e) {
+ return false;
+ }
+ return true;
+ }
+
@Override
public NetworkRequest listenForNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, IBinder binder) {
- enforceAccessPermission();
+ if (!hasWifiNetworkListenPermission(networkCapabilities)) {
+ enforceAccessPermission();
+ }
- NetworkRequest networkRequest = new NetworkRequest(new NetworkCapabilities(
- networkCapabilities), TYPE_NONE, nextNetworkRequestId());
- if (DBG) log("listenForNetwork for " + networkRequest);
+ NetworkRequest networkRequest = new NetworkRequest(
+ new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId());
NetworkRequestInfo nri = new NetworkRequestInfo(messenger, networkRequest, binder,
NetworkRequestInfo.LISTEN);
+ if (DBG) log("listenForNetwork for " + nri);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
return networkRequest;
@@ -3343,6 +3707,18 @@
@Override
public void pendingListenForNetwork(NetworkCapabilities networkCapabilities,
PendingIntent operation) {
+ checkNotNull(operation, "PendingIntent cannot be null.");
+ if (!hasWifiNetworkListenPermission(networkCapabilities)) {
+ enforceAccessPermission();
+ }
+
+ NetworkRequest networkRequest = new NetworkRequest(
+ new NetworkCapabilities(networkCapabilities), TYPE_NONE, nextNetworkRequestId());
+ NetworkRequestInfo nri = new NetworkRequestInfo(networkRequest, operation,
+ NetworkRequestInfo.LISTEN);
+ if (DBG) log("pendingListenForNetwork for " + nri);
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
}
@Override
@@ -3385,20 +3761,33 @@
* and the are the highest scored network available.
* the are keyed off the Requests requestId.
*/
+ // TODO: Yikes, this is accessed on multiple threads: add synchronization.
private final SparseArray<NetworkAgentInfo> mNetworkForRequestId =
new SparseArray<NetworkAgentInfo>();
+ // NOTE: Accessed on multiple threads, must be synchronized on itself.
+ @GuardedBy("mNetworkForNetId")
private final SparseArray<NetworkAgentInfo> mNetworkForNetId =
new SparseArray<NetworkAgentInfo>();
+ // NOTE: Accessed on multiple threads, synchronized with mNetworkForNetId.
+ // An entry is first added to mNetIdInUse, prior to mNetworkForNetId, so
+ // there may not be a strict 1:1 correlation between the two.
+ @GuardedBy("mNetworkForNetId")
+ private final SparseBooleanArray mNetIdInUse = new SparseBooleanArray();
// NetworkAgentInfo keyed off its connecting messenger
// TODO - eval if we can reduce the number of lists/hashmaps/sparsearrays
+ // NOTE: Only should be accessed on ConnectivityServiceThread, except dump().
private final HashMap<Messenger, NetworkAgentInfo> mNetworkAgentInfos =
new HashMap<Messenger, NetworkAgentInfo>();
// Note: if mDefaultRequest is changed, NetworkMonitor needs to be updated.
private final NetworkRequest mDefaultRequest;
+ // Request used to optionally keep mobile data active even when higher
+ // priority networks like Wi-Fi are active.
+ private final NetworkRequest mDefaultMobileDataRequest;
+
private NetworkAgentInfo getDefaultNetwork() {
return mNetworkForRequestId.get(mDefaultRequest.requestId);
}
@@ -3407,28 +3796,32 @@
return nai == getDefaultNetwork();
}
- public void registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+ public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
int currentScore, NetworkMisc networkMisc) {
enforceConnectivityInternalPermission();
// TODO: Instead of passing mDefaultRequest, provide an API to determine whether a Network
// satisfies mDefaultRequest.
- NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
- new NetworkInfo(networkInfo), new LinkProperties(linkProperties),
- new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler,
- new NetworkMisc(networkMisc), mDefaultRequest);
+ final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
+ new Network(reserveNetId()), new NetworkInfo(networkInfo), new LinkProperties(
+ linkProperties), new NetworkCapabilities(networkCapabilities), currentScore,
+ mContext, mTrackerHandler, new NetworkMisc(networkMisc), mDefaultRequest, this);
synchronized (this) {
nai.networkMonitor.systemReady = mSystemReady;
}
+ addValidationLogs(nai.networkMonitor.getValidationLogs(), nai.network);
if (DBG) log("registerNetworkAgent " + nai);
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_AGENT, nai));
+ return nai.network.netId;
}
private void handleRegisterNetworkAgent(NetworkAgentInfo na) {
if (VDBG) log("Got NetworkAgent Messenger");
mNetworkAgentInfos.put(na.messenger, na);
- assignNextNetId(na);
+ synchronized (mNetworkForNetId) {
+ mNetworkForNetId.put(na.network.netId, na);
+ }
na.asyncChannel.connect(mContext, mTrackerHandler, na.messenger);
NetworkInfo networkInfo = na.networkInfo;
na.networkInfo = null;
@@ -3453,10 +3846,12 @@
// }
updateTcpBufferSizes(networkAgent);
- // TODO: deprecate and remove mDefaultDns when we can do so safely.
- // For now, use it only when the network has Internet access. http://b/18327075
- final boolean useDefaultDns = networkAgent.networkCapabilities.hasCapability(
- NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ // TODO: deprecate and remove mDefaultDns when we can do so safely. See http://b/18327075
+ // In L, we used it only when the network had Internet access but provided no DNS servers.
+ // For now, just disable it, and if disabling it doesn't break things, remove it.
+ // final boolean useDefaultDns = networkAgent.networkCapabilities.hasCapability(
+ // NET_CAPABILITY_INTERNET);
+ final boolean useDefaultDns = false;
final boolean flushDns = updateRoutes(newLp, oldLp, netId);
updateDnses(newLp, oldLp, netId, flushDns, useDefaultDns);
@@ -3558,6 +3953,7 @@
}
return !routeDiff.added.isEmpty() || !routeDiff.removed.isEmpty();
}
+
private void updateDnses(LinkProperties newLp, LinkProperties oldLp, int netId,
boolean flush, boolean useDefaultDns) {
if (oldLp == null || (newLp.isIdenticalDnses(oldLp) == false)) {
@@ -3576,7 +3972,7 @@
} catch (Exception e) {
loge("Exception in setDnsServersForNetwork: " + e);
}
- NetworkAgentInfo defaultNai = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ final NetworkAgentInfo defaultNai = getDefaultNetwork();
if (defaultNai != null && defaultNai.network.netId == netId) {
setDefaultDnsSystemProperties(dnses);
}
@@ -3606,24 +4002,44 @@
mNumDnsEntries = last;
}
- private void updateCapabilities(NetworkAgentInfo networkAgent,
- NetworkCapabilities networkCapabilities) {
- if (!Objects.equals(networkAgent.networkCapabilities, networkCapabilities)) {
- if (networkAgent.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) !=
+ /**
+ * Update the NetworkCapabilities for {@code networkAgent} to {@code networkCapabilities}
+ * augmented with any stateful capabilities implied from {@code networkAgent}
+ * (e.g., validated status and captive portal status).
+ *
+ * @param nai the network having its capabilities updated.
+ * @param networkCapabilities the new network capabilities.
+ */
+ private void updateCapabilities(NetworkAgentInfo nai, NetworkCapabilities networkCapabilities) {
+ // Don't modify caller's NetworkCapabilities.
+ networkCapabilities = new NetworkCapabilities(networkCapabilities);
+ if (nai.lastValidated) {
+ networkCapabilities.addCapability(NET_CAPABILITY_VALIDATED);
+ } else {
+ networkCapabilities.removeCapability(NET_CAPABILITY_VALIDATED);
+ }
+ if (nai.lastCaptivePortalDetected) {
+ networkCapabilities.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+ } else {
+ networkCapabilities.removeCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+ }
+ if (!Objects.equals(nai.networkCapabilities, networkCapabilities)) {
+ final int oldScore = nai.getCurrentScore();
+ if (nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) !=
networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) {
try {
- mNetd.setNetworkPermission(networkAgent.network.netId,
+ mNetd.setNetworkPermission(nai.network.netId,
networkCapabilities.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) ?
null : NetworkManagementService.PERMISSION_SYSTEM);
} catch (RemoteException e) {
loge("Exception in setNetworkPermission: " + e);
}
}
- synchronized (networkAgent) {
- networkAgent.networkCapabilities = networkCapabilities;
+ synchronized (nai) {
+ nai.networkCapabilities = networkCapabilities;
}
- rematchAllNetworksAndRequests(networkAgent, networkAgent.getCurrentScore());
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_CAP_CHANGED);
+ rematchAllNetworksAndRequests(nai, oldScore);
+ notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED);
}
}
@@ -3765,7 +4181,7 @@
// one or more NetworkRequests, or if it is a VPN.
//
// - Tears down newNetwork if it just became validated
- // (i.e. nascent==JUST_VALIDATED) but turns out to be unneeded.
+ // but turns out to be unneeded.
//
// - If reapUnvalidatedNetworks==REAP, tears down unvalidated
// networks that have no chance (i.e. even if validated)
@@ -3778,17 +4194,12 @@
// as it performs better by a factor of the number of Networks.
//
// @param newNetwork is the network to be matched against NetworkRequests.
- // @param nascent indicates if newNetwork just became validated, in which case it should be
- // torn down if unneeded.
// @param reapUnvalidatedNetworks indicates if an additional pass over all networks should be
// performed to tear down unvalidated networks that have no chance (i.e. even if
// validated) of becoming the highest scoring network.
- private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork, NascentState nascent,
+ private void rematchNetworkAndRequests(NetworkAgentInfo newNetwork,
ReapUnvalidatedNetworks reapUnvalidatedNetworks) {
if (!newNetwork.created) return;
- if (nascent == NascentState.JUST_VALIDATED && !newNetwork.everValidated) {
- loge("ERROR: nascent network not validated.");
- }
boolean keep = newNetwork.isVPN();
boolean isNewDefault = false;
NetworkAgentInfo oldDefaultNetwork = null;
@@ -3796,11 +4207,13 @@
// Find and migrate to this Network any NetworkRequests for
// which this network is now the best.
ArrayList<NetworkAgentInfo> affectedNetworks = new ArrayList<NetworkAgentInfo>();
+ ArrayList<NetworkRequestInfo> addedRequests = new ArrayList<NetworkRequestInfo>();
if (VDBG) log(" network has: " + newNetwork.networkCapabilities);
for (NetworkRequestInfo nri : mNetworkRequests.values()) {
- NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
- if (newNetwork == currentNetwork) {
- if (DBG) {
+ final NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+ final boolean satisfies = newNetwork.satisfies(nri.request);
+ if (newNetwork == currentNetwork && satisfies) {
+ if (VDBG) {
log("Network " + newNetwork.name() + " was already satisfying" +
" request " + nri.request.requestId + ". No change.");
}
@@ -3810,11 +4223,11 @@
// check if it satisfies the NetworkCapabilities
if (VDBG) log(" checking if request is satisfied: " + nri.request);
- if (newNetwork.satisfies(nri.request)) {
+ if (satisfies) {
if (!nri.isRequest) {
// This is not a request, it's a callback listener.
// Add it to newNetwork regardless of score.
- newNetwork.addRequest(nri.request);
+ if (newNetwork.addRequest(nri.request)) addedRequests.add(nri);
continue;
}
@@ -3837,7 +4250,10 @@
}
unlinger(newNetwork);
mNetworkForRequestId.put(nri.request.requestId, newNetwork);
- newNetwork.addRequest(nri.request);
+ if (!newNetwork.addRequest(nri.request)) {
+ Slog.wtf(TAG, "BUG: " + newNetwork.name() + " already has " + nri.request);
+ }
+ addedRequests.add(nri);
keep = true;
// Tell NetworkFactories about the new score, so they can stop
// trying to connect if they know they cannot match it.
@@ -3850,52 +4266,90 @@
oldDefaultNetwork = currentNetwork;
}
}
+ } else if (newNetwork.networkRequests.get(nri.request.requestId) != null) {
+ // If "newNetwork" is listed as satisfying "nri" but no longer satisfies "nri",
+ // mark it as no longer satisfying "nri". Because networks are processed by
+ // rematchAllNetworkAndRequests() in descending score order, "currentNetwork" will
+ // match "newNetwork" before this loop will encounter a "currentNetwork" with higher
+ // score than "newNetwork" and where "currentNetwork" no longer satisfies "nri".
+ // This means this code doesn't have to handle the case where "currentNetwork" no
+ // longer satisfies "nri" when "currentNetwork" does not equal "newNetwork".
+ if (DBG) {
+ log("Network " + newNetwork.name() + " stopped satisfying" +
+ " request " + nri.request.requestId);
+ }
+ newNetwork.networkRequests.remove(nri.request.requestId);
+ if (currentNetwork == newNetwork) {
+ mNetworkForRequestId.remove(nri.request.requestId);
+ sendUpdatedScoreToFactories(nri.request, 0);
+ } else {
+ if (nri.isRequest == true) {
+ Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
+ newNetwork.name() +
+ " without updating mNetworkForRequestId or factories!");
+ }
+ }
+ // TODO: technically, sending CALLBACK_LOST here is
+ // incorrect if nri is a request (not a listen) and there
+ // is a replacement network currently connected that can
+ // satisfy it. However, the only capability that can both
+ // a) be requested and b) change is NET_CAPABILITY_TRUSTED,
+ // so this code is only incorrect for a network that loses
+ // the TRUSTED capability, which is a rare case.
+ callCallbackForRequest(nri, newNetwork, ConnectivityManager.CALLBACK_LOST);
}
}
// Linger any networks that are no longer needed.
for (NetworkAgentInfo nai : affectedNetworks) {
- if (nai.everValidated && unneeded(nai)) {
- nai.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_LINGER);
- notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING);
+ if (nai.lingering) {
+ // Already lingered. Nothing to do. This can only happen if "nai" is in
+ // "affectedNetworks" twice. The reasoning being that to get added to
+ // "affectedNetworks", "nai" must have been satisfying a NetworkRequest
+ // (i.e. not lingered) so it could have only been lingered by this loop.
+ // unneeded(nai) will be false and we'll call unlinger() below which would
+ // be bad, so handle it here.
+ } else if (unneeded(nai)) {
+ linger(nai);
} else {
+ // Clear nai.networkLingered we might have added above.
unlinger(nai);
}
}
+ if (isNewDefault) {
+ // Notify system services that this network is up.
+ makeDefault(newNetwork);
+ synchronized (ConnectivityService.this) {
+ // have a new default network, release the transition wakelock in
+ // a second if it's held. The second pause is to allow apps
+ // to reconnect over the new network
+ if (mNetTransitionWakeLock.isHeld()) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
+ mNetTransitionWakeLockSerialNumber, 0),
+ 1000);
+ }
+ }
+ }
+
+ // do this after the default net is switched, but
+ // before LegacyTypeTracker sends legacy broadcasts
+ for (NetworkRequestInfo nri : addedRequests) notifyNetworkCallback(newNetwork, nri);
+
+ if (isNewDefault) {
+ // Maintain the illusion: since the legacy API only
+ // understands one network at a time, we must pretend
+ // that the current default network disconnected before
+ // the new one connected.
+ if (oldDefaultNetwork != null) {
+ mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
+ oldDefaultNetwork, true);
+ }
+ mDefaultInetConditionPublished = newNetwork.lastValidated ? 100 : 0;
+ mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
+ notifyLockdownVpn(newNetwork);
+ }
+
if (keep) {
- if (isNewDefault) {
- // Notify system services that this network is up.
- makeDefault(newNetwork);
- synchronized (ConnectivityService.this) {
- // have a new default network, release the transition wakelock in
- // a second if it's held. The second pause is to allow apps
- // to reconnect over the new network
- if (mNetTransitionWakeLock.isHeld()) {
- mHandler.sendMessageDelayed(mHandler.obtainMessage(
- EVENT_CLEAR_NET_TRANSITION_WAKELOCK,
- mNetTransitionWakeLockSerialNumber, 0),
- 1000);
- }
- }
- }
-
- // do this after the default net is switched, but
- // before LegacyTypeTracker sends legacy broadcasts
- notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_AVAILABLE);
-
- if (isNewDefault) {
- // Maintain the illusion: since the legacy API only
- // understands one network at a time, we must pretend
- // that the current default network disconnected before
- // the new one connected.
- if (oldDefaultNetwork != null) {
- mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(),
- oldDefaultNetwork);
- }
- mDefaultInetConditionPublished = newNetwork.everValidated ? 100 : 0;
- mLegacyTypeTracker.add(newNetwork.networkInfo.getType(), newNetwork);
- notifyLockdownVpn(newNetwork);
- }
-
// Notify battery stats service about this network, both the normal
// interface and any stacked links.
// TODO: Avoid redoing this; this must only be done once when a network comes online.
@@ -3942,19 +4396,10 @@
if (newNetwork.isVPN()) {
mLegacyTypeTracker.add(TYPE_VPN, newNetwork);
}
- } else if (nascent == NascentState.JUST_VALIDATED) {
- // Only tear down newly validated networks here. Leave unvalidated to either become
- // validated (and get evaluated against peers, one losing here), or get reaped (see
- // reapUnvalidatedNetworks) if they have no chance of becoming the highest scoring
- // network. Networks that have been up for a while and are validated should be torn
- // down via the lingering process so communication on that network is given time to
- // wrap up.
- if (DBG) log("Validated network turns out to be unwanted. Tear it down.");
- teardownUnneededNetwork(newNetwork);
}
if (reapUnvalidatedNetworks == ReapUnvalidatedNetworks.REAP) {
for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
- if (!nai.everValidated && unneeded(nai)) {
+ if (unneeded(nai)) {
if (DBG) log("Reaping " + nai.name());
teardownUnneededNetwork(nai);
}
@@ -3962,14 +4407,18 @@
}
}
- // Attempt to rematch all Networks with NetworkRequests. This may result in Networks
- // being disconnected.
- // If only one Network's score or capabilities have been modified since the last time
- // this function was called, pass this Network in via the "changed" arugment, otherwise
- // pass null.
- // If only one Network has been changed but its NetworkCapabilities have not changed,
- // pass in the Network's score (from getCurrentScore()) prior to the change via
- // "oldScore", otherwise pass changed.getCurrentScore() or 0 if "changed" is null.
+ /**
+ * Attempt to rematch all Networks with NetworkRequests. This may result in Networks
+ * being disconnected.
+ * @param changed If only one Network's score or capabilities have been modified since the last
+ * time this function was called, pass this Network in this argument, otherwise pass
+ * null.
+ * @param oldScore If only one Network has been changed but its NetworkCapabilities have not
+ * changed, pass in the Network's score (from getCurrentScore()) prior to the change via
+ * this argument, otherwise pass {@code changed.getCurrentScore()} or 0 if
+ * {@code changed} is {@code null}. This is because NetworkCapabilities influence a
+ * network's score.
+ */
private void rematchAllNetworksAndRequests(NetworkAgentInfo changed, int oldScore) {
// TODO: This may get slow. The "changed" parameter is provided for future optimization
// to avoid the slowness. It is not simply enough to process just "changed", for
@@ -3980,16 +4429,20 @@
// can only add more NetworkRequests satisfied by "changed", and this is exactly what
// rematchNetworkAndRequests() handles.
if (changed != null && oldScore < changed.getCurrentScore()) {
- rematchNetworkAndRequests(changed, NascentState.NOT_JUST_VALIDATED,
- ReapUnvalidatedNetworks.REAP);
+ rematchNetworkAndRequests(changed, ReapUnvalidatedNetworks.REAP);
} else {
- for (Iterator i = mNetworkAgentInfos.values().iterator(); i.hasNext(); ) {
- rematchNetworkAndRequests((NetworkAgentInfo)i.next(),
- NascentState.NOT_JUST_VALIDATED,
+ final NetworkAgentInfo[] nais = mNetworkAgentInfos.values().toArray(
+ new NetworkAgentInfo[mNetworkAgentInfos.size()]);
+ // Rematch higher scoring networks first to prevent requests first matching a lower
+ // scoring network and then a higher scoring network, which could produce multiple
+ // callbacks and inadvertently unlinger networks.
+ Arrays.sort(nais);
+ for (NetworkAgentInfo nai : nais) {
+ rematchNetworkAndRequests(nai,
// Only reap the last time through the loop. Reaping before all rematching
// is complete could incorrectly teardown a network that hasn't yet been
// rematched.
- i.hasNext() ? ReapUnvalidatedNetworks.DONT_REAP
+ (nai != nais[nais.length-1]) ? ReapUnvalidatedNetworks.DONT_REAP
: ReapUnvalidatedNetworks.REAP);
}
}
@@ -4023,6 +4476,7 @@
private void updateNetworkInfo(NetworkAgentInfo networkAgent, NetworkInfo newInfo) {
NetworkInfo.State state = newInfo.getState();
NetworkInfo oldInfo = null;
+ final int oldScore = networkAgent.getCurrentScore();
synchronized (networkAgent) {
oldInfo = networkAgent.networkInfo;
networkAgent.networkInfo = newInfo;
@@ -4061,8 +4515,10 @@
networkAgent.created = true;
updateLinkProperties(networkAgent, null);
notifyIfacesChanged();
- notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
+
networkAgent.networkMonitor.sendMessage(NetworkMonitor.CMD_NETWORK_CONNECTED);
+ scheduleUnvalidatedPrompt(networkAgent);
+
if (networkAgent.isVPN()) {
// Temporarily disable the default proxy (not global).
synchronized (mProxyLock) {
@@ -4075,11 +4531,13 @@
}
// TODO: support proxy per network.
}
+
// Consider network even though it is not yet validated.
- rematchNetworkAndRequests(networkAgent, NascentState.NOT_JUST_VALIDATED,
- ReapUnvalidatedNetworks.REAP);
- } else if (state == NetworkInfo.State.DISCONNECTED ||
- state == NetworkInfo.State.SUSPENDED) {
+ rematchNetworkAndRequests(networkAgent, ReapUnvalidatedNetworks.REAP);
+
+ // This has to happen after matching the requests, because callbacks are just requests.
+ notifyNetworkCallbacks(networkAgent, ConnectivityManager.CALLBACK_PRECHECK);
+ } else if (state == NetworkInfo.State.DISCONNECTED) {
networkAgent.asyncChannel.disconnect();
if (networkAgent.isVPN()) {
synchronized (mProxyLock) {
@@ -4091,6 +4549,16 @@
}
}
}
+ } else if ((oldInfo != null && oldInfo.getState() == NetworkInfo.State.SUSPENDED) ||
+ state == NetworkInfo.State.SUSPENDED) {
+ // going into or coming out of SUSPEND: rescore and notify
+ if (networkAgent.getCurrentScore() != oldScore) {
+ rematchAllNetworksAndRequests(networkAgent, oldScore);
+ }
+ notifyNetworkCallbacks(networkAgent, (state == NetworkInfo.State.SUSPENDED ?
+ ConnectivityManager.CALLBACK_SUSPENDED :
+ ConnectivityManager.CALLBACK_RESUMED));
+ mLegacyTypeTracker.update(networkAgent);
}
}
@@ -4126,7 +4594,7 @@
}
}
- private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, boolean connected, int type) {
+ private void sendLegacyNetworkBroadcast(NetworkAgentInfo nai, DetailedState state, int type) {
// The NetworkInfo we actually send out has no bearing on the real
// state of affairs. For example, if the default connection is mobile,
// and a request for HIPRI has just gone away, we need to pretend that
@@ -4135,11 +4603,11 @@
// and is still connected.
NetworkInfo info = new NetworkInfo(nai.networkInfo);
info.setType(type);
- if (connected) {
- info.setDetailedState(DetailedState.CONNECTED, null, info.getExtraInfo());
+ if (state != DetailedState.DISCONNECTED) {
+ info.setDetailedState(state, null, info.getExtraInfo());
sendConnectedBroadcast(info);
} else {
- info.setDetailedState(DetailedState.DISCONNECTED, null, info.getExtraInfo());
+ info.setDetailedState(state, info.getReason(), info.getExtraInfo());
Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info);
intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
@@ -4155,7 +4623,7 @@
}
NetworkAgentInfo newDefaultAgent = null;
if (nai.networkRequests.get(mDefaultRequest.requestId) != null) {
- newDefaultAgent = mNetworkForRequestId.get(mDefaultRequest.requestId);
+ newDefaultAgent = getDefaultNetwork();
if (newDefaultAgent != null) {
intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO,
newDefaultAgent.networkInfo);
@@ -4165,9 +4633,6 @@
}
intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION,
mDefaultInetConditionPublished);
- final Intent immediateIntent = new Intent(intent);
- immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
- sendStickyBroadcast(immediateIntent);
sendStickyBroadcast(intent);
if (newDefaultAgent != null) {
sendConnectedBroadcast(newDefaultAgent.networkInfo);
@@ -4235,8 +4700,57 @@
public boolean setUnderlyingNetworksForVpn(Network[] networks) {
throwIfLockdownEnabled();
int user = UserHandle.getUserId(Binder.getCallingUid());
+ boolean success;
synchronized (mVpns) {
- return mVpns.get(user).setUnderlyingNetworks(networks);
+ success = mVpns.get(user).setUnderlyingNetworks(networks);
+ }
+ if (success) {
+ notifyIfacesChanged();
+ }
+ return success;
+ }
+
+ @Override
+ public void factoryReset() {
+ enforceConnectivityInternalPermission();
+
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
+ return;
+ }
+
+ final int userId = UserHandle.getCallingUserId();
+
+ // Turn airplane mode off
+ setAirplaneMode(false);
+
+ if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_TETHERING)) {
+ // Untether
+ for (String tether : getTetheredIfaces()) {
+ untether(tether);
+ }
+ }
+
+ if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) {
+ // Turn VPN off
+ VpnConfig vpnConfig = getVpnConfig(userId);
+ if (vpnConfig != null) {
+ if (vpnConfig.legacy) {
+ prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId);
+ } else {
+ // Prevent this app (packagename = vpnConfig.user) from initiating VPN connections
+ // in the future without user intervention.
+ setVpnPackageAuthorization(vpnConfig.user, userId, false);
+
+ prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN, userId);
+ }
+ }
}
}
+
+ @VisibleForTesting
+ public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
+ NetworkAgentInfo nai, NetworkRequest defaultRequest) {
+ return new NetworkMonitor(context, handler, nai, defaultRequest);
+ }
+
}
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index 3fa21d0..a9eaeee 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -22,16 +22,13 @@
import java.net.Inet4Address;
import android.content.Context;
-import android.net.IConnectivityManager;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkAgent;
-import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.os.Handler;
import android.os.Message;
-import android.os.Messenger;
import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.util.Slog;
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index f3e0bbc..39333f6 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -16,6 +16,8 @@
package com.android.server.connectivity;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+
import android.content.Context;
import android.net.LinkProperties;
import android.net.Network;
@@ -28,31 +30,92 @@
import android.util.SparseArray;
import com.android.internal.util.AsyncChannel;
+import com.android.server.ConnectivityService;
import com.android.server.connectivity.NetworkMonitor;
import java.util.ArrayList;
+import java.util.Comparator;
/**
* A bag class used by ConnectivityService for holding a collection of most recent
* information published by a particular NetworkAgent as well as the
* AsyncChannel/messenger for reaching that NetworkAgent and lists of NetworkRequests
- * interested in using it.
+ * interested in using it. Default sort order is descending by score.
*/
-public class NetworkAgentInfo {
+// States of a network:
+// --------------------
+// 1. registered, uncreated, disconnected, unvalidated
+// This state is entered when a NetworkFactory registers a NetworkAgent in any state except
+// the CONNECTED state.
+// 2. registered, uncreated, connected, unvalidated
+// This state is entered when a registered NetworkAgent transitions to the CONNECTED state
+// ConnectivityService will tell netd to create the network and immediately transition to
+// state #3.
+// 3. registered, created, connected, unvalidated
+// If this network can satisfy the default NetworkRequest, then NetworkMonitor will
+// probe for Internet connectivity.
+// If this network cannot satisfy the default NetworkRequest, it will immediately be
+// transitioned to state #4.
+// A network may remain in this state if NetworkMonitor fails to find Internet connectivity,
+// for example:
+// a. a captive portal is present, or
+// b. a WiFi router whose Internet backhaul is down, or
+// c. a wireless connection stops transfering packets temporarily (e.g. device is in elevator
+// or tunnel) but does not disconnect from the AP/cell tower, or
+// d. a stand-alone device offering a WiFi AP without an uplink for configuration purposes.
+// 4. registered, created, connected, validated
+//
+// The device's default network connection:
+// ----------------------------------------
+// Networks in states #3 and #4 may be used as a device's default network connection if they
+// satisfy the default NetworkRequest.
+// A network, that satisfies the default NetworkRequest, in state #4 should always be chosen
+// in favor of a network, that satisfies the default NetworkRequest, in state #3.
+// When deciding between two networks, that both satisfy the default NetworkRequest, to select
+// for the default network connection, the one with the higher score should be chosen.
+//
+// When a network disconnects:
+// ---------------------------
+// If a network's transport disappears, for example:
+// a. WiFi turned off, or
+// b. cellular data turned off, or
+// c. airplane mode is turned on, or
+// d. a wireless connection disconnects from AP/cell tower entirely (e.g. device is out of range
+// of AP for an extended period of time, or switches to another AP without roaming)
+// then that network can transition from any state (#1-#4) to unregistered. This happens by
+// the transport disconnecting their NetworkAgent's AsyncChannel with ConnectivityManager.
+// ConnectivityService also tells netd to destroy the network.
+//
+// When ConnectivityService disconnects a network:
+// -----------------------------------------------
+// If a network has no chance of satisfying any requests (even if it were to become validated
+// and enter state #4), ConnectivityService will disconnect the NetworkAgent's AsyncChannel.
+// If the network ever for any period of time had satisfied a NetworkRequest (i.e. had been
+// the highest scoring that satisfied the NetworkRequest's constraints), but is no longer the
+// highest scoring network for any NetworkRequest, then there will be a 30s pause before
+// ConnectivityService disconnects the NetworkAgent's AsyncChannel. During this pause the
+// network is considered "lingering". This pause exists to allow network communication to be
+// wrapped up rather than abruptly terminated. During this pause if the network begins satisfying
+// a NetworkRequest, ConnectivityService will cancel the future disconnection of the NetworkAgent's
+// AsyncChannel, and the network is no longer considered "lingering".
+public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
public NetworkInfo networkInfo;
- public Network network;
+ // This Network object should always be used if possible, so as to encourage reuse of the
+ // enclosed socket factory and connection pool. Avoid creating other Network objects.
+ // This Network object is always valid.
+ public final Network network;
public LinkProperties linkProperties;
+ // This should only be modified via ConnectivityService.updateCapabilities().
public NetworkCapabilities networkCapabilities;
public final NetworkMonitor networkMonitor;
public final NetworkMisc networkMisc;
// Indicates if netd has been told to create this Network. Once created the appropriate routing
// rules are setup and routes are added so packets can begin flowing over the Network.
- // NOTE: This is a sticky bit; once set it is never cleared.
+ // This is a sticky bit; once set it is never cleared.
public boolean created;
// Set to true if this Network successfully passed validation or if it did not satisfy the
// default NetworkRequest in which case validation will not be attempted.
- // NOTE: This is a sticky bit; once set it is never cleared even if future validation attempts
- // fail.
+ // This is a sticky bit; once set it is never cleared even if future validation attempts fail.
public boolean everValidated;
// The result of the last validation attempt on this network (true if validated, false if not).
@@ -62,13 +125,30 @@
// TODO: Fix the network scoring code, remove this, and rename everValidated to validated.
public boolean lastValidated;
+ // Whether a captive portal was ever detected on this network.
+ // This is a sticky bit; once set it is never cleared.
+ public boolean everCaptivePortalDetected;
+
+ // Whether a captive portal was found during the last network validation attempt.
+ public boolean lastCaptivePortalDetected;
+
+ // Indicates whether the network is lingering. Networks are lingered when they become unneeded
+ // as a result of their NetworkRequests being satisfied by a different network, so as to allow
+ // communication to wrap up before the network is taken down. This usually only happens to the
+ // default network. Lingering ends with either the linger timeout expiring and the network
+ // being taken down, or the network satisfying a request again.
+ public boolean lingering;
+
// This represents the last score received from the NetworkAgent.
private int currentScore;
// Penalty applied to scores of Networks that have not been validated.
private static final int UNVALIDATED_SCORE_PENALTY = 40;
// Score for explicitly connected network.
- private static final int EXPLICITLY_SELECTED_NETWORK_SCORE = 100;
+ //
+ // This ensures that a) the explicitly selected network is never trumped by anything else, and
+ // b) the explicitly selected network is never torn down.
+ private static final int MAXIMUM_NETWORK_SCORE = 100;
// The list of NetworkRequests being satisfied by this Network.
public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>();
@@ -83,25 +163,30 @@
// Used by ConnectivityService to keep track of 464xlat.
public Nat464Xlat clatd;
- public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, NetworkInfo info,
+ public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, int score, Context context, Handler handler,
- NetworkMisc misc, NetworkRequest defaultRequest) {
+ NetworkMisc misc, NetworkRequest defaultRequest, ConnectivityService connService) {
this.messenger = messenger;
asyncChannel = ac;
- network = null;
+ network = net;
networkInfo = info;
linkProperties = lp;
networkCapabilities = nc;
currentScore = score;
- networkMonitor = new NetworkMonitor(context, handler, this, defaultRequest);
+ networkMonitor = connService.createNetworkMonitor(context, handler, this, defaultRequest);
networkMisc = misc;
- created = false;
- everValidated = false;
- lastValidated = false;
}
- public void addRequest(NetworkRequest networkRequest) {
+ /**
+ * Add {@code networkRequest} to this network as it's satisfied by this network.
+ * NOTE: This function must only be called on ConnectivityService's main thread.
+ * @return true if {@code networkRequest} was added or false if {@code networkRequest} was
+ * already present.
+ */
+ public boolean addRequest(NetworkRequest networkRequest) {
+ if (networkRequests.get(networkRequest.requestId) == networkRequest) return false;
networkRequests.put(networkRequest.requestId, networkRequest);
+ return true;
}
// Does this network satisfy request?
@@ -120,13 +205,23 @@
// score. The NetworkScore class would provide a nice place to centralize score constants
// so they are not scattered about the transports.
+ // If this network is explicitly selected and the user has decided to use it even if it's
+ // unvalidated, give it the maximum score. Also give it the maximum score if it's explicitly
+ // selected and we're trying to see what its score could be. This ensures that we don't tear
+ // down an explicitly selected network before the user gets a chance to prefer it when
+ // a higher-scoring network (e.g., Ethernet) is available.
+ if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
+ return MAXIMUM_NETWORK_SCORE;
+ }
+
int score = currentScore;
-
- if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY;
+ // Use NET_CAPABILITY_VALIDATED here instead of lastValidated, this allows
+ // ConnectivityService.updateCapabilities() to compute the old score prior to updating
+ // networkCapabilities (with a potentially different validated state).
+ if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED) && !pretendValidated) {
+ score -= UNVALIDATED_SCORE_PENALTY;
+ }
if (score < 0) score = 0;
-
- if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE;
-
return score;
}
@@ -152,8 +247,12 @@
linkProperties + "} nc{" +
networkCapabilities + "} Score{" + getCurrentScore() + "} " +
"everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} " +
- "created{" + created + "} " +
- "explicitlySelected{" + networkMisc.explicitlySelected + "} }";
+ "created{" + created + "} lingering{" + lingering + "} " +
+ "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
+ "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
+ "everCaptivePortalDetected{" + everCaptivePortalDetected + "} " +
+ "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} " +
+ "}";
}
public String name() {
@@ -161,4 +260,10 @@
networkInfo.getSubtypeName() + ") - " +
(network == null ? "null" : network.toString()) + "]";
}
+
+ // Enables sorting in descending order of score.
+ @Override
+ public int compareTo(NetworkAgentInfo other) {
+ return other.getCurrentScore() - getCurrentScore();
+ }
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
new file mode 100644
index 0000000..aca6991
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/NetworkDiagnostics.java
@@ -0,0 +1,529 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity;
+
+import static android.system.OsConstants.*;
+
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkUtils;
+import android.net.RouteInfo;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructTimeval;
+import android.text.TextUtils;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.InterruptedIOException;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import libcore.io.IoUtils;
+
+
+/**
+ * NetworkDiagnostics
+ *
+ * A simple class to diagnose network connectivity fundamentals. Current
+ * checks performed are:
+ * - ICMPv4/v6 echo requests for all routers
+ * - ICMPv4/v6 echo requests for all DNS servers
+ * - DNS UDP queries to all DNS servers
+ *
+ * Currently unimplemented checks include:
+ * - report ARP/ND data about on-link neighbors
+ * - DNS TCP queries to all DNS servers
+ * - HTTP DIRECT and PROXY checks
+ * - port 443 blocking/TLS intercept checks
+ * - QUIC reachability checks
+ * - MTU checks
+ *
+ * The supplied timeout bounds the entire diagnostic process. Each specific
+ * check class must implement this upper bound on measurements in whichever
+ * manner is most appropriate and effective.
+ *
+ * @hide
+ */
+public class NetworkDiagnostics {
+ private static final String TAG = "NetworkDiagnostics";
+
+ private static final InetAddress TEST_DNS4 = NetworkUtils.numericToInetAddress("8.8.8.8");
+ private static final InetAddress TEST_DNS6 = NetworkUtils.numericToInetAddress(
+ "2001:4860:4860::8888");
+
+ // For brevity elsewhere.
+ private static final long now() {
+ return SystemClock.elapsedRealtime();
+ }
+
+ // Values from RFC 1035 section 4.1.1, names from <arpa/nameser.h>.
+ // Should be a member of DnsUdpCheck, but "compiler says no".
+ public static enum DnsResponseCode { NOERROR, FORMERR, SERVFAIL, NXDOMAIN, NOTIMP, REFUSED };
+
+ private final Network mNetwork;
+ private final LinkProperties mLinkProperties;
+ private final Integer mInterfaceIndex;
+
+ private final long mTimeoutMs;
+ private final long mStartTime;
+ private final long mDeadlineTime;
+
+ // A counter, initialized to the total number of measurements,
+ // so callers can wait for completion.
+ private final CountDownLatch mCountDownLatch;
+
+ private class Measurement {
+ private static final String SUCCEEDED = "SUCCEEDED";
+ private static final String FAILED = "FAILED";
+
+ // TODO: Refactor to make these private for better encapsulation.
+ public String description = "";
+ public long startTime;
+ public long finishTime;
+ public String result = "";
+ public Thread thread;
+
+ public void recordSuccess(String msg) {
+ maybeFixupTimes();
+ result = SUCCEEDED + ": " + msg;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ public void recordFailure(String msg) {
+ maybeFixupTimes();
+ result = FAILED + ": " + msg;
+ if (mCountDownLatch != null) {
+ mCountDownLatch.countDown();
+ }
+ }
+
+ private void maybeFixupTimes() {
+ // Allows the caller to just set success/failure and not worry
+ // about also setting the correct finishing time.
+ if (finishTime == 0) { finishTime = now(); }
+
+ // In cases where, for example, a failure has occurred before the
+ // measurement even began, fixup the start time to reflect as much.
+ if (startTime == 0) { startTime = finishTime; }
+ }
+
+ @Override
+ public String toString() {
+ return description + ": " + result + " (" + (finishTime - startTime) + "ms)";
+ }
+ }
+
+ private final Map<InetAddress, Measurement> mIcmpChecks = new HashMap<>();
+ private final Map<InetAddress, Measurement> mDnsUdpChecks = new HashMap<>();
+ private final String mDescription;
+
+
+ public NetworkDiagnostics(Network network, LinkProperties lp, long timeoutMs) {
+ mNetwork = network;
+ mLinkProperties = lp;
+ mInterfaceIndex = getInterfaceIndex(mLinkProperties.getInterfaceName());
+ mTimeoutMs = timeoutMs;
+ mStartTime = now();
+ mDeadlineTime = mStartTime + mTimeoutMs;
+
+ // Hardcode measurements to TEST_DNS4 and TEST_DNS6 in order to test off-link connectivity.
+ // We are free to modify mLinkProperties with impunity because ConnectivityService passes us
+ // a copy and not the original object. It's easier to do it this way because we don't need
+ // to check whether the LinkProperties already contains these DNS servers because
+ // LinkProperties#addDnsServer checks for duplicates.
+ if (mLinkProperties.isReachable(TEST_DNS4)) {
+ mLinkProperties.addDnsServer(TEST_DNS4);
+ }
+ // TODO: we could use mLinkProperties.isReachable(TEST_DNS6) here, because we won't set any
+ // DNS servers for which isReachable() is false, but since this is diagnostic code, be extra
+ // careful.
+ if (mLinkProperties.hasGlobalIPv6Address() || mLinkProperties.hasIPv6DefaultRoute()) {
+ mLinkProperties.addDnsServer(TEST_DNS6);
+ }
+
+ for (RouteInfo route : mLinkProperties.getRoutes()) {
+ if (route.hasGateway()) {
+ prepareIcmpMeasurement(route.getGateway());
+ }
+ }
+ for (InetAddress nameserver : mLinkProperties.getDnsServers()) {
+ prepareIcmpMeasurement(nameserver);
+ prepareDnsMeasurement(nameserver);
+ }
+
+ mCountDownLatch = new CountDownLatch(totalMeasurementCount());
+
+ startMeasurements();
+
+ mDescription = "ifaces{" + TextUtils.join(",", mLinkProperties.getAllInterfaceNames()) + "}"
+ + " index{" + mInterfaceIndex + "}"
+ + " network{" + mNetwork + "}"
+ + " nethandle{" + mNetwork.getNetworkHandle() + "}";
+ }
+
+ private static Integer getInterfaceIndex(String ifname) {
+ try {
+ NetworkInterface ni = NetworkInterface.getByName(ifname);
+ return ni.getIndex();
+ } catch (NullPointerException | SocketException e) {
+ return null;
+ }
+ }
+
+ private void prepareIcmpMeasurement(InetAddress target) {
+ if (!mIcmpChecks.containsKey(target)) {
+ Measurement measurement = new Measurement();
+ measurement.thread = new Thread(new IcmpCheck(target, measurement));
+ mIcmpChecks.put(target, measurement);
+ }
+ }
+
+ private void prepareDnsMeasurement(InetAddress target) {
+ if (!mDnsUdpChecks.containsKey(target)) {
+ Measurement measurement = new Measurement();
+ measurement.thread = new Thread(new DnsUdpCheck(target, measurement));
+ mDnsUdpChecks.put(target, measurement);
+ }
+ }
+
+ private int totalMeasurementCount() {
+ return mIcmpChecks.size() + mDnsUdpChecks.size();
+ }
+
+ private void startMeasurements() {
+ for (Measurement measurement : mIcmpChecks.values()) {
+ measurement.thread.start();
+ }
+ for (Measurement measurement : mDnsUdpChecks.values()) {
+ measurement.thread.start();
+ }
+ }
+
+ public void waitForMeasurements() {
+ try {
+ mCountDownLatch.await(mDeadlineTime - now(), TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ignored) {}
+ }
+
+ public void dump(IndentingPrintWriter pw) {
+ pw.println(TAG + ":" + mDescription);
+ final long unfinished = mCountDownLatch.getCount();
+ if (unfinished > 0) {
+ // This can't happen unless a caller forgets to call waitForMeasurements()
+ // or a measurement isn't implemented to correctly honor the timeout.
+ pw.println("WARNING: countdown wait incomplete: "
+ + unfinished + " unfinished measurements");
+ }
+
+ pw.increaseIndent();
+ for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
+ if (entry.getKey() instanceof Inet4Address) {
+ pw.println(entry.getValue().toString());
+ }
+ }
+ for (Map.Entry<InetAddress, Measurement> entry : mIcmpChecks.entrySet()) {
+ if (entry.getKey() instanceof Inet6Address) {
+ pw.println(entry.getValue().toString());
+ }
+ }
+ for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
+ if (entry.getKey() instanceof Inet4Address) {
+ pw.println(entry.getValue().toString());
+ }
+ }
+ for (Map.Entry<InetAddress, Measurement> entry : mDnsUdpChecks.entrySet()) {
+ if (entry.getKey() instanceof Inet6Address) {
+ pw.println(entry.getValue().toString());
+ }
+ }
+ pw.decreaseIndent();
+ }
+
+
+ private class SimpleSocketCheck implements Closeable {
+ protected final InetAddress mTarget;
+ protected final int mAddressFamily;
+ protected final Measurement mMeasurement;
+ protected FileDescriptor mFileDescriptor;
+ protected SocketAddress mSocketAddress;
+
+ protected SimpleSocketCheck(InetAddress target, Measurement measurement) {
+ mMeasurement = measurement;
+
+ if (target instanceof Inet6Address) {
+ Inet6Address targetWithScopeId = null;
+ if (target.isLinkLocalAddress() && mInterfaceIndex != null) {
+ try {
+ targetWithScopeId = Inet6Address.getByAddress(
+ null, target.getAddress(), mInterfaceIndex);
+ } catch (UnknownHostException e) {
+ mMeasurement.recordFailure(e.toString());
+ }
+ }
+ mTarget = (targetWithScopeId != null) ? targetWithScopeId : target;
+ mAddressFamily = AF_INET6;
+ } else {
+ mTarget = target;
+ mAddressFamily = AF_INET;
+ }
+ }
+
+ protected void setupSocket(
+ int sockType, int protocol, long writeTimeout, long readTimeout, int dstPort)
+ throws ErrnoException, IOException {
+ mFileDescriptor = Os.socket(mAddressFamily, sockType, protocol);
+ // Setting SNDTIMEO is purely for defensive purposes.
+ Os.setsockoptTimeval(mFileDescriptor,
+ SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(writeTimeout));
+ Os.setsockoptTimeval(mFileDescriptor,
+ SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(readTimeout));
+ // TODO: Use IP_RECVERR/IPV6_RECVERR, pending OsContants availability.
+ mNetwork.bindSocket(mFileDescriptor);
+ Os.connect(mFileDescriptor, mTarget, dstPort);
+ mSocketAddress = Os.getsockname(mFileDescriptor);
+ }
+
+ protected String getSocketAddressString() {
+ // The default toString() implementation is not the prettiest.
+ InetSocketAddress inetSockAddr = (InetSocketAddress) mSocketAddress;
+ InetAddress localAddr = inetSockAddr.getAddress();
+ return String.format(
+ (localAddr instanceof Inet6Address ? "[%s]:%d" : "%s:%d"),
+ localAddr.getHostAddress(), inetSockAddr.getPort());
+ }
+
+ @Override
+ public void close() {
+ IoUtils.closeQuietly(mFileDescriptor);
+ }
+ }
+
+
+ private class IcmpCheck extends SimpleSocketCheck implements Runnable {
+ private static final int TIMEOUT_SEND = 100;
+ private static final int TIMEOUT_RECV = 300;
+ private static final int ICMPV4_ECHO_REQUEST = 8;
+ private static final int ICMPV6_ECHO_REQUEST = 128;
+ private static final int PACKET_BUFSIZE = 512;
+ private final int mProtocol;
+ private final int mIcmpType;
+
+ public IcmpCheck(InetAddress target, Measurement measurement) {
+ super(target, measurement);
+
+ if (mAddressFamily == AF_INET6) {
+ mProtocol = IPPROTO_ICMPV6;
+ mIcmpType = ICMPV6_ECHO_REQUEST;
+ mMeasurement.description = "ICMPv6";
+ } else {
+ mProtocol = IPPROTO_ICMP;
+ mIcmpType = ICMPV4_ECHO_REQUEST;
+ mMeasurement.description = "ICMPv4";
+ }
+
+ mMeasurement.description += " dst{" + mTarget.getHostAddress() + "}";
+ }
+
+ @Override
+ public void run() {
+ // Check if this measurement has already failed during setup.
+ if (mMeasurement.finishTime > 0) {
+ // If the measurement failed during construction it didn't
+ // decrement the countdown latch; do so here.
+ mCountDownLatch.countDown();
+ return;
+ }
+
+ try {
+ setupSocket(SOCK_DGRAM, mProtocol, TIMEOUT_SEND, TIMEOUT_RECV, 0);
+ } catch (ErrnoException | IOException e) {
+ mMeasurement.recordFailure(e.toString());
+ return;
+ }
+ mMeasurement.description += " src{" + getSocketAddressString() + "}";
+
+ // Build a trivial ICMP packet.
+ final byte[] icmpPacket = {
+ (byte) mIcmpType, 0, 0, 0, 0, 0, 0, 0 // ICMP header
+ };
+
+ int count = 0;
+ mMeasurement.startTime = now();
+ while (now() < mDeadlineTime - (TIMEOUT_SEND + TIMEOUT_RECV)) {
+ count++;
+ icmpPacket[icmpPacket.length - 1] = (byte) count;
+ try {
+ Os.write(mFileDescriptor, icmpPacket, 0, icmpPacket.length);
+ } catch (ErrnoException | InterruptedIOException e) {
+ mMeasurement.recordFailure(e.toString());
+ break;
+ }
+
+ try {
+ ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE);
+ Os.read(mFileDescriptor, reply);
+ // TODO: send a few pings back to back to guesstimate packet loss.
+ mMeasurement.recordSuccess("1/" + count);
+ break;
+ } catch (ErrnoException | InterruptedIOException e) {
+ continue;
+ }
+ }
+ if (mMeasurement.finishTime == 0) {
+ mMeasurement.recordFailure("0/" + count);
+ }
+
+ close();
+ }
+ }
+
+
+ private class DnsUdpCheck extends SimpleSocketCheck implements Runnable {
+ private static final int TIMEOUT_SEND = 100;
+ private static final int TIMEOUT_RECV = 500;
+ private static final int DNS_SERVER_PORT = 53;
+ private static final int RR_TYPE_A = 1;
+ private static final int RR_TYPE_AAAA = 28;
+ private static final int PACKET_BUFSIZE = 512;
+
+ private final Random mRandom = new Random();
+
+ // Should be static, but the compiler mocks our puny, human attempts at reason.
+ private String responseCodeStr(int rcode) {
+ try {
+ return DnsResponseCode.values()[rcode].toString();
+ } catch (IndexOutOfBoundsException e) {
+ return String.valueOf(rcode);
+ }
+ }
+
+ private final int mQueryType;
+
+ public DnsUdpCheck(InetAddress target, Measurement measurement) {
+ super(target, measurement);
+
+ // TODO: Ideally, query the target for both types regardless of address family.
+ if (mAddressFamily == AF_INET6) {
+ mQueryType = RR_TYPE_AAAA;
+ } else {
+ mQueryType = RR_TYPE_A;
+ }
+
+ mMeasurement.description = "DNS UDP dst{" + mTarget.getHostAddress() + "}";
+ }
+
+ @Override
+ public void run() {
+ // Check if this measurement has already failed during setup.
+ if (mMeasurement.finishTime > 0) {
+ // If the measurement failed during construction it didn't
+ // decrement the countdown latch; do so here.
+ mCountDownLatch.countDown();
+ return;
+ }
+
+ try {
+ setupSocket(SOCK_DGRAM, IPPROTO_UDP, TIMEOUT_SEND, TIMEOUT_RECV, DNS_SERVER_PORT);
+ } catch (ErrnoException | IOException e) {
+ mMeasurement.recordFailure(e.toString());
+ return;
+ }
+ mMeasurement.description += " src{" + getSocketAddressString() + "}";
+
+ // This needs to be fixed length so it can be dropped into the pre-canned packet.
+ final String sixRandomDigits =
+ Integer.valueOf(mRandom.nextInt(900000) + 100000).toString();
+ mMeasurement.description += " qtype{" + mQueryType + "}"
+ + " qname{" + sixRandomDigits + "-android-ds.metric.gstatic.com}";
+
+ // Build a trivial DNS packet.
+ final byte[] dnsPacket = getDnsQueryPacket(sixRandomDigits);
+
+ int count = 0;
+ mMeasurement.startTime = now();
+ while (now() < mDeadlineTime - (TIMEOUT_RECV + TIMEOUT_RECV)) {
+ count++;
+ try {
+ Os.write(mFileDescriptor, dnsPacket, 0, dnsPacket.length);
+ } catch (ErrnoException | InterruptedIOException e) {
+ mMeasurement.recordFailure(e.toString());
+ break;
+ }
+
+ try {
+ ByteBuffer reply = ByteBuffer.allocate(PACKET_BUFSIZE);
+ Os.read(mFileDescriptor, reply);
+ // TODO: more correct and detailed evaluation of the response,
+ // possibly adding the returned IP address(es) to the output.
+ final String rcodeStr = (reply.limit() > 3)
+ ? " " + responseCodeStr((int) (reply.get(3)) & 0x0f)
+ : "";
+ mMeasurement.recordSuccess("1/" + count + rcodeStr);
+ break;
+ } catch (ErrnoException | InterruptedIOException e) {
+ continue;
+ }
+ }
+ if (mMeasurement.finishTime == 0) {
+ mMeasurement.recordFailure("0/" + count);
+ }
+
+ close();
+ }
+
+ private byte[] getDnsQueryPacket(String sixRandomDigits) {
+ byte[] rnd = sixRandomDigits.getBytes(StandardCharsets.US_ASCII);
+ return new byte[] {
+ (byte) mRandom.nextInt(), (byte) mRandom.nextInt(), // [0-1] query ID
+ 1, 0, // [2-3] flags; byte[2] = 1 for recursion desired (RD).
+ 0, 1, // [4-5] QDCOUNT (number of queries)
+ 0, 0, // [6-7] ANCOUNT (number of answers)
+ 0, 0, // [8-9] NSCOUNT (number of name server records)
+ 0, 0, // [10-11] ARCOUNT (number of additional records)
+ 17, rnd[0], rnd[1], rnd[2], rnd[3], rnd[4], rnd[5],
+ '-', 'a', 'n', 'd', 'r', 'o', 'i', 'd', '-', 'd', 's',
+ 6, 'm', 'e', 't', 'r', 'i', 'c',
+ 7, 'g', 's', 't', 'a', 't', 'i', 'c',
+ 3, 'c', 'o', 'm',
+ 0, // null terminator of FQDN (root TLD)
+ 0, (byte) mQueryType, // QTYPE
+ 0, 1 // QCLASS, set to 1 = IN (Internet)
+ };
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index c198900..b4c76b7 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -16,10 +16,30 @@
package com.android.server;
-import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IA;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
@@ -30,30 +50,52 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
+import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkMisc;
+import android.net.NetworkRequest;
import android.net.RouteInfo;
+import android.os.ConditionVariable;
import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import android.os.INetworkManagementService;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.util.Log;
import android.util.LogPrinter;
+import com.android.server.connectivity.NetworkAgentInfo;
+import com.android.server.connectivity.NetworkMonitor;
+
import org.mockito.ArgumentCaptor;
import java.net.InetAddress;
import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* Tests for {@link ConnectivityService}.
+ *
+ * Build, install and run with:
+ * runtest frameworks-services -c com.android.server.ConnectivityServiceTest
*/
-@LargeTest
public class ConnectivityServiceTest extends AndroidTestCase {
private static final String TAG = "ConnectivityServiceTest";
@@ -75,73 +117,1074 @@
private INetworkManagementService mNetManager;
private INetworkStatsService mStatsService;
private INetworkPolicyManager mPolicyService;
-// private ConnectivityService.NetworkFactory mNetFactory;
private BroadcastInterceptingContext mServiceContext;
- private ConnectivityService mService;
+ private WrappedConnectivityService mService;
+ private ConnectivityManager mCm;
+ private MockNetworkAgent mWiFiNetworkAgent;
+ private MockNetworkAgent mCellNetworkAgent;
-// TODO: rework with network factory
-// private MockNetwork mMobile;
-// private MockNetwork mWifi;
-//
-// private Handler mTrackerHandler;
-//
-// private static class MockNetwork {
-// public NetworkStateTracker tracker;
-// public NetworkInfo info;
-// public LinkProperties link;
-//
-// public MockNetwork(int type) {
-// tracker = mock(NetworkStateTracker.class);
-// info = new NetworkInfo(type, -1, getNetworkTypeName(type), null);
-// link = new LinkProperties();
-// }
-//
-// public void doReturnDefaults() {
-// // TODO: eventually CS should make defensive copies
-// doReturn(new NetworkInfo(info)).when(tracker).getNetworkInfo();
-// doReturn(new LinkProperties(link)).when(tracker).getLinkProperties();
-//
-// // fallback to default TCP buffers
-// doReturn("").when(tracker).getTcpBufferSizesPropName();
-// }
-// }
-//
-// @Override
-// public void setUp() throws Exception {
-// super.setUp();
-//
-// mServiceContext = new BroadcastInterceptingContext(getContext());
-//
-// mNetManager = mock(INetworkManagementService.class);
-// mStatsService = mock(INetworkStatsService.class);
-// mPolicyService = mock(INetworkPolicyManager.class);
-// mNetFactory = mock(ConnectivityService.NetworkFactory.class);
-//
-// mMobile = new MockNetwork(TYPE_MOBILE);
-// mWifi = new MockNetwork(TYPE_WIFI);
-//
-// // omit most network trackers
-// doThrow(new IllegalArgumentException("Not supported in test environment"))
-// .when(mNetFactory).createTracker(anyInt(), isA(NetworkConfig.class));
-//
-// doReturn(mMobile.tracker)
-// .when(mNetFactory).createTracker(eq(TYPE_MOBILE), isA(NetworkConfig.class));
-// doReturn(mWifi.tracker)
-// .when(mNetFactory).createTracker(eq(TYPE_WIFI), isA(NetworkConfig.class));
-//
-// final ArgumentCaptor<Handler> trackerHandler = ArgumentCaptor.forClass(Handler.class);
-// doNothing().when(mMobile.tracker)
-// .startMonitoring(isA(Context.class), trackerHandler.capture());
-//
-// mService = new ConnectivityService(
-// mServiceContext, mNetManager, mStatsService, mPolicyService);
-// mService.systemReady();
-//
-// mTrackerHandler = trackerHandler.getValue();
-// mTrackerHandler.getLooper().setMessageLogging(new LogPrinter(Log.INFO, TAG));
-// }
-//
+ private class MockContext extends BroadcastInterceptingContext {
+ MockContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ // PendingIntents sent by the AlarmManager are not intercepted by
+ // BroadcastInterceptingContext so we must really register the receiver.
+ // This shouldn't effect the real NetworkMonitors as the action contains a random token.
+ if (filter.getAction(0).startsWith("android.net.netmon.lingerExpired")) {
+ return getBaseContext().registerReceiver(receiver, filter);
+ } else {
+ return super.registerReceiver(receiver, filter);
+ }
+ }
+
+ @Override
+ public Object getSystemService (String name) {
+ if (name == Context.CONNECTIVITY_SERVICE) return mCm;
+ return super.getSystemService(name);
+ }
+ }
+
+ private class MockNetworkAgent {
+ private final WrappedNetworkMonitor mWrappedNetworkMonitor;
+ private final NetworkInfo mNetworkInfo;
+ private final NetworkCapabilities mNetworkCapabilities;
+ private final Thread mThread;
+ private final ConditionVariable mDisconnected = new ConditionVariable();
+ private int mScore;
+ private NetworkAgent mNetworkAgent;
+
+ MockNetworkAgent(int transport) {
+ final int type = transportToLegacyType(transport);
+ final String typeName = ConnectivityManager.getNetworkTypeName(type);
+ mNetworkInfo = new NetworkInfo(type, 0, typeName, "Mock");
+ mNetworkCapabilities = new NetworkCapabilities();
+ mNetworkCapabilities.addTransportType(transport);
+ switch (transport) {
+ case TRANSPORT_WIFI:
+ mScore = 60;
+ break;
+ case TRANSPORT_CELLULAR:
+ mScore = 50;
+ break;
+ default:
+ throw new UnsupportedOperationException("unimplemented network type");
+ }
+ final ConditionVariable initComplete = new ConditionVariable();
+ final ConditionVariable networkMonitorAvailable = mService.getNetworkMonitorCreatedCV();
+ mThread = new Thread() {
+ public void run() {
+ Looper.prepare();
+ mNetworkAgent = new NetworkAgent(Looper.myLooper(), mServiceContext,
+ "Mock" + typeName, mNetworkInfo, mNetworkCapabilities,
+ new LinkProperties(), mScore, new NetworkMisc()) {
+ public void unwanted() { mDisconnected.open(); }
+ };
+ initComplete.open();
+ Looper.loop();
+ }
+ };
+ mThread.start();
+ waitFor(initComplete);
+ waitFor(networkMonitorAvailable);
+ mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor();
+ }
+
+ public void adjustScore(int change) {
+ mScore += change;
+ mNetworkAgent.sendNetworkScore(mScore);
+ }
+
+ public void addCapability(int capability) {
+ mNetworkCapabilities.addCapability(capability);
+ mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
+ }
+
+ public void connectWithoutInternet() {
+ mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
+ mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+ }
+
+ /**
+ * Transition this NetworkAgent to CONNECTED state with NET_CAPABILITY_INTERNET.
+ * @param validated Indicate if network should pretend to be validated.
+ */
+ public void connect(boolean validated) {
+ assertEquals(mNetworkInfo.getDetailedState(), DetailedState.IDLE);
+ assertFalse(mNetworkCapabilities.hasCapability(NET_CAPABILITY_INTERNET));
+
+ NetworkCallback callback = null;
+ final ConditionVariable validatedCv = new ConditionVariable();
+ if (validated) {
+ mWrappedNetworkMonitor.gen204ProbeResult = 204;
+ NetworkRequest request = new NetworkRequest.Builder()
+ .addTransportType(mNetworkCapabilities.getTransportTypes()[0])
+ .build();
+ callback = new NetworkCallback() {
+ public void onCapabilitiesChanged(Network network,
+ NetworkCapabilities networkCapabilities) {
+ if (network.equals(getNetwork()) &&
+ networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
+ validatedCv.open();
+ }
+ }
+ };
+ mCm.registerNetworkCallback(request, callback);
+ }
+ addCapability(NET_CAPABILITY_INTERNET);
+
+ connectWithoutInternet();
+
+ if (validated) {
+ // Wait for network to validate.
+ waitFor(validatedCv);
+ mWrappedNetworkMonitor.gen204ProbeResult = 500;
+ }
+
+ if (callback != null) mCm.unregisterNetworkCallback(callback);
+ }
+
+ public void connectWithCaptivePortal() {
+ mWrappedNetworkMonitor.gen204ProbeResult = 200;
+ connect(false);
+ waitFor(new Criteria() { public boolean get() {
+ NetworkCapabilities caps = mCm.getNetworkCapabilities(getNetwork());
+ return caps != null && caps.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL);} });
+ mWrappedNetworkMonitor.gen204ProbeResult = 500;
+ }
+
+ public void disconnect() {
+ mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
+ mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+ }
+
+ public Network getNetwork() {
+ return new Network(mNetworkAgent.netId);
+ }
+
+ public ConditionVariable getDisconnectedCV() {
+ return mDisconnected;
+ }
+
+ public WrappedNetworkMonitor getWrappedNetworkMonitor() {
+ return mWrappedNetworkMonitor;
+ }
+ }
+
+ private static class MockNetworkFactory extends NetworkFactory {
+ private final ConditionVariable mNetworkStartedCV = new ConditionVariable();
+ private final ConditionVariable mNetworkStoppedCV = new ConditionVariable();
+ private final ConditionVariable mNetworkRequestedCV = new ConditionVariable();
+ private final ConditionVariable mNetworkReleasedCV = new ConditionVariable();
+ private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false);
+
+ public MockNetworkFactory(Looper looper, Context context, String logTag,
+ NetworkCapabilities filter) {
+ super(looper, context, logTag, filter);
+ }
+
+ public int getMyRequestCount() {
+ return getRequestCount();
+ }
+
+ protected void startNetwork() {
+ mNetworkStarted.set(true);
+ mNetworkStartedCV.open();
+ }
+
+ protected void stopNetwork() {
+ mNetworkStarted.set(false);
+ mNetworkStoppedCV.open();
+ }
+
+ public boolean getMyStartRequested() {
+ return mNetworkStarted.get();
+ }
+
+ public ConditionVariable getNetworkStartedCV() {
+ mNetworkStartedCV.close();
+ return mNetworkStartedCV;
+ }
+
+ public ConditionVariable getNetworkStoppedCV() {
+ mNetworkStoppedCV.close();
+ return mNetworkStoppedCV;
+ }
+
+ protected void needNetworkFor(NetworkRequest networkRequest, int score) {
+ super.needNetworkFor(networkRequest, score);
+ mNetworkRequestedCV.open();
+ }
+
+ protected void releaseNetworkFor(NetworkRequest networkRequest) {
+ super.releaseNetworkFor(networkRequest);
+ mNetworkReleasedCV.open();
+ }
+
+ public ConditionVariable getNetworkRequestedCV() {
+ mNetworkRequestedCV.close();
+ return mNetworkRequestedCV;
+ }
+
+ public ConditionVariable getNetworkReleasedCV() {
+ mNetworkReleasedCV.close();
+ return mNetworkReleasedCV;
+ }
+
+ public void waitForNetworkRequests(final int count) {
+ waitFor(new Criteria() { public boolean get() { return count == getRequestCount(); } });
+ }
+ }
+
+ // NetworkMonitor implementation allowing overriding of Internet connectivity probe result.
+ private class WrappedNetworkMonitor extends NetworkMonitor {
+ // HTTP response code fed back to NetworkMonitor for Internet connectivity probe.
+ public int gen204ProbeResult = 500;
+
+ public WrappedNetworkMonitor(Context context, Handler handler,
+ NetworkAgentInfo networkAgentInfo, NetworkRequest defaultRequest) {
+ super(context, handler, networkAgentInfo, defaultRequest);
+ }
+
+ @Override
+ protected int isCaptivePortal() {
+ return gen204ProbeResult;
+ }
+ }
+
+ private class WrappedConnectivityService extends ConnectivityService {
+ private final ConditionVariable mNetworkMonitorCreated = new ConditionVariable();
+ private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
+
+ public WrappedConnectivityService(Context context, INetworkManagementService netManager,
+ INetworkStatsService statsService, INetworkPolicyManager policyManager) {
+ super(context, netManager, statsService, policyManager);
+ }
+
+ @Override
+ protected int getDefaultTcpRwnd() {
+ // Prevent wrapped ConnectivityService from trying to write to SystemProperties.
+ return 0;
+ }
+
+ @Override
+ protected int reserveNetId() {
+ while (true) {
+ final int netId = super.reserveNetId();
+
+ // Don't overlap test NetIDs with real NetIDs as binding sockets to real networks
+ // can have odd side-effects, like network validations succeeding.
+ final Network[] networks = ConnectivityManager.from(getContext()).getAllNetworks();
+ boolean overlaps = false;
+ for (Network network : networks) {
+ if (netId == network.netId) {
+ overlaps = true;
+ break;
+ }
+ }
+ if (overlaps) continue;
+
+ return netId;
+ }
+ }
+
+ @Override
+ public NetworkMonitor createNetworkMonitor(Context context, Handler handler,
+ NetworkAgentInfo nai, NetworkRequest defaultRequest) {
+ final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(context, handler, nai,
+ defaultRequest);
+ mLastCreatedNetworkMonitor = monitor;
+ mNetworkMonitorCreated.open();
+ return monitor;
+ }
+
+ public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
+ return mLastCreatedNetworkMonitor;
+ }
+
+ public ConditionVariable getNetworkMonitorCreatedCV() {
+ mNetworkMonitorCreated.close();
+ return mNetworkMonitorCreated;
+ }
+ }
+
+ private interface Criteria {
+ public boolean get();
+ }
+
+ /**
+ * Wait up to 500ms for {@code criteria.get()} to become true, polling.
+ * Fails if 500ms goes by before {@code criteria.get()} to become true.
+ */
+ static private void waitFor(Criteria criteria) {
+ int delays = 0;
+ while (!criteria.get()) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ if (++delays == 5) fail();
+ }
+ }
+
+ /**
+ * Wait up to 500ms for {@code conditonVariable} to open.
+ * Fails if 500ms goes by before {@code conditionVariable} opens.
+ */
+ static private void waitFor(ConditionVariable conditionVariable) {
+ assertTrue(conditionVariable.block(500));
+ }
+
+ /**
+ * This should only be used to verify that nothing happens, in other words that no unexpected
+ * changes occur. It should never be used to wait for a specific positive signal to occur.
+ */
+ private void shortSleep() {
+ // TODO: Instead of sleeping, instead wait for all message loops to idle.
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mServiceContext = new MockContext(getContext());
+
+ mNetManager = mock(INetworkManagementService.class);
+ mStatsService = mock(INetworkStatsService.class);
+ mPolicyService = mock(INetworkPolicyManager.class);
+
+ mService = new WrappedConnectivityService(
+ mServiceContext, mNetManager, mStatsService, mPolicyService);
+ mService.systemReady();
+ mCm = new ConnectivityManager(getContext(), mService);
+ }
+
+ private int transportToLegacyType(int transport) {
+ switch (transport) {
+ case TRANSPORT_WIFI:
+ return TYPE_WIFI;
+ case TRANSPORT_CELLULAR:
+ return TYPE_MOBILE;
+ default:
+ throw new IllegalStateException("Unknown transport" + transport);
+ }
+ }
+
+ private void verifyActiveNetwork(int transport) {
+ // Test getActiveNetworkInfo()
+ assertNotNull(mCm.getActiveNetworkInfo());
+ assertEquals(transportToLegacyType(transport), mCm.getActiveNetworkInfo().getType());
+ // Test getActiveNetwork()
+ assertNotNull(mCm.getActiveNetwork());
+ switch (transport) {
+ case TRANSPORT_WIFI:
+ assertEquals(mCm.getActiveNetwork(), mWiFiNetworkAgent.getNetwork());
+ break;
+ case TRANSPORT_CELLULAR:
+ assertEquals(mCm.getActiveNetwork(), mCellNetworkAgent.getNetwork());
+ break;
+ default:
+ throw new IllegalStateException("Unknown transport" + transport);
+ }
+ // Test getNetworkInfo(Network)
+ assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork()));
+ assertEquals(transportToLegacyType(transport), mCm.getNetworkInfo(mCm.getActiveNetwork()).getType());
+ // Test getNetworkCapabilities(Network)
+ assertNotNull(mCm.getNetworkCapabilities(mCm.getActiveNetwork()));
+ assertTrue(mCm.getNetworkCapabilities(mCm.getActiveNetwork()).hasTransport(transport));
+ }
+
+ private void verifyNoNetwork() {
+ // Test getActiveNetworkInfo()
+ assertNull(mCm.getActiveNetworkInfo());
+ // Test getActiveNetwork()
+ assertNull(mCm.getActiveNetwork());
+ // Test getAllNetworks()
+ assertEquals(0, mCm.getAllNetworks().length);
+ }
+
+ /**
+ * Return a ConditionVariable that opens when {@code count} numbers of CONNECTIVITY_ACTION
+ * broadcasts are received.
+ */
+ private ConditionVariable waitForConnectivityBroadcasts(final int count) {
+ final ConditionVariable cv = new ConditionVariable();
+ mServiceContext.registerReceiver(new BroadcastReceiver() {
+ private int remaining = count;
+ public void onReceive(Context context, Intent intent) {
+ if (--remaining == 0) {
+ cv.open();
+ mServiceContext.unregisterReceiver(this);
+ }
+ }
+ }, new IntentFilter(CONNECTIVITY_ACTION));
+ return cv;
+ }
+
+ @LargeTest
+ public void testLingering() throws Exception {
+ // Decrease linger timeout to the minimum allowed by AlarmManagerService.
+ NetworkMonitor.SetDefaultLingerTime(5000);
+ verifyNoNetwork();
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ assertNull(mCm.getActiveNetworkInfo());
+ assertNull(mCm.getActiveNetwork());
+ // Test bringing up validated cellular.
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mCellNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ assertEquals(2, mCm.getAllNetworks().length);
+ assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) ||
+ mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork()));
+ assertTrue(mCm.getAllNetworks()[0].equals(mWiFiNetworkAgent.getNetwork()) ||
+ mCm.getAllNetworks()[1].equals(mWiFiNetworkAgent.getNetwork()));
+ // Test bringing up validated WiFi.
+ cv = waitForConnectivityBroadcasts(2);
+ mWiFiNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ assertEquals(2, mCm.getAllNetworks().length);
+ assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) ||
+ mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork()));
+ assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) ||
+ mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
+ // Test cellular linger timeout.
+ try {
+ Thread.sleep(6000);
+ } catch (InterruptedException e) {
+ }
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ assertEquals(1, mCm.getAllNetworks().length);
+ assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork());
+ // Test WiFi disconnect.
+ cv = waitForConnectivityBroadcasts(1);
+ mWiFiNetworkAgent.disconnect();
+ waitFor(cv);
+ verifyNoNetwork();
+ }
+
+ @LargeTest
+ public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception {
+ // Test bringing up unvalidated WiFi
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mWiFiNetworkAgent.connect(false);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Test bringing up unvalidated cellular
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(false);
+ shortSleep();
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Test cellular disconnect.
+ mCellNetworkAgent.disconnect();
+ shortSleep();
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Test bringing up validated cellular
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ cv = waitForConnectivityBroadcasts(2);
+ mCellNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ // Test cellular disconnect.
+ cv = waitForConnectivityBroadcasts(2);
+ mCellNetworkAgent.disconnect();
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Test WiFi disconnect.
+ cv = waitForConnectivityBroadcasts(1);
+ mWiFiNetworkAgent.disconnect();
+ waitFor(cv);
+ verifyNoNetwork();
+ }
+
+ @LargeTest
+ public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception {
+ // Test bringing up unvalidated cellular.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mCellNetworkAgent.connect(false);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ // Test bringing up unvalidated WiFi.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ cv = waitForConnectivityBroadcasts(2);
+ mWiFiNetworkAgent.connect(false);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Test WiFi disconnect.
+ cv = waitForConnectivityBroadcasts(2);
+ mWiFiNetworkAgent.disconnect();
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ // Test cellular disconnect.
+ cv = waitForConnectivityBroadcasts(1);
+ mCellNetworkAgent.disconnect();
+ waitFor(cv);
+ verifyNoNetwork();
+ }
+
+ @LargeTest
+ public void testUnlingeringDoesNotValidate() throws Exception {
+ // Test bringing up unvalidated WiFi.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mWiFiNetworkAgent.connect(false);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ // Test bringing up validated cellular.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ cv = waitForConnectivityBroadcasts(2);
+ mCellNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ // Test cellular disconnect.
+ cv = waitForConnectivityBroadcasts(2);
+ mCellNetworkAgent.disconnect();
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Unlingering a network should not cause it to be marked as validated.
+ assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ }
+
+ @LargeTest
+ public void testCellularOutscoresWeakWifi() throws Exception {
+ // Test bringing up validated cellular.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mCellNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ // Test bringing up validated WiFi.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ cv = waitForConnectivityBroadcasts(2);
+ mWiFiNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Test WiFi getting really weak.
+ cv = waitForConnectivityBroadcasts(2);
+ mWiFiNetworkAgent.adjustScore(-11);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ // Test WiFi restoring signal strength.
+ cv = waitForConnectivityBroadcasts(2);
+ mWiFiNetworkAgent.adjustScore(11);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ mCellNetworkAgent.disconnect();
+ mWiFiNetworkAgent.disconnect();
+ }
+
+ @LargeTest
+ public void testReapingNetwork() throws Exception {
+ // Test bringing up WiFi without NET_CAPABILITY_INTERNET.
+ // Expect it to be torn down immediately because it satisfies no requests.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ ConditionVariable cv = mWiFiNetworkAgent.getDisconnectedCV();
+ mWiFiNetworkAgent.connectWithoutInternet();
+ waitFor(cv);
+ // Test bringing up cellular without NET_CAPABILITY_INTERNET.
+ // Expect it to be torn down immediately because it satisfies no requests.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ cv = mCellNetworkAgent.getDisconnectedCV();
+ mCellNetworkAgent.connectWithoutInternet();
+ waitFor(cv);
+ // Test bringing up validated WiFi.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ cv = waitForConnectivityBroadcasts(1);
+ mWiFiNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Test bringing up unvalidated cellular.
+ // Expect it to be torn down because it could never be the highest scoring network
+ // satisfying the default request even if it validated.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ cv = mCellNetworkAgent.getDisconnectedCV();
+ mCellNetworkAgent.connect(false);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ cv = mWiFiNetworkAgent.getDisconnectedCV();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(cv);
+ }
+
+ @LargeTest
+ public void testCellularFallback() throws Exception {
+ // Test bringing up validated cellular.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mCellNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ // Test bringing up validated WiFi.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ cv = waitForConnectivityBroadcasts(2);
+ mWiFiNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Reevaluate WiFi (it'll instantly fail DNS).
+ cv = waitForConnectivityBroadcasts(2);
+ assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ mCm.reportBadNetwork(mWiFiNetworkAgent.getNetwork());
+ // Should quickly fall back to Cellular.
+ waitFor(cv);
+ assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ // Reevaluate cellular (it'll instantly fail DNS).
+ cv = waitForConnectivityBroadcasts(2);
+ assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ mCm.reportBadNetwork(mCellNetworkAgent.getNetwork());
+ // Should quickly fall back to WiFi.
+ waitFor(cv);
+ assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ mCellNetworkAgent.disconnect();
+ mWiFiNetworkAgent.disconnect();
+ }
+
+ @LargeTest
+ public void testWiFiFallback() throws Exception {
+ // Test bringing up unvalidated WiFi.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mWiFiNetworkAgent.connect(false);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Test bringing up validated cellular.
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ cv = waitForConnectivityBroadcasts(2);
+ mCellNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ // Reevaluate cellular (it'll instantly fail DNS).
+ cv = waitForConnectivityBroadcasts(2);
+ assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ mCm.reportBadNetwork(mCellNetworkAgent.getNetwork());
+ // Should quickly fall back to WiFi.
+ waitFor(cv);
+ assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability(
+ NET_CAPABILITY_VALIDATED));
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ mCellNetworkAgent.disconnect();
+ mWiFiNetworkAgent.disconnect();
+ }
+
+ enum CallbackState {
+ NONE,
+ AVAILABLE,
+ LOSING,
+ LOST
+ }
+
+ private class TestNetworkCallback extends NetworkCallback {
+ private final ConditionVariable mConditionVariable = new ConditionVariable();
+ private CallbackState mLastCallback = CallbackState.NONE;
+
+ public void onAvailable(Network network) {
+ assertEquals(CallbackState.NONE, mLastCallback);
+ mLastCallback = CallbackState.AVAILABLE;
+ mConditionVariable.open();
+ }
+
+ public void onLosing(Network network, int maxMsToLive) {
+ assertEquals(CallbackState.NONE, mLastCallback);
+ mLastCallback = CallbackState.LOSING;
+ mConditionVariable.open();
+ }
+
+ public void onLost(Network network) {
+ assertEquals(CallbackState.NONE, mLastCallback);
+ mLastCallback = CallbackState.LOST;
+ mConditionVariable.open();
+ }
+
+ ConditionVariable getConditionVariable() {
+ mLastCallback = CallbackState.NONE;
+ mConditionVariable.close();
+ return mConditionVariable;
+ }
+
+ CallbackState getLastCallback() {
+ return mLastCallback;
+ }
+ }
+
+ @LargeTest
+ public void testStateChangeNetworkCallbacks() throws Exception {
+ final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
+ final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
+ final NetworkRequest wifiRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI).build();
+ final NetworkRequest cellRequest = new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
+ mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
+
+ // Test unvalidated networks
+ ConditionVariable cellCv = cellNetworkCallback.getConditionVariable();
+ ConditionVariable wifiCv = wifiNetworkCallback.getConditionVariable();
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(false);
+ waitFor(cellCv);
+ assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback());
+ assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ waitFor(cv);
+
+ cellCv = cellNetworkCallback.getConditionVariable();
+ wifiCv = wifiNetworkCallback.getConditionVariable();
+ // This should not trigger spurious onAvailable() callbacks, b/21762680.
+ mCellNetworkAgent.adjustScore(-1);
+ shortSleep();
+ assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+ assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ cellCv = cellNetworkCallback.getConditionVariable();
+ wifiCv = wifiNetworkCallback.getConditionVariable();
+ cv = waitForConnectivityBroadcasts(2);
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+ waitFor(wifiCv);
+ assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback());
+ assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+ waitFor(cv);
+
+ cellCv = cellNetworkCallback.getConditionVariable();
+ wifiCv = wifiNetworkCallback.getConditionVariable();
+ cv = waitForConnectivityBroadcasts(2);
+ mWiFiNetworkAgent.disconnect();
+ waitFor(wifiCv);
+ assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback());
+ assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+ waitFor(cv);
+
+ cellCv = cellNetworkCallback.getConditionVariable();
+ wifiCv = wifiNetworkCallback.getConditionVariable();
+ cv = waitForConnectivityBroadcasts(1);
+ mCellNetworkAgent.disconnect();
+ waitFor(cellCv);
+ assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback());
+ assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+ waitFor(cv);
+
+ // Test validated networks
+
+ cellCv = cellNetworkCallback.getConditionVariable();
+ wifiCv = wifiNetworkCallback.getConditionVariable();
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.connect(true);
+ waitFor(cellCv);
+ assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback());
+ assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ cellCv = cellNetworkCallback.getConditionVariable();
+ wifiCv = wifiNetworkCallback.getConditionVariable();
+ // This should not trigger spurious onAvailable() callbacks, b/21762680.
+ mCellNetworkAgent.adjustScore(-1);
+ shortSleep();
+ assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+ assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+ assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ cellCv = cellNetworkCallback.getConditionVariable();
+ wifiCv = wifiNetworkCallback.getConditionVariable();
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(true);
+ waitFor(wifiCv);
+ assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback());
+ waitFor(cellCv);
+ assertEquals(CallbackState.LOSING, cellNetworkCallback.getLastCallback());
+ assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
+
+ cellCv = cellNetworkCallback.getConditionVariable();
+ wifiCv = wifiNetworkCallback.getConditionVariable();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(wifiCv);
+ assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback());
+ assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback());
+
+ cellCv = cellNetworkCallback.getConditionVariable();
+ wifiCv = wifiNetworkCallback.getConditionVariable();
+ mCellNetworkAgent.disconnect();
+ waitFor(cellCv);
+ assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback());
+ assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback());
+ }
+
+ private void tryNetworkFactoryRequests(int capability) throws Exception {
+ // Verify NOT_RESTRICTED is set appropriately
+ final NetworkCapabilities nc = new NetworkRequest.Builder().addCapability(capability)
+ .build().networkCapabilities;
+ if (capability == NET_CAPABILITY_CBS || capability == NET_CAPABILITY_DUN ||
+ capability == NET_CAPABILITY_EIMS || capability == NET_CAPABILITY_FOTA ||
+ capability == NET_CAPABILITY_IA || capability == NET_CAPABILITY_IMS ||
+ capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP) {
+ assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ } else {
+ assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED));
+ }
+
+ NetworkCapabilities filter = new NetworkCapabilities();
+ filter.addCapability(capability);
+ final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests");
+ handlerThread.start();
+ final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(),
+ mServiceContext, "testFactory", filter);
+ testFactory.setScoreFilter(40);
+ ConditionVariable cv = testFactory.getNetworkStartedCV();
+ testFactory.register();
+ int expectedRequestCount = 1;
+ NetworkCallback networkCallback = null;
+ // For non-INTERNET capabilities we cannot rely on the default request being present, so
+ // add one.
+ if (capability != NET_CAPABILITY_INTERNET) {
+ testFactory.waitForNetworkRequests(1);
+ assertFalse(testFactory.getMyStartRequested());
+ NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build();
+ networkCallback = new NetworkCallback();
+ mCm.requestNetwork(request, networkCallback);
+ expectedRequestCount++;
+ }
+ waitFor(cv);
+ assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+ assertTrue(testFactory.getMyStartRequested());
+
+ // Now bring in a higher scored network.
+ MockNetworkAgent testAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ // Rather than create a validated network which complicates things by registering it's
+ // own NetworkRequest during startup, just bump up the score to cancel out the
+ // unvalidated penalty.
+ testAgent.adjustScore(40);
+ cv = testFactory.getNetworkStoppedCV();
+ testAgent.connect(false);
+ testAgent.addCapability(capability);
+ waitFor(cv);
+ assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+ assertFalse(testFactory.getMyStartRequested());
+
+ // Bring in a bunch of requests.
+ ConnectivityManager.NetworkCallback[] networkCallbacks =
+ new ConnectivityManager.NetworkCallback[10];
+ for (int i = 0; i< networkCallbacks.length; i++) {
+ networkCallbacks[i] = new ConnectivityManager.NetworkCallback();
+ NetworkRequest.Builder builder = new NetworkRequest.Builder();
+ builder.addCapability(capability);
+ mCm.requestNetwork(builder.build(), networkCallbacks[i]);
+ }
+ testFactory.waitForNetworkRequests(10 + expectedRequestCount);
+ assertFalse(testFactory.getMyStartRequested());
+
+ // Remove the requests.
+ for (int i = 0; i < networkCallbacks.length; i++) {
+ mCm.unregisterNetworkCallback(networkCallbacks[i]);
+ }
+ testFactory.waitForNetworkRequests(expectedRequestCount);
+ assertFalse(testFactory.getMyStartRequested());
+
+ // Drop the higher scored network.
+ cv = testFactory.getNetworkStartedCV();
+ testAgent.disconnect();
+ waitFor(cv);
+ assertEquals(expectedRequestCount, testFactory.getMyRequestCount());
+ assertTrue(testFactory.getMyStartRequested());
+
+ testFactory.unregister();
+ if (networkCallback != null) mCm.unregisterNetworkCallback(networkCallback);
+ handlerThread.quit();
+ }
+
+ @LargeTest
+ public void testNetworkFactoryRequests() throws Exception {
+ tryNetworkFactoryRequests(NET_CAPABILITY_MMS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_SUPL);
+ tryNetworkFactoryRequests(NET_CAPABILITY_DUN);
+ tryNetworkFactoryRequests(NET_CAPABILITY_FOTA);
+ tryNetworkFactoryRequests(NET_CAPABILITY_IMS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_CBS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_WIFI_P2P);
+ tryNetworkFactoryRequests(NET_CAPABILITY_IA);
+ tryNetworkFactoryRequests(NET_CAPABILITY_RCS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_XCAP);
+ tryNetworkFactoryRequests(NET_CAPABILITY_EIMS);
+ tryNetworkFactoryRequests(NET_CAPABILITY_NOT_METERED);
+ tryNetworkFactoryRequests(NET_CAPABILITY_INTERNET);
+ tryNetworkFactoryRequests(NET_CAPABILITY_TRUSTED);
+ tryNetworkFactoryRequests(NET_CAPABILITY_NOT_VPN);
+ // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed.
+ }
+
+ @LargeTest
+ public void testNoMutableNetworkRequests() throws Exception {
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent("a"), 0);
+ NetworkRequest.Builder builder = new NetworkRequest.Builder();
+ builder.addCapability(NET_CAPABILITY_VALIDATED);
+ try {
+ mCm.requestNetwork(builder.build(), new NetworkCallback());
+ fail();
+ } catch (IllegalArgumentException expected) {}
+ try {
+ mCm.requestNetwork(builder.build(), pendingIntent);
+ fail();
+ } catch (IllegalArgumentException expected) {}
+ builder = new NetworkRequest.Builder();
+ builder.addCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
+ try {
+ mCm.requestNetwork(builder.build(), new NetworkCallback());
+ fail();
+ } catch (IllegalArgumentException expected) {}
+ try {
+ mCm.requestNetwork(builder.build(), pendingIntent);
+ fail();
+ } catch (IllegalArgumentException expected) {}
+ }
+
+ @LargeTest
+ public void testMMSonWiFi() throws Exception {
+ // Test bringing up cellular without MMS NetworkRequest gets reaped
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
+ ConditionVariable cv = mCellNetworkAgent.getDisconnectedCV();
+ mCellNetworkAgent.connectWithoutInternet();
+ waitFor(cv);
+ waitFor(new Criteria() {
+ public boolean get() { return mCm.getAllNetworks().length == 0; } });
+ verifyNoNetwork();
+ // Test bringing up validated WiFi.
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ cv = waitForConnectivityBroadcasts(1);
+ mWiFiNetworkAgent.connect(true);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Register MMS NetworkRequest
+ NetworkRequest.Builder builder = new NetworkRequest.Builder();
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ mCm.requestNetwork(builder.build(), networkCallback);
+ // Test bringing up unvalidated cellular with MMS
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS);
+ cv = networkCallback.getConditionVariable();
+ mCellNetworkAgent.connectWithoutInternet();
+ waitFor(cv);
+ assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback());
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ // Test releasing NetworkRequest disconnects cellular with MMS
+ cv = mCellNetworkAgent.getDisconnectedCV();
+ mCm.unregisterNetworkCallback(networkCallback);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_WIFI);
+ }
+
+ @LargeTest
+ public void testMMSonCell() throws Exception {
+ // Test bringing up cellular without MMS
+ mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ ConditionVariable cv = waitForConnectivityBroadcasts(1);
+ mCellNetworkAgent.connect(false);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ // Register MMS NetworkRequest
+ NetworkRequest.Builder builder = new NetworkRequest.Builder();
+ builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS);
+ final TestNetworkCallback networkCallback = new TestNetworkCallback();
+ mCm.requestNetwork(builder.build(), networkCallback);
+ // Test bringing up MMS cellular network
+ cv = networkCallback.getConditionVariable();
+ MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
+ mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS);
+ mmsNetworkAgent.connectWithoutInternet();
+ waitFor(cv);
+ assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback());
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent
+ cv = mmsNetworkAgent.getDisconnectedCV();
+ mCm.unregisterNetworkCallback(networkCallback);
+ waitFor(cv);
+ verifyActiveNetwork(TRANSPORT_CELLULAR);
+ }
+
+ @LargeTest
+ public void testCaptivePortal() {
+ final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+ final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+ mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+ final TestNetworkCallback validatedCallback = new TestNetworkCallback();
+ final NetworkRequest validatedRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_VALIDATED).build();
+ mCm.registerNetworkCallback(validatedRequest, validatedCallback);
+ ConditionVariable validatedCv = validatedCallback.getConditionVariable();
+
+ // Bring up a network with a captive portal.
+ // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
+ ConditionVariable cv = captivePortalCallback.getConditionVariable();
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connectWithCaptivePortal();
+ waitFor(cv);
+ assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback());
+
+ // Take down network.
+ // Expect onLost callback.
+ cv = captivePortalCallback.getConditionVariable();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(cv);
+ assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback());
+
+ // Bring up a network with a captive portal.
+ // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
+ cv = captivePortalCallback.getConditionVariable();
+ mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connectWithCaptivePortal();
+ waitFor(cv);
+ assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback());
+
+ // Make captive portal disappear then revalidate.
+ // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
+ cv = captivePortalCallback.getConditionVariable();
+ mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204;
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+ waitFor(cv);
+ assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback());
+
+ // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
+ waitFor(validatedCv);
+ assertEquals(CallbackState.AVAILABLE, validatedCallback.getLastCallback());
+
+ // Break network connectivity.
+ // Expect NET_CAPABILITY_VALIDATED onLost callback.
+ validatedCv = validatedCallback.getConditionVariable();
+ mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500;
+ mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
+ waitFor(validatedCv);
+ assertEquals(CallbackState.LOST, validatedCallback.getLastCallback());
+ }
+
// @Override
// public void tearDown() throws Exception {
// super.tearDown();
@@ -157,9 +1200,9 @@
// mMobile.link.addRoute(MOBILE_ROUTE_V6);
// mMobile.doReturnDefaults();
//
-// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+// cv = waitForConnectivityBroadcasts(1);
// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
-// nextConnBroadcast.get();
+// waitFor(cv);
//
// // verify that both routes were added
// int mobileNetId = mMobile.tracker.getNetwork().netId;
@@ -177,9 +1220,9 @@
// mMobile.link.addRoute(MOBILE_ROUTE_V6);
// mMobile.doReturnDefaults();
//
-// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+// cv = waitForConnectivityBroadcasts(1);
// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
-// nextConnBroadcast.get();
+// waitFor(cv);
//
// reset(mNetManager);
//
@@ -193,9 +1236,9 @@
// // expect that mobile will be torn down
// doReturn(true).when(mMobile.tracker).teardown();
//
-// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+// cv = waitForConnectivityBroadcasts(1);
// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget();
-// nextConnBroadcast.get();
+// waitFor(cv);
//
// // verify that wifi routes added, and teardown requested
// int wifiNetId = mWifi.tracker.getNetwork().netId;
@@ -212,9 +1255,9 @@
// mMobile.link.clear();
// mMobile.doReturnDefaults();
//
-// nextConnBroadcast = mServiceContext.nextBroadcastIntent(CONNECTIVITY_ACTION_IMMEDIATE);
+// cv = waitForConnectivityBroadcasts(1);
// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget();
-// nextConnBroadcast.get();
+// waitFor(cv);
//
// verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4));
// verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6));
diff --git a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
index 7383478..90b4f43 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkStatsServiceTest.java
@@ -18,7 +18,7 @@
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.EXTRA_UID;
-import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.ConnectivityManager.TYPE_WIMAX;
@@ -39,6 +39,7 @@
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_POLL;
+import static org.easymock.EasyMock.anyInt;
import static org.easymock.EasyMock.anyLong;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
@@ -191,7 +192,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
// verify service has empty history for wifi
assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -245,7 +246,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
// verify service has empty history for wifi
assertNetworkTotal(sTemplateWifi, 0L, 0L, 0L, 0L, 0);
@@ -336,7 +337,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// modify some number on wifi, and trigger poll event
@@ -388,7 +389,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some traffic on first network
@@ -430,7 +431,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
verifyAndReset();
@@ -476,7 +477,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some traffic
@@ -545,7 +546,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some traffic
@@ -579,7 +580,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
mServiceContext.sendBroadcast(new Intent(ACTION_NETWORK_STATS_POLL));
verifyAndReset();
@@ -616,7 +617,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some traffic for two apps
@@ -682,7 +683,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some initial traffic
@@ -747,7 +748,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some tethering traffic
@@ -787,7 +788,7 @@
expectNetworkStatsPoll();
replay();
- mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION_IMMEDIATE));
+ mServiceContext.sendBroadcast(new Intent(CONNECTIVITY_ACTION));
verifyAndReset();
// create some traffic, but only for DEV, and across 1.5 buckets
@@ -879,7 +880,7 @@
expectLastCall().anyTimes();
mAlarmManager.set(eq(AlarmManager.ELAPSED_REALTIME), anyLong(), anyLong(), anyLong(),
- isA(PendingIntent.class), isA(WorkSource.class),
+ anyInt(), isA(PendingIntent.class), isA(WorkSource.class),
isA(AlarmManager.AlarmClockInfo.class));
expectLastCall().atLeastOnce();