Add a device config for actively preferring bad wifi

Test: FrameworksNetTests
      adb shell device_config put connectivity actively_prefer_bad_wifi
       + check that dumpsys updates
Test: at this patchset tests fail with :
      Permission denial: reading from settings requires:android.permission.READ_DEVICE_CONFIG

Change-Id: Icfe1e64b8313921ff1dc8e4cbd45f967692e1c4a
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 1236243..84cf561 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -5112,22 +5112,32 @@
 
         pw.println("Bad Wi-Fi avoidance: " + avoidBadWifi());
         pw.increaseIndent();
-        pw.println("Config restrict:   " + configRestrict);
-        pw.println("Actively prefer:   " + activelyPreferBadWifi());
+        pw.println("Config restrict:               " + configRestrict);
+        pw.println("Actively prefer bad wifi:      " + activelyPreferBadWifi());
 
-        final String value = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
+        final String settingValue = mMultinetworkPolicyTracker.getAvoidBadWifiSetting();
         String description;
         // Can't use a switch statement because strings are legal case labels, but null is not.
-        if ("0".equals(value)) {
+        if ("0".equals(settingValue)) {
             description = "get stuck";
-        } else if (value == null) {
+        } else if (settingValue == null) {
             description = "prompt";
-        } else if ("1".equals(value)) {
+        } else if ("1".equals(settingValue)) {
             description = "avoid";
         } else {
-            description = value + " (?)";
+            description = settingValue + " (?)";
         }
-        pw.println("User setting:      " + description);
+        pw.println("Avoid bad wifi setting:        " + description);
+        final Boolean configValue = mMultinetworkPolicyTracker.deviceConfigActivelyPreferBadWifi();
+        if (null == configValue) {
+            description = "unset";
+        } else if (configValue) {
+            description = "force true";
+        } else {
+            description = "force false";
+        }
+        pw.println("Actively prefer bad wifi conf: " + description);
+        pw.println();
         pw.println("Network overrides:");
         pw.increaseIndent();
         for (NetworkAgentInfo nai : networksSortedById()) {
diff --git a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
index a36bf4b..58196f7 100644
--- a/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
+++ b/service/src/com/android/server/connectivity/MultinetworkPolicyTracker.java
@@ -32,6 +32,7 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyCallback;
@@ -41,6 +42,7 @@
 import com.android.connectivity.resources.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.DeviceConfigUtils;
 
 import java.util.Arrays;
 import java.util.List;
@@ -67,6 +69,9 @@
 public class MultinetworkPolicyTracker {
     private static String TAG = MultinetworkPolicyTracker.class.getSimpleName();
 
+    // See Dependencies#getConfigActivelyPreferBadWifi
+    public static final String CONFIG_ACTIVELY_PREFER_BAD_WIFI = "actively_prefer_bad_wifi";
+
     private final Context mContext;
     private final ConnectivityResources mResources;
     private final Handler mHandler;
@@ -82,6 +87,43 @@
     private volatile long mTestAllowBadWifiUntilMs = 0;
 
     /**
+     * Dependencies for testing
+     */
+    @VisibleForTesting
+    public static class Dependencies {
+        /**
+         * @see DeviceConfigUtils#getDeviceConfigPropertyInt
+         */
+        protected int getConfigActivelyPreferBadWifi() {
+            // CONFIG_ACTIVELY_PREFER_BAD_WIFI is not a feature to be rolled out, but an override
+            // for tests and an emergency kill switch (which could force the behavior on OR off).
+            // As such it uses a -1/null/1 scheme, but features should use
+            // DeviceConfigUtils#isFeatureEnabled instead, to make sure rollbacks disable the
+            // feature before it's ready on R and before.
+            return DeviceConfig.getInt(DeviceConfig.NAMESPACE_CONNECTIVITY,
+                    CONFIG_ACTIVELY_PREFER_BAD_WIFI, 0);
+        }
+
+        /**
+         @see DeviceConfig#addOnPropertiesChangedListener
+         */
+        protected void addOnDevicePropertiesChangedListener(@NonNull final Executor executor,
+                @NonNull final DeviceConfig.OnPropertiesChangedListener listener) {
+            DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_CONNECTIVITY,
+                    executor, listener);
+        }
+
+        @VisibleForTesting
+        @NonNull
+        protected Resources getResourcesForActiveSubId(
+                @NonNull final ConnectivityResources resources, final int activeSubId) {
+            return SubscriptionManager.getResourcesForSubId(
+                    resources.getResourcesContext(), activeSubId);
+        }
+    }
+    private final Dependencies mDeps;
+
+    /**
      * Whether to prefer bad wifi to a network that yields to bad wifis, even if it never validated
      *
      * This setting only makes sense if the system is configured not to avoid bad wifis, i.e.
@@ -129,15 +171,17 @@
         }
     }
 
-    public MultinetworkPolicyTracker(Context ctx, Handler handler) {
-        this(ctx, handler, null);
+    public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
+        this(ctx, handler, avoidBadWifiCallback, new Dependencies());
     }
 
-    public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback) {
+    public MultinetworkPolicyTracker(Context ctx, Handler handler, Runnable avoidBadWifiCallback,
+            Dependencies deps) {
         mContext = ctx;
         mResources = new ConnectivityResources(ctx);
         mHandler = handler;
         mAvoidBadWifiCallback = avoidBadWifiCallback;
+        mDeps = deps;
         mSettingsUris = Arrays.asList(
                 Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI),
                 Settings.Global.getUriFor(NETWORK_METERED_MULTIPATH_PREFERENCE));
@@ -166,8 +210,11 @@
         mContext.registerReceiverForAllUsers(mBroadcastReceiver, intentFilter,
                 null /* broadcastPermission */, mHandler);
 
+        final Executor handlerExecutor = new HandlerExecutor(mHandler);
         mContext.getSystemService(TelephonyManager.class).registerTelephonyCallback(
-                new HandlerExecutor(mHandler), new ActiveDataSubscriptionIdListener());
+                handlerExecutor, new ActiveDataSubscriptionIdListener());
+        mDeps.addOnDevicePropertiesChangedListener(handlerExecutor,
+                properties -> reevaluateInternal());
 
         reevaluate();
     }
@@ -201,7 +248,7 @@
         // NETWORK_AVOID_BAD_WIFI setting.
         if (allowBadWifi) return true;
 
-        return getResourcesForActiveSubId()
+        return mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
                 .getInteger(R.integer.config_networkAvoidBadWifi) == 0;
     }
 
@@ -223,7 +270,7 @@
         // bad wifi (only stay stuck on it if already on there). This implementation treats
         // any non-0 value like 1, on the assumption that anybody setting it non-zero wants
         // the newer behavior.
-        return 0 != getResourcesForActiveSubId()
+        return 0 != mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
                 .getInteger(R.integer.config_activelyPreferBadWifi);
     }
 
@@ -237,13 +284,6 @@
         reevaluateInternal();
     }
 
-    @VisibleForTesting
-    @NonNull
-    protected Resources getResourcesForActiveSubId() {
-        return SubscriptionManager.getResourcesForSubId(
-                mResources.getResourcesContext(), mActiveSubId);
-    }
-
     /**
      * Whether we should display a notification when wifi becomes unvalidated.
      */
@@ -255,6 +295,29 @@
         return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI);
     }
 
+    /**
+     * Returns whether device config says the device should actively prefer bad wifi.
+     *
+     * {@see #configActivelyPrefersBadWifi} for a description of what this does. This device
+     * config overrides that config overlay.
+     *
+     * @return True on Android U and above.
+     *         True if device config says to actively prefer bad wifi.
+     *         False if device config says not to actively prefer bad wifi.
+     *         null if device config doesn't have an opinion (then fall back on the resource).
+     */
+    public Boolean deviceConfigActivelyPreferBadWifi() {
+        if (SdkLevel.isAtLeastU()) return true;
+        switch (mDeps.getConfigActivelyPreferBadWifi()) {
+            case 1:
+                return Boolean.TRUE;
+            case -1:
+                return Boolean.FALSE;
+            default:
+                return null;
+        }
+    }
+
     @VisibleForTesting
     public void reevaluate() {
         mHandler.post(this::reevaluateInternal);
@@ -276,7 +339,12 @@
         mAvoidBadWifi = settingAvoidBadWifi || !configRestrictsAvoidBadWifi();
 
         final boolean prevActive = mActivelyPreferBadWifi;
-        mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
+        final Boolean deviceConfigPreferBadWifi = deviceConfigActivelyPreferBadWifi();
+        if (null == deviceConfigPreferBadWifi) {
+            mActivelyPreferBadWifi = configActivelyPrefersBadWifi();
+        } else {
+            mActivelyPreferBadWifi = deviceConfigPreferBadWifi;
+        }
 
         return mAvoidBadWifi != prevAvoid || mActivelyPreferBadWifi != prevActive;
     }
@@ -285,10 +353,8 @@
      * The default (device and carrier-dependent) value for metered multipath preference.
      */
     public int configMeteredMultipathPreference() {
-        // TODO: use R.integer.config_networkMeteredMultipathPreference directly
-        final int id = mResources.get().getIdentifier("config_networkMeteredMultipathPreference",
-                "integer", mResources.getResourcesContext().getPackageName());
-        return mResources.get().getInteger(id);
+        return mDeps.getResourcesForActiveSubId(mResources, mActiveSubId)
+                .getInteger(R.integer.config_networkMeteredMultipathPreference);
     }
 
     public void updateMeteredMultipathPreference() {