Merge "Remove needless locking of mRulesLock that caused deadlocks." into lmp-dev
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 7c69a7d..e3cbef5 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1402,6 +1402,20 @@
         return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
     }
 
+    /** {@hide */
+    public static final void enforceTetherChangePermission(Context context) {
+        if (context.getResources().getStringArray(
+                com.android.internal.R.array.config_mobile_hotspot_provision_app).length == 2) {
+            // Have a provisioning app - must only let system apps (which check this app)
+            // turn on tethering
+            context.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.CONNECTIVITY_INTERNAL, "ConnectivityService");
+        } else {
+            context.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.CHANGE_NETWORK_STATE, "ConnectivityService");
+        }
+    }
+
     /**
      * Get the set of tetherable, available interfaces.  This list is limited by
      * device configuration and current interface existence.
diff --git a/core/java/android/net/IpPrefix.java b/core/java/android/net/IpPrefix.java
index f1fa3eb..b268986 100644
--- a/core/java/android/net/IpPrefix.java
+++ b/core/java/android/net/IpPrefix.java
@@ -172,7 +172,7 @@
     /**
      * 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::"}.
+     * @return a string such as {@code "192.0.2.0/24"} or {@code "2001:db8:1:2::/64"}.
      */
     public String toString() {
         try {
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index 3d6a132..662c576 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -381,7 +381,8 @@
         return new RouteInfo(
             route.getDestination(),
             route.getGateway(),
-            mIfaceName);
+            mIfaceName,
+            route.getType());
     }
 
     /**
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 80e5b91..b83198d 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -120,6 +120,25 @@
      */
     public static final int EVENT_UNBLOCK_ADDRESS_FAMILY = BASE + 8;
 
+    /**
+     * Sent by ConnectivitySerice 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.
+     *
+     * arg1 = either {@code VALID_NETWORK} or {@code INVALID_NETWORK}
+     */
+    public static final int CMD_REPORT_NETWORK_STATUS = BASE + 9;
+
+    public static final int VALID_NETWORK = 1;
+    public static final int INVALID_NETWORK = 2;
+
+     /**
+     * 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.
+     */
+    public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 10;
+
     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);
@@ -181,6 +200,14 @@
                 log("Unhandled Message " + msg);
                 break;
             }
+            case CMD_REPORT_NETWORK_STATUS: {
+                if (VDBG) {
+                    log("CMD_REPORT_NETWORK_STATUS(" +
+                            (msg.arg1 == VALID_NETWORK ? "VALID)" : "INVALID)"));
+                }
+                networkStatus(msg.arg1);
+                break;
+            }
         }
     }
 
@@ -261,6 +288,15 @@
     }
 
     /**
+     * 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.
+     */
+    public void explicitlySelected() {
+        queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED, 0);
+    }
+
+    /**
      * Called when ConnectivityService has indicated they no longer want this network.
      * The parent factory should (previously) have received indication of the change
      * as well, either canceling NetworkRequests or altering their score such that this
@@ -268,6 +304,24 @@
      */
     abstract protected void unwanted();
 
+    /**
+     * Called when the system determines the usefulness of this network.
+     *
+     * Networks claiming internet connectivity will have their internet
+     * connectivity verified.
+     *
+     * Currently there are two possible values:
+     * {@code VALID_NETWORK} if the system is happy with the connection,
+     * {@code INVALID_NETWORK} if the system is not happy.
+     * TODO - add indications of captive portal-ness and related success/failure,
+     * ie, CAPTIVE_SUCCESS_NETWORK, CAPTIVE_NETWORK for successful login and detection
+     *
+     * This may be called multiple times as the network status changes and may
+     * generate false negatives if we lose ip connectivity before the link is torn down.
+     */
+    protected void networkStatus(int status) {
+    }
+
     protected void log(String s) {
         Log.d(LOG_TAG, "NetworkAgent: " + s);
     }
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 7664c95..393637e 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -411,8 +411,7 @@
 
     /**
      * Report the extra information about the network state, if any was
-     * provided by the lower networking layers.,
-     * if one is available.
+     * provided by the lower networking layers.
      * @return the extra information, or null if not available
      */
     public String getExtraInfo() {
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index 34f6cf4..5d2a43d 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -25,12 +25,32 @@
  * @hide
  */
 public class NetworkMisc implements Parcelable {
+
     /**
      * If the {@link Network} is a VPN, whether apps are allowed to bypass the VPN. This is set by
      * a {@link VpnService} and used by {@link ConnectivityService} when creating a VPN.
      */
     public boolean allowBypass;
 
+    /**
+     * Set if the network was manually/explicitly connected to by the user either from settings
+     * or a 3rd party app.  For example, turning on cell data is not explicit but tapping on a wifi
+     * ap in the wifi settings to trigger a connection is explicit.  A 3rd party app asking to
+     * connect to a particular access point is also explicit, though this may change in the future
+     * as we want apps to use the multinetwork apis.
+     */
+    public boolean explicitlySelected;
+
+    public NetworkMisc() {
+    }
+
+    public NetworkMisc(NetworkMisc nm) {
+        if (nm != null) {
+            allowBypass = nm.allowBypass;
+            explicitlySelected = nm.explicitlySelected;
+        }
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -39,6 +59,7 @@
     @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(allowBypass ? 1 : 0);
+        out.writeInt(explicitlySelected ? 1 : 0);
     }
 
     public static final Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
@@ -46,6 +67,7 @@
         public NetworkMisc createFromParcel(Parcel in) {
             NetworkMisc networkMisc = new NetworkMisc();
             networkMisc.allowBypass = in.readInt() != 0;
+            networkMisc.explicitlySelected = in.readInt() != 0;
             return networkMisc;
         }
 
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index a4ec80c..cfd20a0 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -62,6 +62,23 @@
      */
     private final String mInterface;
 
+
+    /** Unicast route. @hide */
+    public static final int RTN_UNICAST = 1;
+
+    /** Unreachable route. @hide */
+    public static final int RTN_UNREACHABLE = 7;
+
+    /** Throw route. @hide */
+    public static final int RTN_THROW = 9;
+
+    /**
+     * The type of this route; one of the RTN_xxx constants above.
+     */
+    private final int mType;
+
+    // Derived data members.
+    // TODO: remove these.
     private final boolean mIsHost;
     private final boolean mHasGateway;
 
@@ -82,7 +99,26 @@
      *
      * @hide
      */
-    public RouteInfo(IpPrefix destination, InetAddress gateway, String iface) {
+    public RouteInfo(IpPrefix destination, InetAddress gateway, String iface, int type) {
+        switch (type) {
+            case RTN_UNICAST:
+            case RTN_UNREACHABLE:
+            case RTN_THROW:
+                // TODO: It would be nice to ensure that route types that don't have nexthops or
+                // interfaces, such as unreachable or throw, can't be created if an interface or
+                // a gateway is specified. This is a bit too complicated to do at the moment
+                // because:
+                //
+                // - LinkProperties sets the interface on routes added to it, and modifies the
+                //   interfaces of all the routes when its interface name changes.
+                // - Even when the gateway is null, we store a non-null gateway here.
+                //
+                // For now, we just rely on the code that sets routes to do things properly.
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown route type " + type);
+        }
+
         if (destination == null) {
             if (gateway != null) {
                 if (gateway instanceof Inet4Address) {
@@ -117,10 +153,18 @@
         mDestination = destination;  // IpPrefix objects are immutable.
         mGateway = gateway;          // InetAddress objects are immutable.
         mInterface = iface;          // Strings are immutable.
+        mType = type;
         mIsHost = isHost();
     }
 
     /**
+     *  @hide
+     */
+    public RouteInfo(IpPrefix destination, InetAddress gateway, String iface) {
+        this(destination, gateway, iface, RTN_UNICAST);
+    }
+
+    /**
      * @hide
      */
     public RouteInfo(LinkAddress destination, InetAddress gateway, String iface) {
@@ -150,6 +194,8 @@
 
     /**
      * @hide
+     *
+     * TODO: Remove this.
      */
     public RouteInfo(LinkAddress destination, InetAddress gateway) {
         this(destination, gateway, null);
@@ -188,6 +234,13 @@
     /**
      * @hide
      */
+    public RouteInfo(IpPrefix destination, int type) {
+        this(destination, null, null, type);
+    }
+
+    /**
+     * @hide
+     */
     public static RouteInfo makeHostRoute(InetAddress host, String iface) {
         return makeHostRoute(host, null, iface);
     }
@@ -249,12 +302,23 @@
     }
 
     /**
+     * Retrieves the type of this route.
+     *
+     * @return The type of this route; one of the {@code RTN_xxx} constants defined in this class.
+     *
+     * @hide
+     */
+    public int getType() {
+        return mType;
+    }
+
+    /**
      * Indicates if this route is a default route (ie, has no destination specified).
      *
      * @return {@code true} if the destination has a prefix length of 0.
      */
     public boolean isDefaultRoute() {
-        return mDestination.getPrefixLength() == 0;
+        return mType == RTN_UNICAST && mDestination.getPrefixLength() == 0;
     }
 
     /**
@@ -345,9 +409,18 @@
     public String toString() {
         String val = "";
         if (mDestination != null) val = mDestination.toString();
-        val += " ->";
-        if (mGateway != null) val += " " + mGateway.getHostAddress();
-        if (mInterface != null) val += " " + mInterface;
+        if (mType == RTN_UNREACHABLE) {
+            val += " unreachable";
+        } else if (mType == RTN_THROW) {
+            val += " throw";
+        } else {
+            val += " ->";
+            if (mGateway != null) val += " " + mGateway.getHostAddress();
+            if (mInterface != null) val += " " + mInterface;
+            if (mType != RTN_UNICAST) {
+                val += " unknown type " + mType;
+            }
+        }
         return val;
     }
 
@@ -364,7 +437,8 @@
 
         return Objects.equals(mDestination, target.getDestination()) &&
                 Objects.equals(mGateway, target.getGateway()) &&
-                Objects.equals(mInterface, target.getInterface());
+                Objects.equals(mInterface, target.getInterface()) &&
+                mType == target.getType();
     }
 
     /**
@@ -373,7 +447,8 @@
     public int hashCode() {
         return (mDestination.hashCode() * 41)
                 + (mGateway == null ? 0 :mGateway.hashCode() * 47)
-                + (mInterface == null ? 0 :mInterface.hashCode() * 67);
+                + (mInterface == null ? 0 :mInterface.hashCode() * 67)
+                + (mType * 71);
     }
 
     /**
@@ -391,6 +466,7 @@
         byte[] gatewayBytes = (mGateway == null) ? null : mGateway.getAddress();
         dest.writeByteArray(gatewayBytes);
         dest.writeString(mInterface);
+        dest.writeInt(mType);
     }
 
     /**
@@ -408,8 +484,9 @@
             } catch (UnknownHostException e) {}
 
             String iface = in.readString();
+            int type = in.readInt();
 
-            return new RouteInfo(dest, gateway, iface);
+            return new RouteInfo(dest, gateway, iface, type);
         }
 
         public RouteInfo[] newArray(int size) {
diff --git a/core/tests/coretests/src/android/net/NetworkStatsTest.java b/core/tests/coretests/src/android/net/NetworkStatsTest.java
index 6331964..9ee4e20 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsTest.java
@@ -310,6 +310,16 @@
         assertEquals(128L + 512L, clone.getTotalBytes());
     }
 
+    public void testAddWhenEmpty() throws Exception {
+        final NetworkStats red = new NetworkStats(TEST_START, -1);
+        final NetworkStats blue = new NetworkStats(TEST_START, 5)
+                .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+                .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+
+        // We're mostly checking that we don't crash
+        red.combineAllValues(blue);
+    }
+
     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/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ef9bf51..5c43f6b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -1353,13 +1353,6 @@
                 "ConnectivityService");
     }
 
-    // TODO Make this a special check when it goes public
-    private void enforceTetherChangePermission() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CHANGE_NETWORK_STATE,
-                "ConnectivityService");
-    }
-
     private void enforceTetherAccessPermission() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.ACCESS_NETWORK_STATE,
@@ -1921,6 +1914,15 @@
                     }
                     break;
                 }
+                case NetworkAgent.EVENT_SET_EXPLICITLY_SELECTED: {
+                    NetworkAgentInfo nai = mNetworkAgentInfos.get(msg.replyTo);
+                    if (nai == null) {
+                        loge("EVENT_SET_EXPLICITLY_SELECTED from unknown NetworkAgent");
+                        break;
+                    }
+                    nai.networkMisc.explicitlySelected = true;
+                    break;
+                }
                 case NetworkMonitor.EVENT_NETWORK_TESTED: {
                     NetworkAgentInfo nai = (NetworkAgentInfo)msg.obj;
                     if (isLiveNetworkAgent(nai, "EVENT_NETWORK_VALIDATED")) {
@@ -1931,6 +1933,11 @@
                             rematchNetworkAndRequests(nai);
                         }
                         updateInetCondition(nai, valid);
+                        // Let the NetworkAgent know the state of its network
+                        nai.asyncChannel.sendMessage(
+                                android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS,
+                                (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
+                                0, null);
                     }
                     break;
                 }
@@ -2371,8 +2378,7 @@
 
     // javadoc from interface
     public int tether(String iface) {
-        enforceTetherChangePermission();
-
+        ConnectivityManager.enforceTetherChangePermission(mContext);
         if (isTetheringSupported()) {
             return mTethering.tether(iface);
         } else {
@@ -2382,7 +2388,7 @@
 
     // javadoc from interface
     public int untether(String iface) {
-        enforceTetherChangePermission();
+        ConnectivityManager.enforceTetherChangePermission(mContext);
 
         if (isTetheringSupported()) {
             return mTethering.untether(iface);
@@ -2431,7 +2437,7 @@
     }
 
     public int setUsbTethering(boolean enable) {
-        enforceTetherChangePermission();
+        ConnectivityManager.enforceTetherChangePermission(mContext);
         if (isTetheringSupported()) {
             return mTethering.setUsbTethering(enable);
         } else {
@@ -2513,6 +2519,7 @@
             nai = mNetworkForNetId.get(network.netId);
         }
         if (nai == null) return;
+        if (DBG) log("reportBadNetwork(" + nai.name() + ") by " + uid);
         synchronized (nai) {
             if (isNetworkBlocked(nai, uid)) return;
 
@@ -4241,7 +4248,7 @@
         NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
             new NetworkInfo(networkInfo), new LinkProperties(linkProperties),
             new NetworkCapabilities(networkCapabilities), currentScore, mContext, mTrackerHandler,
-            networkMisc);
+            new NetworkMisc(networkMisc));
         synchronized (this) {
             nai.networkMonitor.systemReady = mSystemReady;
         }
@@ -4543,8 +4550,10 @@
         for (NetworkRequestInfo nri : mNetworkRequests.values()) {
             NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
             if (newNetwork == currentNetwork) {
-                if (DBG) log("Network " + newNetwork.name() + " was already satisfying" +
-                              " request " + nri.request.requestId + ". No change.");
+                if (DBG) {
+                    log("Network " + newNetwork.name() + " was already satisfying" +
+                            " request " + nri.request.requestId + ". No change.");
+                }
                 keep = true;
                 continue;
             }
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 957d705..15ffc0d 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -53,6 +53,9 @@
     // 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;
+
     // The list of NetworkRequests being satisfied by this Network.
     public final SparseArray<NetworkRequest> networkRequests = new SparseArray<NetworkRequest>();
     public final ArrayList<NetworkRequest> networkLingered = new ArrayList<NetworkRequest>();
@@ -95,9 +98,10 @@
         int score = currentScore;
 
         if (!validated) score -= UNVALIDATED_SCORE_PENALTY;
-
         if (score < 0) score = 0;
 
+        if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE;
+
         return score;
     }
 
@@ -110,7 +114,8 @@
                 network + "}  lp{" +
                 linkProperties + "}  nc{" +
                 networkCapabilities + "}  Score{" + getCurrentScore() + "} " +
-                "validated{" + validated + "} created{" + created + "} }";
+                "validated{" + validated + "} created{" + created + "} " +
+                "explicitlySelected{" + networkMisc.explicitlySelected + "} }";
     }
 
     public String name() {