[Tether02] Migrate TetheringConfiguration into module

TetheringConfiguration is a utility class to encapsulate the various
configuration elements.

Bug: 136040414
Test: -build, flash, boot
      -atest TetheringTests

Change-Id: I9434ab213bc5e0fca59f14a6c8cea554abefc3a4
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index a8d3018..dc88fd4 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -19,6 +19,8 @@
     platform_apis: true,
     srcs: [
         "src/**/*.java",
+        ":framework-tethering-shared-srcs",
+        ":services-tethering-shared-srcs",
     ],
     static_libs: [
         "androidx.annotation_annotation",
@@ -60,3 +62,11 @@
     // The permission configuration *must* be included to ensure security of the device
     required: ["NetworkPermissionConfig"],
 }
+
+// This group will be removed when tethering migration is done.
+filegroup {
+    name: "tethering-services-srcs",
+    srcs: [
+        "src/com/android/server/connectivity/tethering/TetheringConfiguration.java",
+    ],
+}
diff --git a/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
new file mode 100644
index 0000000..7709727
--- /dev/null
+++ b/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2017 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.tethering;
+
+import static android.content.Context.TELEPHONY_SERVICE;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+
+import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
+import static com.android.internal.R.array.config_tether_bluetooth_regexs;
+import static com.android.internal.R.array.config_tether_dhcp_range;
+import static com.android.internal.R.array.config_tether_upstream_types;
+import static com.android.internal.R.array.config_tether_usb_regexs;
+import static com.android.internal.R.array.config_tether_wifi_p2p_regexs;
+import static com.android.internal.R.array.config_tether_wifi_regexs;
+import static com.android.internal.R.bool.config_tether_upstream_automatic;
+import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
+import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.ConnectivityManager;
+import android.net.util.SharedLog;
+import android.provider.Settings;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.StringJoiner;
+
+
+/**
+ * A utility class to encapsulate the various tethering configuration elements.
+ *
+ * This configuration data includes elements describing upstream properties
+ * (preferred and required types of upstream connectivity as well as default
+ * DNS servers to use if none are available) and downstream properties (such
+ * as regular expressions use to match suitable downstream interfaces and the
+ * DHCPv4 ranges to use).
+ *
+ * @hide
+ */
+public class TetheringConfiguration {
+    private static final String TAG = TetheringConfiguration.class.getSimpleName();
+
+    private static final String[] EMPTY_STRING_ARRAY = new String[0];
+
+    // Default ranges used for the legacy DHCP server.
+    // USB is  192.168.42.1 and 255.255.255.0
+    // Wifi is 192.168.43.1 and 255.255.255.0
+    // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
+    // with 255.255.255.0
+    // P2P is 192.168.49.1 and 255.255.255.0
+    private static final String[] LEGACY_DHCP_DEFAULT_RANGE = {
+        "192.168.42.2", "192.168.42.254", "192.168.43.2", "192.168.43.254",
+        "192.168.44.2", "192.168.44.254", "192.168.45.2", "192.168.45.254",
+        "192.168.46.2", "192.168.46.254", "192.168.47.2", "192.168.47.254",
+        "192.168.48.2", "192.168.48.254", "192.168.49.2", "192.168.49.254",
+    };
+
+    private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
+
+    public final String[] tetherableUsbRegexs;
+    public final String[] tetherableWifiRegexs;
+    public final String[] tetherableWifiP2pRegexs;
+    public final String[] tetherableBluetoothRegexs;
+    public final boolean isDunRequired;
+    public final boolean chooseUpstreamAutomatically;
+    public final Collection<Integer> preferredUpstreamIfaceTypes;
+    public final String[] legacyDhcpRanges;
+    public final String[] defaultIPv4DNS;
+    public final boolean enableLegacyDhcpServer;
+
+    public final String[] provisioningApp;
+    public final String provisioningAppNoUi;
+    public final int provisioningCheckPeriod;
+
+    public final int subId;
+
+    public TetheringConfiguration(Context ctx, SharedLog log, int id) {
+        final SharedLog configLog = log.forSubComponent("config");
+
+        subId = id;
+        Resources res = getResources(ctx, subId);
+
+        tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs);
+        // TODO: Evaluate deleting this altogether now that Wi-Fi always passes
+        // us an interface name. Careful consideration needs to be given to
+        // implications for Settings and for provisioning checks.
+        tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs);
+        tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs);
+        tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
+
+        isDunRequired = checkDunRequired(ctx, subId);
+
+        chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
+        preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
+
+        legacyDhcpRanges = getLegacyDhcpRanges(res);
+        defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
+        enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx);
+
+        provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
+        provisioningAppNoUi = getProvisioningAppNoUi(res);
+        provisioningCheckPeriod = getResourceInteger(res,
+                config_mobile_hotspot_provision_check_period,
+                0 /* No periodic re-check */);
+
+        configLog.log(toString());
+    }
+
+    /** Check whether input interface belong to usb.*/
+    public boolean isUsb(String iface) {
+        return matchesDownstreamRegexs(iface, tetherableUsbRegexs);
+    }
+
+    /** Check whether input interface belong to wifi.*/
+    public boolean isWifi(String iface) {
+        return matchesDownstreamRegexs(iface, tetherableWifiRegexs);
+    }
+
+    /** Check whether this interface is Wifi P2P interface. */
+    public boolean isWifiP2p(String iface) {
+        return matchesDownstreamRegexs(iface, tetherableWifiP2pRegexs);
+    }
+
+    /** Check whether using legacy mode for wifi P2P. */
+    public boolean isWifiP2pLegacyTetheringMode() {
+        return (tetherableWifiP2pRegexs == null || tetherableWifiP2pRegexs.length == 0);
+    }
+
+    /** Check whether input interface belong to bluetooth.*/
+    public boolean isBluetooth(String iface) {
+        return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
+    }
+
+    /** Check whether no ui entitlement application is available.*/
+    public boolean hasMobileHotspotProvisionApp() {
+        return !TextUtils.isEmpty(provisioningAppNoUi);
+    }
+
+    /** Does the dumping.*/
+    public void dump(PrintWriter pw) {
+        pw.print("subId: ");
+        pw.println(subId);
+
+        dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
+        dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
+        dumpStringArray(pw, "tetherableWifiP2pRegexs", tetherableWifiP2pRegexs);
+        dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs);
+
+        pw.print("isDunRequired: ");
+        pw.println(isDunRequired);
+
+        pw.print("chooseUpstreamAutomatically: ");
+        pw.println(chooseUpstreamAutomatically);
+        dumpStringArray(pw, "preferredUpstreamIfaceTypes",
+                preferredUpstreamNames(preferredUpstreamIfaceTypes));
+
+        dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges);
+        dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
+
+        dumpStringArray(pw, "provisioningApp", provisioningApp);
+        pw.print("provisioningAppNoUi: ");
+        pw.println(provisioningAppNoUi);
+
+        pw.print("enableLegacyDhcpServer: ");
+        pw.println(enableLegacyDhcpServer);
+    }
+
+    /** Returns the string representation of this object.*/
+    public String toString() {
+        final StringJoiner sj = new StringJoiner(" ");
+        sj.add(String.format("subId:%d", subId));
+        sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs)));
+        sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs)));
+        sj.add(String.format("tetherableWifiP2pRegexs:%s", makeString(tetherableWifiP2pRegexs)));
+        sj.add(String.format("tetherableBluetoothRegexs:%s",
+                makeString(tetherableBluetoothRegexs)));
+        sj.add(String.format("isDunRequired:%s", isDunRequired));
+        sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically));
+        sj.add(String.format("preferredUpstreamIfaceTypes:%s",
+                makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes))));
+        sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
+        sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
+        sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer));
+        return String.format("TetheringConfiguration{%s}", sj.toString());
+    }
+
+    private static void dumpStringArray(PrintWriter pw, String label, String[] values) {
+        pw.print(label);
+        pw.print(": ");
+
+        if (values != null) {
+            final StringJoiner sj = new StringJoiner(", ", "[", "]");
+            for (String value : values) sj.add(value);
+            pw.print(sj.toString());
+        } else {
+            pw.print("null");
+        }
+
+        pw.println();
+    }
+
+    private static String makeString(String[] strings) {
+        if (strings == null) return "null";
+        final StringJoiner sj = new StringJoiner(",", "[", "]");
+        for (String s : strings) sj.add(s);
+        return sj.toString();
+    }
+
+    private static String[] preferredUpstreamNames(Collection<Integer> upstreamTypes) {
+        String[] upstreamNames = null;
+
+        if (upstreamTypes != null) {
+            upstreamNames = new String[upstreamTypes.size()];
+            int i = 0;
+            for (Integer netType : upstreamTypes) {
+                upstreamNames[i] = ConnectivityManager.getNetworkTypeName(netType);
+                i++;
+            }
+        }
+
+        return upstreamNames;
+    }
+
+    /** Check whether dun is required. */
+    public static boolean checkDunRequired(Context ctx, int id) {
+        final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
+        return (tm != null) ? tm.getTetherApnRequired(id) : false;
+    }
+
+    private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
+        final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types);
+        final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
+        for (int i : ifaceTypes) {
+            switch (i) {
+                case TYPE_MOBILE:
+                case TYPE_MOBILE_HIPRI:
+                    if (dunRequired) continue;
+                    break;
+                case TYPE_MOBILE_DUN:
+                    if (!dunRequired) continue;
+                    break;
+            }
+            upstreamIfaceTypes.add(i);
+        }
+
+        // Fix up upstream interface types for DUN or mobile. NOTE: independent
+        // of the value of |dunRequired|, cell data of one form or another is
+        // *always* an upstream, regardless of the upstream interface types
+        // specified by configuration resources.
+        if (dunRequired) {
+            appendIfNotPresent(upstreamIfaceTypes, TYPE_MOBILE_DUN);
+        } else {
+            // Do not modify if a cellular interface type is already present in the
+            // upstream interface types. Add TYPE_MOBILE and TYPE_MOBILE_HIPRI if no
+            // cellular interface types are found in the upstream interface types.
+            // This preserves backwards compatibility and prevents the DUN and default
+            // mobile types incorrectly appearing together, which could happen on
+            // previous releases in the common case where checkDunRequired returned
+            // DUN_UNSPECIFIED.
+            if (!containsOneOf(upstreamIfaceTypes, TYPE_MOBILE, TYPE_MOBILE_HIPRI)) {
+                upstreamIfaceTypes.add(TYPE_MOBILE);
+                upstreamIfaceTypes.add(TYPE_MOBILE_HIPRI);
+            }
+        }
+
+        // Always make sure our good friend Ethernet is present.
+        // TODO: consider unilaterally forcing this at the front.
+        prependIfNotPresent(upstreamIfaceTypes, TYPE_ETHERNET);
+
+        return upstreamIfaceTypes;
+    }
+
+    private static boolean matchesDownstreamRegexs(String iface, String[] regexs) {
+        for (String regex : regexs) {
+            if (iface.matches(regex)) return true;
+        }
+        return false;
+    }
+
+    private static String[] getLegacyDhcpRanges(Resources res) {
+        final String[] fromResource = getResourceStringArray(res, config_tether_dhcp_range);
+        if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
+            return fromResource;
+        }
+        return copy(LEGACY_DHCP_DEFAULT_RANGE);
+    }
+
+    private static String getProvisioningAppNoUi(Resources res) {
+        try {
+            return res.getString(config_mobile_hotspot_provision_app_no_ui);
+        } catch (Resources.NotFoundException e) {
+            return "";
+        }
+    }
+
+    private static boolean getResourceBoolean(Resources res, int resId) {
+        try {
+            return res.getBoolean(resId);
+        } catch (Resources.NotFoundException e404) {
+            return false;
+        }
+    }
+
+    private static String[] getResourceStringArray(Resources res, int resId) {
+        try {
+            final String[] strArray = res.getStringArray(resId);
+            return (strArray != null) ? strArray : EMPTY_STRING_ARRAY;
+        } catch (Resources.NotFoundException e404) {
+            return EMPTY_STRING_ARRAY;
+        }
+    }
+
+    private static int getResourceInteger(Resources res, int resId, int defaultValue) {
+        try {
+            return res.getInteger(resId);
+        } catch (Resources.NotFoundException e404) {
+            return defaultValue;
+        }
+    }
+
+    private static boolean getEnableLegacyDhcpServer(Context ctx) {
+        final ContentResolver cr = ctx.getContentResolver();
+        final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
+        return intVal != 0;
+    }
+
+    private Resources getResources(Context ctx, int subId) {
+        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            return getResourcesForSubIdWrapper(ctx, subId);
+        } else {
+            return ctx.getResources();
+        }
+    }
+
+    @VisibleForTesting
+    protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
+        return SubscriptionManager.getResourcesForSubId(ctx, subId);
+    }
+
+    private static String[] copy(String[] strarray) {
+        return Arrays.copyOf(strarray, strarray.length);
+    }
+
+    private static void prependIfNotPresent(ArrayList<Integer> list, int value) {
+        if (list.contains(value)) return;
+        list.add(0, value);
+    }
+
+    private static void appendIfNotPresent(ArrayList<Integer> list, int value) {
+        if (list.contains(value)) return;
+        list.add(value);
+    }
+
+    private static boolean containsOneOf(ArrayList<Integer> list, Integer... values) {
+        for (Integer value : values) {
+            if (list.contains(value)) return true;
+        }
+        return false;
+    }
+}
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
new file mode 100644
index 0000000..089bbd3
--- /dev/null
+++ b/Tethering/tests/unit/Android.bp
@@ -0,0 +1,47 @@
+//
+// Copyright (C) 2019 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.
+//
+
+android_test {
+    name: "TetheringTests",
+    certificate: "platform",
+    srcs: ["src/**/*.java"],
+    test_suites: ["device-tests"],
+    static_libs: [
+        "androidx.test.rules",
+        "frameworks-base-testutils",
+        "mockito-target-extended-minus-junit4",
+        "TetheringApiCurrentLib",
+        "testables",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+        "android.test.mock",
+    ],
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+}
+
+// This group would be removed when tethering migration is done.
+filegroup {
+    name: "tethering-tests-src",
+    srcs: [
+        "src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java",
+    ],
+}
diff --git a/Tethering/tests/unit/AndroidManifest.xml b/Tethering/tests/unit/AndroidManifest.xml
new file mode 100644
index 0000000..049ff6d
--- /dev/null
+++ b/Tethering/tests/unit/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.tethering.tests.unit">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.tethering.tests.unit"
+        android:label="Tethering service tests">
+    </instrumentation>
+</manifest>
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
new file mode 100644
index 0000000..9f9221f
--- /dev/null
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2017 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.tethering;
+
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
+import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
+import static com.android.internal.R.array.config_tether_bluetooth_regexs;
+import static com.android.internal.R.array.config_tether_dhcp_range;
+import static com.android.internal.R.array.config_tether_upstream_types;
+import static com.android.internal.R.array.config_tether_usb_regexs;
+import static com.android.internal.R.array.config_tether_wifi_regexs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.Resources;
+import android.net.util.SharedLog;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TetheringConfigurationTest {
+    private final SharedLog mLog = new SharedLog("TetheringConfigurationTest");
+
+    private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
+    @Mock private Context mContext;
+    @Mock private TelephonyManager mTelephonyManager;
+    @Mock private Resources mResources;
+    @Mock private Resources mResourcesForSubId;
+    private MockContentResolver mContentResolver;
+    private Context mMockContext;
+    private boolean mHasTelephonyManager;
+
+    private class MockTetheringConfiguration extends TetheringConfiguration {
+        MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
+            super(ctx, log, id);
+        }
+
+        @Override
+        protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
+            return mResourcesForSubId;
+        }
+    }
+
+    private class MockContext extends BroadcastInterceptingContext {
+        MockContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public Resources getResources() {
+            return mResources;
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            if (Context.TELEPHONY_SERVICE.equals(name)) {
+                return mHasTelephonyManager ? mTelephonyManager : null;
+            }
+            return super.getSystemService(name);
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mContentResolver;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]);
+        when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]);
+        when(mResources.getStringArray(config_tether_wifi_regexs))
+                .thenReturn(new String[]{ "test_wlan\\d" });
+        when(mResources.getStringArray(config_tether_bluetooth_regexs)).thenReturn(new String[0]);
+        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
+        when(mResources.getStringArray(config_mobile_hotspot_provision_app))
+                .thenReturn(new String[0]);
+        mContentResolver = new MockContentResolver();
+        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        mHasTelephonyManager = true;
+        mMockContext = new MockContext(mContext);
+    }
+
+    private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
+        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+                legacyTetherUpstreamTypes);
+        return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+    }
+
+    @Test
+    public void testNoTelephonyManagerMeansNoDun() {
+        mHasTelephonyManager = false;
+        final TetheringConfiguration cfg = getTetheringConfiguration(
+                new int[]{TYPE_MOBILE_DUN, TYPE_WIFI});
+        assertFalse(cfg.isDunRequired);
+        assertFalse(cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
+        // Just to prove we haven't clobbered Wi-Fi:
+        assertTrue(cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI));
+    }
+
+    @Test
+    public void testDunFromTelephonyManagerMeansDun() {
+        when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(true);
+
+        final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
+        final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
+                TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI);
+        final TetheringConfiguration cfgWifiDun = getTetheringConfiguration(
+                TYPE_WIFI, TYPE_MOBILE_DUN);
+        final TetheringConfiguration cfgMobileWifiHipriDun = getTetheringConfiguration(
+                TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI, TYPE_MOBILE_DUN);
+
+        for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri,
+                cfgWifiDun, cfgMobileWifiHipriDun)) {
+            String msg = "config=" + cfg.toString();
+            assertTrue(msg, cfg.isDunRequired);
+            assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
+            assertFalse(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
+            assertFalse(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI));
+            // Just to prove we haven't clobbered Wi-Fi:
+            assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI));
+        }
+    }
+
+    @Test
+    public void testDunNotRequiredFromTelephonyManagerMeansNoDun() {
+        when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false);
+
+        final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
+        final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
+                TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI);
+        final TetheringConfiguration cfgWifiDun = getTetheringConfiguration(
+                TYPE_WIFI, TYPE_MOBILE_DUN);
+        final TetheringConfiguration cfgWifiMobile = getTetheringConfiguration(
+                TYPE_WIFI, TYPE_MOBILE);
+        final TetheringConfiguration cfgWifiHipri = getTetheringConfiguration(
+                TYPE_WIFI, TYPE_MOBILE_HIPRI);
+        final TetheringConfiguration cfgMobileWifiHipriDun = getTetheringConfiguration(
+                TYPE_MOBILE, TYPE_WIFI, TYPE_MOBILE_HIPRI, TYPE_MOBILE_DUN);
+
+        String msg;
+        // TYPE_MOBILE_DUN should be present in none of the combinations.
+        // TYPE_WIFI should not be affected.
+        for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri, cfgWifiDun,
+                cfgWifiMobile, cfgWifiHipri, cfgMobileWifiHipriDun)) {
+            msg = "config=" + cfg.toString();
+            assertFalse(msg, cfg.isDunRequired);
+            assertFalse(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_DUN));
+            assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_WIFI));
+        }
+
+        for (TetheringConfiguration cfg : Arrays.asList(cfgWifi, cfgMobileWifiHipri, cfgWifiDun,
+                cfgMobileWifiHipriDun)) {
+            msg = "config=" + cfg.toString();
+            assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
+            assertTrue(msg, cfg.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI));
+        }
+        msg = "config=" + cfgWifiMobile.toString();
+        assertTrue(msg, cfgWifiMobile.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
+        assertFalse(msg, cfgWifiMobile.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI));
+        msg = "config=" + cfgWifiHipri.toString();
+        assertFalse(msg, cfgWifiHipri.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE));
+        assertTrue(msg, cfgWifiHipri.preferredUpstreamIfaceTypes.contains(TYPE_MOBILE_HIPRI));
+
+    }
+
+    @Test
+    public void testNoDefinedUpstreamTypesAddsEthernet() {
+        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{});
+        when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
+        assertTrue(upstreamIterator.hasNext());
+        assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue());
+        // The following is because the code always adds some kind of mobile
+        // upstream, be it DUN or, in this case where DUN is NOT required,
+        // make sure there is at least one of MOBILE or HIPRI. With the empty
+        // list of the configuration in this test, it will always add both
+        // MOBILE and HIPRI, in that order.
+        assertTrue(upstreamIterator.hasNext());
+        assertEquals(TYPE_MOBILE, upstreamIterator.next().intValue());
+        assertTrue(upstreamIterator.hasNext());
+        assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue());
+        assertFalse(upstreamIterator.hasNext());
+    }
+
+    @Test
+    public void testDefinedUpstreamTypesSansEthernetAddsEthernet() {
+        when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+                new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
+        when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
+        assertTrue(upstreamIterator.hasNext());
+        assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue());
+        assertTrue(upstreamIterator.hasNext());
+        assertEquals(TYPE_WIFI, upstreamIterator.next().intValue());
+        assertTrue(upstreamIterator.hasNext());
+        assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue());
+        assertFalse(upstreamIterator.hasNext());
+    }
+
+    @Test
+    public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() {
+        when(mResources.getIntArray(config_tether_upstream_types))
+                .thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI});
+        when(mTelephonyManager.getTetherApnRequired(anyInt())).thenReturn(false);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        final Iterator<Integer> upstreamIterator = cfg.preferredUpstreamIfaceTypes.iterator();
+        assertTrue(upstreamIterator.hasNext());
+        assertEquals(TYPE_WIFI, upstreamIterator.next().intValue());
+        assertTrue(upstreamIterator.hasNext());
+        assertEquals(TYPE_ETHERNET, upstreamIterator.next().intValue());
+        assertTrue(upstreamIterator.hasNext());
+        assertEquals(TYPE_MOBILE_HIPRI, upstreamIterator.next().intValue());
+        assertFalse(upstreamIterator.hasNext());
+    }
+
+    @Test
+    public void testNewDhcpServerDisabled() {
+        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertTrue(cfg.enableLegacyDhcpServer);
+    }
+
+    @Test
+    public void testNewDhcpServerEnabled() {
+        Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
+
+        final TetheringConfiguration cfg = new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertFalse(cfg.enableLegacyDhcpServer);
+    }
+
+    @Test
+    public void testGetResourcesBySubId() {
+        setUpResourceForSubId();
+        final TetheringConfiguration cfg = new TetheringConfiguration(
+                mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+        assertTrue(cfg.provisioningApp.length == 0);
+        final int anyValidSubId = 1;
+        final MockTetheringConfiguration mockCfg =
+                new MockTetheringConfiguration(mMockContext, mLog, anyValidSubId);
+        assertEquals(mockCfg.provisioningApp[0], PROVISIONING_APP_NAME[0]);
+        assertEquals(mockCfg.provisioningApp[1], PROVISIONING_APP_NAME[1]);
+    }
+
+    private void setUpResourceForSubId() {
+        when(mResourcesForSubId.getStringArray(
+                config_tether_dhcp_range)).thenReturn(new String[0]);
+        when(mResourcesForSubId.getStringArray(
+                config_tether_usb_regexs)).thenReturn(new String[0]);
+        when(mResourcesForSubId.getStringArray(
+                config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
+        when(mResourcesForSubId.getStringArray(
+                config_tether_bluetooth_regexs)).thenReturn(new String[0]);
+        when(mResourcesForSubId.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
+        when(mResourcesForSubId.getStringArray(
+                config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
+    }
+
+}