[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);
+ }
+
+}