Support "don't ask again" in the avoid bad wifi dialog. am: 165c51c0eb am: aab02df195 am: 63275aba54
am: 3f67cbec46

Change-Id: I60ebe27ec892658c5a051457b13506e59ba33824
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index fd9e60d..c793a6e 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -3241,6 +3241,27 @@
     }
 
     /**
+     * Informs the system to penalize {@code network}'s score when it becomes unvalidated. This is
+     * only meaningful if the system is configured not to penalize such networks, e.g., if the
+     * {@code config_networkAvoidBadWifi} configuration variable is set to 0 and the {@code
+     * NETWORK_AVOID_BAD_WIFI setting is unset}.
+     *
+     * <p>This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}
+     *
+     * @param network The network to accept.
+     *
+     * @hide
+     */
+    public void setAvoidUnvalidated(Network network) {
+        try {
+            mService.setAvoidUnvalidated(network);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * 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 d48c155..4aabda9 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -161,6 +161,7 @@
     void releaseNetworkRequest(in NetworkRequest networkRequest);
 
     void setAcceptUnvalidated(in Network network, boolean accept, boolean always);
+    void setAvoidUnvalidated(in Network network);
 
     int getRestoreDefaultNetworkDelay(int networkType);
 
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f8cf9ab..5c2ca2e 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -7559,6 +7559,13 @@
 
        /**
         * Whether to automatically switch away from wifi networks that lose Internet access.
+        * Only meaningful if config_networkAvoidBadWifi is set to 0, otherwise the system always
+        * avoids such networks. Valid values are:
+        *
+        * 0: Don't avoid bad wifi, don't prompt the user. Get stuck on bad wifi like it's 2013.
+        * null: Ask the user whether to switch away from bad wifi.
+        * 1: Avoid bad wifi.
+        *
         * @hide
         */
        public static final String NETWORK_AVOID_BAD_WIFI = "network_avoid_bad_wifi";
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 667a4a9..a2d8aa0 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -372,6 +372,11 @@
     private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28;
 
     /**
+     * used to specify whether a network should not be penalized when it becomes unvalidated.
+     */
+    private static final int EVENT_SET_AVOID_UNVALIDATED = 35;
+
+    /**
      * used to ask the user to confirm a connection to an unvalidated network.
      * obj  = network
      */
@@ -2720,6 +2725,12 @@
                 accept ? 1 : 0, always ? 1: 0, network));
     }
 
+    @Override
+    public void setAvoidUnvalidated(Network network) {
+        enforceConnectivityInternalPermission();
+        mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_AVOID_UNVALIDATED, network));
+    }
+
     private void handleSetAcceptUnvalidated(Network network, boolean accept, boolean always) {
         if (DBG) log("handleSetAcceptUnvalidated network=" + network +
                 " accept=" + accept + " always=" + always);
@@ -2760,6 +2771,20 @@
 
     }
 
+    private void handleSetAvoidUnvalidated(Network network) {
+        NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+        if (nai == null || nai.lastValidated) {
+            // Nothing to do. The network either disconnected or revalidated.
+            return;
+        }
+        if (!nai.avoidUnvalidated) {
+            int oldScore = nai.getCurrentScore();
+            nai.avoidUnvalidated = true;
+            rematchAllNetworksAndRequests(nai, oldScore);
+            sendUpdatedScoreToFactories(nai);
+        }
+    }
+
     private void scheduleUnvalidatedPrompt(NetworkAgentInfo nai) {
         if (VDBG) log("scheduleUnvalidatedPrompt " + nai.network);
         mHandler.sendMessageDelayed(
@@ -2767,28 +2792,31 @@
                 PROMPT_UNVALIDATED_DELAY_MS);
     }
 
-    private boolean mAvoidBadWifi;
+    private boolean mAvoidBadWifi = true;
 
     public boolean avoidBadWifi() {
         return mAvoidBadWifi;
     }
 
     @VisibleForTesting
-    public boolean updateAvoidBadWifi() {
-        // There are two modes: either we always automatically avoid unvalidated wifi, or we show a
-        // dialog and don't switch to it. The behaviour is controlled by the NETWORK_AVOID_BAD_WIFI
-        // setting. If the setting has no value, then the value is taken from the config value,
-        // which can be changed via OEM/carrier overlays.
-        //
-        // The only valid values for NETWORK_AVOID_BAD_WIFI are null and unset. Currently, the unit
-        // test uses 0 in order to avoid having to mock out fetching the carrier setting.
-        int defaultAvoidBadWifi =
-            mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi);
-        int avoid = Settings.Global.getInt(mContext.getContentResolver(),
-            Settings.Global.NETWORK_AVOID_BAD_WIFI, defaultAvoidBadWifi);
+    /** Whether the device or carrier configuration disables avoiding bad wifi by default. */
+    public boolean configRestrictsAvoidBadWifi() {
+        return mContext.getResources().getInteger(R.integer.config_networkAvoidBadWifi) == 0;
+    }
+
+    /** Whether we should display a notification when wifi becomes unvalidated. */
+    public boolean shouldNotifyWifiUnvalidated() {
+        return configRestrictsAvoidBadWifi() &&
+                Settings.Global.getString(mContext.getContentResolver(),
+                        Settings.Global.NETWORK_AVOID_BAD_WIFI) == null;
+    }
+
+    private boolean updateAvoidBadWifi() {
+        boolean settingAvoidBadWifi = "1".equals(Settings.Global.getString(
+                mContext.getContentResolver(), Settings.Global.NETWORK_AVOID_BAD_WIFI));
 
         boolean prev = mAvoidBadWifi;
-        mAvoidBadWifi = (avoid == 1);
+        mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
         return mAvoidBadWifi != prev;
     }
 
@@ -2841,7 +2869,7 @@
         NetworkCapabilities nc = nai.networkCapabilities;
         if (DBG) log("handleNetworkUnvalidated " + nai.name() + " cap=" + nc);
 
-        if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && !avoidBadWifi()) {
+        if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) && shouldNotifyWifiUnvalidated()) {
             showValidationNotification(nai, NotificationType.LOST_INTERNET);
         }
     }
@@ -2924,6 +2952,10 @@
                     handleSetAcceptUnvalidated((Network) msg.obj, msg.arg1 != 0, msg.arg2 != 0);
                     break;
                 }
+                case EVENT_SET_AVOID_UNVALIDATED: {
+                    handleSetAvoidUnvalidated((Network) msg.obj);
+                    break;
+                }
                 case EVENT_PROMPT_UNVALIDATED: {
                     handlePromptUnvalidated((Network) msg.obj);
                     break;
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index cb4bb88..2a618bc 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -140,12 +140,14 @@
     public boolean everValidated;
 
     // The result of the last validation attempt on this network (true if validated, false if not).
-    // This bit exists only because we never unvalidate a network once it's been validated, and that
-    // is because the network scoring and revalidation code does not (may not?) deal properly with
-    // networks becoming unvalidated.
-    // TODO: Fix the network scoring code, remove this, and rename everValidated to validated.
     public boolean lastValidated;
 
+    // If true, becoming unvalidated will lower the network's score. This is only meaningful if the
+    // system is configured not to do this for certain networks, e.g., if the
+    // config_networkAvoidBadWifi option is set to 0 and the user has not overridden that via
+    // Settings.Global.NETWORK_AVOID_BAD_WIFI.
+    public boolean avoidUnvalidated;
+
     // Whether a captive portal was ever detected on this network.
     // This is a sticky bit; once set it is never cleared.
     public boolean everCaptivePortalDetected;
@@ -426,8 +428,10 @@
     // Return true on devices configured to ignore score penalty for wifi networks
     // that become unvalidated (b/31075769).
     private boolean ignoreWifiUnvalidationPenalty() {
-        boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI);
-        return isWifi && !mConnService.avoidBadWifi() && everValidated;
+        boolean isWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) &&
+                networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+        boolean avoidBadWifi = mConnService.avoidBadWifi() || avoidUnvalidated;
+        return isWifi && !avoidBadWifi && everValidated;
     }
 
     // Get the current score for this Network.  This may be modified from what the
diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
index de90de8..340be62 100644
--- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java
@@ -82,6 +82,7 @@
 
 import java.net.InetAddress;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -601,6 +602,7 @@
 
     private class WrappedConnectivityService extends ConnectivityService {
         private WrappedNetworkMonitor mLastCreatedNetworkMonitor;
+        public boolean configRestrictsAvoidBadWifi;
 
         public WrappedConnectivityService(Context context, INetworkManagementService netManager,
                 INetworkStatsService statsService, INetworkPolicyManager policyManager,
@@ -656,6 +658,11 @@
             return new FakeWakeupMessage(context, handler, cmdName, cmd, 0, 0, obj);
         }
 
+        @Override
+        public boolean configRestrictsAvoidBadWifi() {
+            return configRestrictsAvoidBadWifi;
+        }
+
         public WrappedNetworkMonitor getLastCreatedWrappedNetworkMonitor() {
             return mLastCreatedNetworkMonitor;
         }
@@ -2041,8 +2048,48 @@
 
     @SmallTest
     public void testAvoidBadWifiSetting() throws Exception {
+        final ContentResolver cr = mServiceContext.getContentResolver();
+        final String settingName = Settings.Global.NETWORK_AVOID_BAD_WIFI;
+
+        mService.configRestrictsAvoidBadWifi = false;
+        String[] values = new String[] {null, "0", "1"};
+        for (int i = 0; i < values.length; i++) {
+            Settings.Global.putInt(cr, settingName, 1);
+            mService.updateNetworkAvoidBadWifi();
+            mService.waitForIdle();
+            String msg = String.format("config=false, setting=%s", values[i]);
+            assertTrue(msg, mService.avoidBadWifi());
+            assertFalse(msg, mService.shouldNotifyWifiUnvalidated());
+        }
+
+        mService.configRestrictsAvoidBadWifi = true;
+
+        Settings.Global.putInt(cr, settingName, 0);
+        mService.updateNetworkAvoidBadWifi();
+        mService.waitForIdle();
+        assertFalse(mService.avoidBadWifi());
+        assertFalse(mService.shouldNotifyWifiUnvalidated());
+
+        Settings.Global.putInt(cr, settingName, 1);
+        mService.updateNetworkAvoidBadWifi();
+        mService.waitForIdle();
+        assertTrue(mService.avoidBadWifi());
+        assertFalse(mService.shouldNotifyWifiUnvalidated());
+
+        Settings.Global.putString(cr, settingName, null);
+        mService.updateNetworkAvoidBadWifi();
+        mService.waitForIdle();
+        assertFalse(mService.avoidBadWifi());
+        assertTrue(mService.shouldNotifyWifiUnvalidated());
+    }
+
+    @SmallTest
+    public void testAvoidBadWifi() throws Exception {
         ContentResolver cr = mServiceContext.getContentResolver();
 
+        // Pretend we're on a carrier that restricts switching away from bad wifi.
+        mService.configRestrictsAvoidBadWifi = true;
+
         // File a request for cell to ensure it doesn't go down.
         final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
         final NetworkRequest cellRequest = new NetworkRequest.Builder()
@@ -2059,8 +2106,8 @@
         TestNetworkCallback validatedWifiCallback = new TestNetworkCallback();
         mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback);
 
-        // Takes effect on every rematch.
         Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 0);
+        mService.updateNetworkAvoidBadWifi();
 
         // Bring up validated cell.
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
@@ -2089,7 +2136,42 @@
                 NET_CAPABILITY_VALIDATED));
         assertEquals(mCm.getActiveNetwork(), wifiNetwork);
 
-        // Simulate the user selecting "switch" on the dialog.
+        // Simulate switching to a carrier that does not restrict avoiding bad wifi, and expect
+        // that we switch back to cell.
+        mService.configRestrictsAvoidBadWifi = false;
+        mService.updateNetworkAvoidBadWifi();
+        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        assertEquals(mCm.getActiveNetwork(), cellNetwork);
+
+        // Switch back to a restrictive carrier.
+        mService.configRestrictsAvoidBadWifi = true;
+        mService.updateNetworkAvoidBadWifi();
+        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        assertEquals(mCm.getActiveNetwork(), wifiNetwork);
+
+        // Simulate the user selecting "switch" on the dialog, and check that we switch to cell.
+        mCm.setAvoidUnvalidated(wifiNetwork);
+        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability(
+                NET_CAPABILITY_VALIDATED));
+        assertEquals(mCm.getActiveNetwork(), cellNetwork);
+
+        // Disconnect and reconnect wifi to clear the one-time switch above.
+        mWiFiNetworkAgent.disconnect();
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(true);
+        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        validatedWifiCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        wifiNetwork = mWiFiNetworkAgent.getNetwork();
+
+        // Fail validation on wifi and expect the dialog to appear.
+        mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 599;
+        mCm.reportNetworkConnectivity(wifiNetwork, false);
+        validatedWifiCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // Simulate the user selecting "switch" and checking the don't ask again checkbox.
         Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
         mService.updateNetworkAvoidBadWifi();
 
@@ -2101,6 +2183,17 @@
                 NET_CAPABILITY_VALIDATED));
         assertEquals(mCm.getActiveNetwork(), cellNetwork);
 
+        // Simulate the user turning the cellular fallback setting off and then on.
+        // We switch to wifi and then to cell.
+        Settings.Global.putString(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, null);
+        mService.updateNetworkAvoidBadWifi();
+        defaultCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        assertEquals(mCm.getActiveNetwork(), wifiNetwork);
+        Settings.Global.putInt(cr, Settings.Global.NETWORK_AVOID_BAD_WIFI, 1);
+        mService.updateNetworkAvoidBadWifi();
+        defaultCallback.expectCallback(CallbackState.AVAILABLE, mCellNetworkAgent);
+        assertEquals(mCm.getActiveNetwork(), cellNetwork);
+
         // If cell goes down, we switch to wifi.
         mCellNetworkAgent.disconnect();
         defaultCallback.expectCallback(CallbackState.LOST, mCellNetworkAgent);