Merge changes from topic 'no_internet'

* changes:
  Add a noInternetAccessExpected boolean to WifiConfiguration.
  Prompt if a network without an Internet connection is selected
  Give SystemUI the OVERRIDE_WIFI_CONFIG permission.
  Add an UNKNOWN_UID constant to WifiConfiguration.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index da2c5e0..55e39b1 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -286,6 +286,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
      */
@@ -2455,6 +2463,29 @@
     }
 
     /**
+     * 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
      */
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 3c09978..1aa9c45 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -156,6 +156,8 @@
 
     void releaseNetworkRequest(in NetworkRequest networkRequest);
 
+    void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
+
     int getRestoreDefaultNetworkDelay(int networkType);
 
     boolean addVpnAddress(String address, int prefixLength);
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index 24aaf0d..95ceb2a 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -104,7 +104,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.
      *
@@ -119,9 +119,21 @@
      * 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;
+
     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);
@@ -191,6 +203,9 @@
                 networkStatus(msg.arg1);
                 break;
             }
+            case CMD_SAVE_ACCEPT_UNVALIDATED: {
+                saveAcceptUnvalidated(msg.arg1 != 0);
+            }
         }
     }
 
@@ -258,10 +273,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);
     }
 
     /**
@@ -290,6 +311,16 @@
     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) {
+    }
+
     protected void log(String s) {
         Log.d(LOG_TAG, "NetworkAgent: " + s);
     }
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/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 012c84c..a8a4baa 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -53,6 +53,7 @@
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
     <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
     <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
     <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1ac1c8a..b5796c9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -161,6 +161,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;
@@ -324,6 +328,19 @@
      */
     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;
 
     /** Handler used for internal events. */
     final private InternalHandler mHandler;
@@ -1843,6 +1860,7 @@
                         loge("ERROR: created network explicitly selected.");
                     }
                     nai.networkMisc.explicitlySelected = true;
+                    nai.networkMisc.acceptUnvalidated = (boolean) msg.obj;
                     break;
                 }
                 case NetworkMonitor.EVENT_NETWORK_TESTED: {
@@ -1866,6 +1884,9 @@
                                 android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS,
                                 (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
                                 0, null);
+
+                        // TODO: trigger a NetworkCapabilities update so that the dialog can know
+                        // that the network is now validated and close itself.
                     }
                     break;
                 }
@@ -2227,6 +2248,91 @@
         }
     }
 
+    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. Don't make any changes. There's a
+            // TODO in the dialog code to make it go away if the network validates; once that's
+            // implemented, taking action here will be confusing.
+            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);
+        }
+
+        // TODO: should we also disconnect from the network if accept is false?
+    }
+
+    private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(EVENT_PROMPT_UNVALIDATED, nai.network),
+                PROMPT_UNVALIDATED_DELAY_MS);
+    }
+
+    private void handlePromptUnvalidated(Network 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.
+        if (nai == null || nai.everValidated ||
+                !nai.networkMisc.explicitlySelected || nai.networkMisc.acceptUnvalidated) {
+            return;
+        }
+
+        // TODO: What should we do if we've already switched to this network because we had no
+        // better option? There are two obvious alternatives.
+        //
+        // 1. Decide that there's no point prompting because this is our only usable network.
+        //    However, because we didn't prompt, if later on a validated network comes along, we'll
+        //    either a) silently switch to it - bad if the user wanted to connect to stay on this
+        //    unvalidated network - or b) prompt the user at that later time - bad because the user
+        //    might not understand why they are now being prompted.
+        //
+        // 2. Always prompt the user, even if we have no other network to use. The user could then
+        //    try to find an alternative network to join (remember, if we got here, then the user
+        //    selected this network manually). This is bad because the prompt isn't really very
+        //    useful.
+        //
+        // For now we do #1, but we can revisit that later.
+        if (isDefaultNetwork(nai)) {
+            return;
+        }
+
+        Intent intent = new Intent(ConnectivityManager.ACTION_PROMPT_UNVALIDATED);
+        intent.putExtra(ConnectivityManager.EXTRA_NETWORK, network);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClassName("com.android.settings",
+                "com.android.settings.wifi.WifiNoInternetDialog");
+        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+    }
+
     private class InternalHandler extends Handler {
         public InternalHandler(Looper looper) {
             super(looper);
@@ -2297,6 +2403,14 @@
                     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_SYSTEM_READY: {
                     for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
                         nai.networkMonitor.systemReady = true;
@@ -4099,6 +4213,7 @@
             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) {
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index f3e0bbc..dac0580 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -68,7 +68,10 @@
     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>();
@@ -120,13 +123,18 @@
         // score.  The NetworkScore class would provide a nice place to centralize score constants
         // so they are not scattered about the transports.
 
-        int score = currentScore;
+        // 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;
         if (score < 0) score = 0;
-
-        if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE;
-
         return score;
     }
 
@@ -153,7 +161,9 @@
                 networkCapabilities + "}  Score{" + getCurrentScore() + "}  " +
                 "everValidated{" + everValidated + "}  lastValidated{" + lastValidated + "}  " +
                 "created{" + created + "}  " +
-                "explicitlySelected{" + networkMisc.explicitlySelected + "} }";
+                "explicitlySelected{" + networkMisc.explicitlySelected + "} " +
+                "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} " +
+                "}";
     }
 
     public String name() {
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 6ea0db2..c6f2991 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -194,6 +194,9 @@
     /** @hide */
     public static final int DISABLED_BY_WIFI_MANAGER                        = 5;
 
+    /** @hide */
+    public static final int UNKNOWN_UID = -1;
+
     /**
      * The ID number that the supplicant uses to identify this
      * network configuration entry. This must be passed as an argument
@@ -688,6 +691,14 @@
     }
 
     /**
+     * The WiFi configuration is expected not to have Internet access (e.g., a wireless printer, a
+     * Chromecast hotspot, etc.). This will be set if the user explicitly confirms a connection to
+     * this configuration and selects "don't ask again".
+     * @hide
+     */
+    public boolean noInternetAccessExpected;
+
+    /**
      * @hide
      * Last time we blacklisted the configuration
      */
@@ -1131,7 +1142,10 @@
         if (creatorName != null) sbuf.append(" cname=" + creatorName);
         if (lastUpdateUid != 0) sbuf.append(" luid=" + lastUpdateUid);
         if (lastUpdateName != null) sbuf.append(" lname=" + lastUpdateName);
-        sbuf.append("userApproved=" + userApprovedAsString(userApproved));
+        sbuf.append(" lcuid=" + lastConnectUid);
+        sbuf.append(" userApproved=" + userApprovedAsString(userApproved));
+        sbuf.append(" noInternetAccessExpected=" + noInternetAccessExpected);
+        sbuf.append(" ");
 
         if (this.lastConnected != 0) {
             sbuf.append('\n');
@@ -1542,6 +1556,7 @@
             dirty = source.dirty;
             userApproved = source.userApproved;
             numNoInternetAccessReports = source.numNoInternetAccessReports;
+            noInternetAccessExpected = source.noInternetAccessExpected;
         }
     }
 
@@ -1620,6 +1635,7 @@
         dest.writeInt(autoJoinBailedDueToLowRssi ? 1 : 0);
         dest.writeInt(userApproved);
         dest.writeInt(numNoInternetAccessReports);
+        dest.writeInt(noInternetAccessExpected ? 1 : 0);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -1694,6 +1710,7 @@
                 config.autoJoinBailedDueToLowRssi = in.readInt() != 0;
                 config.userApproved = in.readInt();
                 config.numNoInternetAccessReports = in.readInt();
+                config.noInternetAccessExpected = in.readInt() != 0;
                 return config;
             }