From 43e97e01b73abf471727e67b5df35637c6b44de2 Mon Sep 17 00:00:00 2001 From: markchien Date: Tue, 3 Sep 2019 15:58:06 +0800 Subject: [PATCH] [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 --- Tethering/Android.bp | 10 + .../tethering/TetheringConfiguration.java | 387 ++++++++++++++++++ Tethering/tests/unit/Android.bp | 47 +++ Tethering/tests/unit/AndroidManifest.xml | 26 ++ .../tethering/TetheringConfigurationTest.java | 315 ++++++++++++++ 5 files changed, 785 insertions(+) create mode 100644 Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java create mode 100644 Tethering/tests/unit/Android.bp create mode 100644 Tethering/tests/unit/AndroidManifest.xml create mode 100644 Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java diff --git a/Tethering/Android.bp b/Tethering/Android.bp index a8d3018bc1..dc88fd4014 100644 --- a/Tethering/Android.bp +++ b/Tethering/Android.bp @@ -19,6 +19,8 @@ java_defaults { platform_apis: true, srcs: [ "src/**/*.java", + ":framework-tethering-shared-srcs", + ":services-tethering-shared-srcs", ], static_libs: [ "androidx.annotation_annotation", @@ -60,3 +62,11 @@ android_app { // 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 0000000000..77097271c4 --- /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 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 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 getUpstreamIfaceTypes(Resources res, boolean dunRequired) { + final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types); + final ArrayList 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 list, int value) { + if (list.contains(value)) return; + list.add(0, value); + } + + private static void appendIfNotPresent(ArrayList list, int value) { + if (list.contains(value)) return; + list.add(value); + } + + private static boolean containsOneOf(ArrayList 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 0000000000..089bbd31d9 --- /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 0000000000..049ff6d415 --- /dev/null +++ b/Tethering/tests/unit/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + 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 0000000000..9f9221f301 --- /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 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 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 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); + } + +}