[automerger skipped] Allows the caller to specify configuration by TetheringRequest am: f6761bdd0c am: d4f206ce74 am: ef72953b83 -s ours
am skip reason: Change-Id Ic7c3a33195bbd7e72f9b8e73fa148be476b87bf3 with SHA-1 ecaba161bd is in history
Change-Id: I036e4263a187c92c88c7fc3d33484efd6b687fcb
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 1315ed0..cf2b1f0 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -16,7 +16,7 @@
java_defaults {
name: "TetheringAndroidLibraryDefaults",
- platform_apis: true,
+ sdk_version: "system_current",
srcs: [
"src/**/*.java",
":framework-tethering-shared-srcs",
@@ -29,6 +29,7 @@
"netlink-client",
"networkstack-aidl-interfaces-unstable-java",
"android.hardware.tetheroffload.control-V1.0-java",
+ "net-utils-framework-common",
],
libs: [
"framework-tethering",
@@ -80,7 +81,7 @@
// Common defaults for compiling the actual APK.
java_defaults {
name: "TetheringAppDefaults",
- platform_apis: true,
+ sdk_version: "system_current",
privileged: true,
// Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
// explicitly.
diff --git a/Tethering/apex/manifest.json b/Tethering/apex/manifest.json
index b64de4f..538ffb3 100644
--- a/Tethering/apex/manifest.json
+++ b/Tethering/apex/manifest.json
@@ -1,4 +1,4 @@
{
"name": "com.android.tethering",
- "version": 290000000
+ "version": 300000000
}
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index b0ef153..e0adb34 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -19,7 +19,15 @@
local_include_dir: "src",
include_dirs: ["frameworks/base/core/java"], // For framework parcelables.
srcs: [
- "src/android/net/*.aidl",
+ // @JavaOnlyStableParcelable aidl declarations must not be listed here, as this would cause
+ // compilation to fail (b/148001843).
+ "src/android/net/IIntResultListener.aidl",
+ "src/android/net/ITetheringConnector.aidl",
+ "src/android/net/ITetheringEventCallback.aidl",
+ "src/android/net/TetheringCallbackStartedParcel.aidl",
+ "src/android/net/TetheringConfigurationParcel.aidl",
+ "src/android/net/TetheringRequestParcel.aidl",
+ "src/android/net/TetherStatesParcel.aidl",
],
backend: {
ndk: {
@@ -35,6 +43,7 @@
name: "framework-tethering",
sdk_version: "system_current",
srcs: [
+ "src/android/net/TetheredClient.java",
"src/android/net/TetheringManager.java",
"src/android/net/TetheringConstants.java",
":framework-tethering-annotations",
@@ -63,6 +72,8 @@
filegroup {
name: "framework-tethering-srcs",
srcs: [
+ "src/android/net/TetheredClient.aidl",
+ "src/android/net/TetheredClient.java",
"src/android/net/TetheringManager.java",
"src/android/net/TetheringConstants.java",
"src/android/net/IIntResultListener.aidl",
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheredClient.aidl b/Tethering/common/TetheringLib/src/android/net/TetheredClient.aidl
new file mode 100644
index 0000000..0b279b8
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheredClient.aidl
@@ -0,0 +1,18 @@
+/**
+ * Copyright (C) 2020 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 android.net;
+
+@JavaOnlyStableParcelable parcelable TetheredClient;
\ No newline at end of file
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheredClient.java b/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
new file mode 100644
index 0000000..6514688
--- /dev/null
+++ b/Tethering/common/TetheringLib/src/android/net/TetheredClient.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2020 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 android.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Information on a tethered downstream client.
+ * @hide
+ */
+@SystemApi
+@TestApi
+public final class TetheredClient implements Parcelable {
+ @NonNull
+ private final MacAddress mMacAddress;
+ @NonNull
+ private final List<AddressInfo> mAddresses;
+ // TODO: use an @IntDef here
+ private final int mTetheringType;
+
+ public TetheredClient(@NonNull MacAddress macAddress,
+ @NonNull Collection<AddressInfo> addresses, int tetheringType) {
+ mMacAddress = macAddress;
+ mAddresses = new ArrayList<>(addresses);
+ mTetheringType = tetheringType;
+ }
+
+ private TetheredClient(@NonNull Parcel in) {
+ this(in.readParcelable(null), in.createTypedArrayList(AddressInfo.CREATOR), in.readInt());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mMacAddress, flags);
+ dest.writeTypedList(mAddresses);
+ dest.writeInt(mTetheringType);
+ }
+
+ @NonNull
+ public MacAddress getMacAddress() {
+ return mMacAddress;
+ }
+
+ @NonNull
+ public List<AddressInfo> getAddresses() {
+ return new ArrayList<>(mAddresses);
+ }
+
+ public int getTetheringType() {
+ return mTetheringType;
+ }
+
+ /**
+ * Return a new {@link TetheredClient} that has all the attributes of this instance, plus the
+ * {@link AddressInfo} of the provided {@link TetheredClient}.
+ *
+ * <p>Duplicate addresses are removed.
+ * @hide
+ */
+ public TetheredClient addAddresses(@NonNull TetheredClient other) {
+ final HashSet<AddressInfo> newAddresses = new HashSet<>(
+ mAddresses.size() + other.mAddresses.size());
+ newAddresses.addAll(mAddresses);
+ newAddresses.addAll(other.mAddresses);
+ return new TetheredClient(mMacAddress, newAddresses, mTetheringType);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mMacAddress, mAddresses, mTetheringType);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof TetheredClient)) return false;
+ final TetheredClient other = (TetheredClient) obj;
+ return mMacAddress.equals(other.mMacAddress)
+ && mAddresses.equals(other.mAddresses)
+ && mTetheringType == other.mTetheringType;
+ }
+
+ /**
+ * Information on an lease assigned to a tethered client.
+ */
+ public static final class AddressInfo implements Parcelable {
+ @NonNull
+ private final LinkAddress mAddress;
+ @Nullable
+ private final String mHostname;
+ // TODO: use LinkAddress expiration time once it is supported
+ private final long mExpirationTime;
+
+ /** @hide */
+ public AddressInfo(@NonNull LinkAddress address, @Nullable String hostname) {
+ this(address, hostname, 0);
+ }
+
+ /** @hide */
+ public AddressInfo(@NonNull LinkAddress address, String hostname, long expirationTime) {
+ this.mAddress = address;
+ this.mHostname = hostname;
+ this.mExpirationTime = expirationTime;
+ }
+
+ private AddressInfo(Parcel in) {
+ this(in.readParcelable(null), in.readString(), in.readLong());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mAddress, flags);
+ dest.writeString(mHostname);
+ dest.writeLong(mExpirationTime);
+ }
+
+ @NonNull
+ public LinkAddress getAddress() {
+ return mAddress;
+ }
+
+ @Nullable
+ public String getHostname() {
+ return mHostname;
+ }
+
+ /** @hide TODO: use expiration time in LinkAddress */
+ public long getExpirationTime() {
+ return mExpirationTime;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mAddress, mHostname, mExpirationTime);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof AddressInfo)) return false;
+ final AddressInfo other = (AddressInfo) obj;
+ // Use .equals() for addresses as all changes, including address expiry changes,
+ // should be included.
+ return other.mAddress.equals(mAddress)
+ && Objects.equals(mHostname, other.mHostname)
+ && mExpirationTime == other.mExpirationTime;
+ }
+
+ @NonNull
+ public static final Creator<AddressInfo> CREATOR = new Creator<AddressInfo>() {
+ @NonNull
+ @Override
+ public AddressInfo createFromParcel(@NonNull Parcel in) {
+ return new AddressInfo(in);
+ }
+
+ @NonNull
+ @Override
+ public AddressInfo[] newArray(int size) {
+ return new AddressInfo[size];
+ }
+ };
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<TetheredClient> CREATOR = new Creator<TetheredClient>() {
+ @NonNull
+ @Override
+ public TetheredClient createFromParcel(@NonNull Parcel in) {
+ return new TetheredClient(in);
+ }
+
+ @NonNull
+ @Override
+ public TetheredClient[] newArray(int size) {
+ return new TetheredClient[size];
+ }
+ };
+}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 58bc4e7..79c6930 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -31,6 +31,7 @@
import android.util.Log;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -129,6 +130,12 @@
*/
public static final int TETHERING_WIFI_P2P = 3;
+ /**
+ * Ncm local tethering type.
+ * @see #startTethering(TetheringRequest, Executor, StartTetheringCallback)
+ */
+ public static final int TETHERING_NCM = 4;
+
public static final int TETHER_ERROR_NO_ERROR = 0;
public static final int TETHER_ERROR_UNKNOWN_IFACE = 1;
public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2;
@@ -653,6 +660,19 @@
* @param error One of {@code TetheringManager#TETHER_ERROR_*}.
*/
public void onError(@NonNull String ifName, int error) {}
+
+ /**
+ * Called when the list of tethered clients changes.
+ *
+ * <p>This callback provides best-effort information on connected clients based on state
+ * known to the system, however the list cannot be completely accurate (and should not be
+ * used for security purposes). For example, clients behind a bridge and using static IP
+ * assignments are not visible to the tethering device; or even when using DHCP, such
+ * clients may still be reported by this callback after disconnection as the system cannot
+ * determine if they are still connected.
+ * @param clients The new set of tethered clients; the collection is not ordered.
+ */
+ public void onClientsChanged(@NonNull Collection<TetheredClient> clients) {}
}
/**
diff --git a/Tethering/res/values/config.xml b/Tethering/res/values/config.xml
index 6fa1f77..c489cbc 100644
--- a/Tethering/res/values/config.xml
+++ b/Tethering/res/values/config.xml
@@ -29,6 +29,12 @@
</string-array>
<!-- List of regexpressions describing the interface (if any) that represent tetherable
+ NCM interfaces. If the device doesn't want to support tethering over NCM this should
+ be empty. -->
+ <string-array translatable="false" name="config_tether_ncm_regexs">
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
Wifi interfaces. If the device doesn't want to support tethering over Wifi this
should be empty. An example would be "softap.*" -->
<string-array translatable="false" name="config_tether_wifi_regexs">
@@ -38,8 +44,9 @@
<!-- List of regexpressions describing the interface (if any) that represent tetherable
Wifi P2P interfaces. If the device doesn't want to support tethering over Wifi P2p this
- should be empty. An example would be "p2p-p2p.*" -->
+ should be empty. An example would be "p2p-p2p\\d-.*" -->
<string-array translatable="false" name="config_tether_wifi_p2p_regexs">
+ <item>"p2p-p2p\\d-.*"</item>
</string-array>
<!-- List of regexpressions describing the interface (if any) that represent tetherable
diff --git a/Tethering/res/values/overlayable.xml b/Tethering/res/values/overlayable.xml
index e089d9d..fe025c7 100644
--- a/Tethering/res/values/overlayable.xml
+++ b/Tethering/res/values/overlayable.xml
@@ -17,6 +17,7 @@
<overlayable name="TetheringConfig">
<policy type="product|system|vendor">
<item type="array" name="config_tether_usb_regexs"/>
+ <item type="array" name="config_tether_ncm_regexs" />
<item type="array" name="config_tether_wifi_regexs"/>
<item type="array" name="config_tether_wifi_p2p_regexs"/>
<item type="array" name="config_tether_bluetooth_regexs"/>
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index 0491ad7..57cc4dd 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -416,7 +416,8 @@
final Inet4Address srvAddr;
int prefixLen = 0;
try {
- if (mInterfaceType == TetheringManager.TETHERING_USB) {
+ if (mInterfaceType == TetheringManager.TETHERING_USB
+ || mInterfaceType == TetheringManager.TETHERING_NCM) {
srvAddr = (Inet4Address) parseNumericAddress(USB_NEAR_IFACE_ADDR);
prefixLen = USB_PREFIX_LENGTH;
} else if (mInterfaceType == TetheringManager.TETHERING_WIFI) {
diff --git a/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index ce6a43f..02ba17e 100644
--- a/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -19,6 +19,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
+import static android.hardware.usb.UsbManager.USB_FUNCTION_NCM;
import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
@@ -30,6 +31,7 @@
import static android.net.TetheringManager.EXTRA_ERRORED_TETHER;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
import static android.net.TetheringManager.TETHERING_INVALID;
+import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHERING_WIFI_P2P;
@@ -408,6 +410,8 @@
return TETHERING_USB;
} else if (cfg.isBluetooth(iface)) {
return TETHERING_BLUETOOTH;
+ } else if (cfg.isNcm(iface)) {
+ return TETHERING_NCM;
}
return TETHERING_INVALID;
}
@@ -456,6 +460,10 @@
case TETHERING_BLUETOOTH:
setBluetoothTethering(enable, listener);
break;
+ case TETHERING_NCM:
+ result = setNcmTethering(enable);
+ sendTetherResult(listener, result);
+ break;
default:
Log.w(TAG, "Invalid tether type.");
sendTetherResult(listener, TETHER_ERROR_UNKNOWN_IFACE);
@@ -479,7 +487,7 @@
mLog.e("setWifiTethering: failed to get WifiManager!");
return TETHER_ERROR_SERVICE_UNAVAIL;
}
- if ((enable && mgr.startSoftAp(null /* use existing wifi config */))
+ if ((enable && mgr.startTetheredHotspot(null /* use existing softap config */))
|| (!enable && mgr.stopSoftAp())) {
mWifiTetherRequested = enable;
return TETHER_ERROR_NO_ERROR;
@@ -805,6 +813,7 @@
final boolean usbConnected = intent.getBooleanExtra(USB_CONNECTED, false);
final boolean usbConfigured = intent.getBooleanExtra(USB_CONFIGURED, false);
final boolean rndisEnabled = intent.getBooleanExtra(USB_FUNCTION_RNDIS, false);
+ final boolean ncmEnabled = intent.getBooleanExtra(USB_FUNCTION_NCM, false);
mLog.log(String.format("USB bcast connected:%s configured:%s rndis:%s",
usbConnected, usbConfigured, rndisEnabled));
@@ -832,6 +841,8 @@
} else if (usbConfigured && rndisEnabled) {
// Tether if rndis is enabled and usb is configured.
tetherMatchingInterfaces(IpServer.STATE_TETHERED, TETHERING_USB);
+ } else if (usbConnected && ncmEnabled) {
+ tetherMatchingInterfaces(IpServer.STATE_LOCAL_ONLY, TETHERING_NCM);
}
mRndisEnabled = usbConfigured && rndisEnabled;
}
@@ -1133,6 +1144,16 @@
return TETHER_ERROR_NO_ERROR;
}
+ private int setNcmTethering(boolean enable) {
+ if (VDBG) Log.d(TAG, "setNcmTethering(" + enable + ")");
+ UsbManager usbManager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
+ synchronized (mPublicSync) {
+ usbManager.setCurrentFunctions(enable ? UsbManager.FUNCTION_NCM
+ : UsbManager.FUNCTION_NONE);
+ }
+ return TETHER_ERROR_NO_ERROR;
+ }
+
// TODO review API - figure out how to delete these entirely.
String[] getTetheredIfaces() {
ArrayList<String> list = new ArrayList<String>();
diff --git a/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 068c346..7e9e26f 100644
--- a/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -83,6 +83,7 @@
public final String[] tetherableWifiRegexs;
public final String[] tetherableWifiP2pRegexs;
public final String[] tetherableBluetoothRegexs;
+ public final String[] tetherableNcmRegexs;
public final boolean isDunRequired;
public final boolean chooseUpstreamAutomatically;
public final Collection<Integer> preferredUpstreamIfaceTypes;
@@ -103,6 +104,7 @@
Resources res = getResources(ctx, activeDataSubId);
tetherableUsbRegexs = getResourceStringArray(res, R.array.config_tether_usb_regexs);
+ tetherableNcmRegexs = getResourceStringArray(res, R.array.config_tether_ncm_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.
@@ -156,6 +158,11 @@
return matchesDownstreamRegexs(iface, tetherableBluetoothRegexs);
}
+ /** Check if interface is ncm */
+ public boolean isNcm(String iface) {
+ return matchesDownstreamRegexs(iface, tetherableNcmRegexs);
+ }
+
/** Check whether no ui entitlement application is available.*/
public boolean hasMobileHotspotProvisionApp() {
return !TextUtils.isEmpty(provisioningAppNoUi);
@@ -170,6 +177,7 @@
dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
dumpStringArray(pw, "tetherableWifiP2pRegexs", tetherableWifiP2pRegexs);
dumpStringArray(pw, "tetherableBluetoothRegexs", tetherableBluetoothRegexs);
+ dumpStringArray(pw, "tetherableNcmRegexs", tetherableNcmRegexs);
pw.print("isDunRequired: ");
pw.println(isDunRequired);
diff --git a/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
index d5cdd8a..4dd6830 100644
--- a/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
+++ b/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
@@ -22,6 +22,8 @@
import android.net.RouteInfo;
import android.net.util.InterfaceSet;
+import com.android.net.module.util.NetUtils;
+
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -85,7 +87,7 @@
private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) {
final RouteInfo ri = (lp != null)
- ? RouteInfo.selectBestRoute(lp.getAllRoutes(), dst)
+ ? NetUtils.selectBestRoute(lp.getAllRoutes(), dst)
: null;
return (ri != null) ? ri.getInterface() : null;
}
diff --git a/Tethering/tests/unit/src/android/net/TetheredClientTest.kt b/Tethering/tests/unit/src/android/net/TetheredClientTest.kt
new file mode 100644
index 0000000..83c19ec
--- /dev/null
+++ b/Tethering/tests/unit/src/android/net/TetheredClientTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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 android.net
+
+import android.net.InetAddresses.parseNumericAddress
+import android.net.TetheredClient.AddressInfo
+import android.net.TetheringManager.TETHERING_BLUETOOTH
+import android.net.TetheringManager.TETHERING_USB
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.testutils.assertParcelSane
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertNotEquals
+
+private val TEST_MACADDR = MacAddress.fromBytes(byteArrayOf(12, 23, 34, 45, 56, 67))
+private val TEST_OTHER_MACADDR = MacAddress.fromBytes(byteArrayOf(23, 34, 45, 56, 67, 78))
+private val TEST_ADDR1 = LinkAddress(parseNumericAddress("192.168.113.3"), 24)
+private val TEST_ADDR2 = LinkAddress(parseNumericAddress("fe80::1:2:3"), 64)
+private val TEST_ADDRINFO1 = AddressInfo(TEST_ADDR1, "test_hostname")
+private val TEST_ADDRINFO2 = AddressInfo(TEST_ADDR2, null)
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class TetheredClientTest {
+ @Test
+ fun testParceling() {
+ assertParcelSane(makeTestClient(), fieldCount = 3)
+ }
+
+ @Test
+ fun testEquals() {
+ assertEquals(makeTestClient(), makeTestClient())
+
+ // Different mac address
+ assertNotEquals(makeTestClient(), TetheredClient(
+ TEST_OTHER_MACADDR,
+ listOf(TEST_ADDRINFO1, TEST_ADDRINFO2),
+ TETHERING_BLUETOOTH))
+
+ // Different hostname
+ assertNotEquals(makeTestClient(), TetheredClient(
+ TEST_MACADDR,
+ listOf(AddressInfo(TEST_ADDR1, "test_other_hostname"), TEST_ADDRINFO2),
+ TETHERING_BLUETOOTH))
+
+ // Null hostname
+ assertNotEquals(makeTestClient(), TetheredClient(
+ TEST_MACADDR,
+ listOf(AddressInfo(TEST_ADDR1, null), TEST_ADDRINFO2),
+ TETHERING_BLUETOOTH))
+
+ // Missing address
+ assertNotEquals(makeTestClient(), TetheredClient(
+ TEST_MACADDR,
+ listOf(TEST_ADDRINFO2),
+ TETHERING_BLUETOOTH))
+
+ // Different type
+ assertNotEquals(makeTestClient(), TetheredClient(
+ TEST_MACADDR,
+ listOf(TEST_ADDRINFO1, TEST_ADDRINFO2),
+ TETHERING_BLUETOOTH))
+ }
+
+ @Test
+ fun testAddAddresses() {
+ val client1 = TetheredClient(TEST_MACADDR, listOf(TEST_ADDRINFO1), TETHERING_USB)
+ val client2 = TetheredClient(TEST_OTHER_MACADDR, listOf(TEST_ADDRINFO2), TETHERING_USB)
+ assertEquals(TetheredClient(
+ TEST_MACADDR,
+ listOf(TEST_ADDRINFO1, TEST_ADDRINFO2),
+ TETHERING_USB), client1.addAddresses(client2))
+ }
+
+ private fun makeTestClient() = TetheredClient(
+ TEST_MACADDR,
+ listOf(TEST_ADDRINFO1, TEST_ADDRINFO2),
+ TETHERING_BLUETOOTH)
+}
\ No newline at end of file
diff --git a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index e6a5521..4710287 100644
--- a/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -18,6 +18,7 @@
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
+import static android.hardware.usb.UsbManager.USB_FUNCTION_NCM;
import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
@@ -27,6 +28,7 @@
import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
+import static android.net.TetheringManager.TETHERING_NCM;
import static android.net.TetheringManager.TETHERING_USB;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
@@ -97,7 +99,7 @@
import android.net.util.InterfaceParams;
import android.net.util.NetworkConstants;
import android.net.util.SharedLog;
-import android.net.wifi.WifiConfiguration;
+import android.net.wifi.SoftApConfiguration;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pInfo;
@@ -151,6 +153,7 @@
private static final String TEST_USB_IFNAME = "test_rndis0";
private static final String TEST_WLAN_IFNAME = "test_wlan0";
private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
+ private static final String TEST_NCM_IFNAME = "test_ncm0";
private static final String TETHERING_NAME = "Tethering";
private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
@@ -252,9 +255,11 @@
ifName.equals(TEST_USB_IFNAME)
|| ifName.equals(TEST_WLAN_IFNAME)
|| ifName.equals(TEST_MOBILE_IFNAME)
- || ifName.equals(TEST_P2P_IFNAME));
+ || ifName.equals(TEST_P2P_IFNAME)
+ || ifName.equals(TEST_NCM_IFNAME));
final String[] ifaces = new String[] {
- TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME, TEST_P2P_IFNAME};
+ TEST_USB_IFNAME, TEST_WLAN_IFNAME, TEST_MOBILE_IFNAME, TEST_P2P_IFNAME,
+ TEST_NCM_IFNAME};
return new InterfaceParams(ifName, ArrayUtils.indexOf(ifaces, ifName) + IFINDEX_OFFSET,
MacAddress.ALL_ZEROS_ADDRESS);
}
@@ -428,13 +433,16 @@
.thenReturn(new String[]{ "test_p2p-p2p\\d-.*" });
when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
.thenReturn(new String[0]);
+ when(mResources.getStringArray(R.array.config_tether_ncm_regexs))
+ .thenReturn(new String[] { "test_ncm\\d" });
when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(false);
when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
false);
when(mNetd.interfaceGetList())
.thenReturn(new String[] {
- TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME});
+ TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME,
+ TEST_NCM_IFNAME});
when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
mInterfaceConfiguration = new InterfaceConfigurationParcel();
mInterfaceConfiguration.flags = new String[0];
@@ -524,11 +532,16 @@
P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST);
}
- private void sendUsbBroadcast(boolean connected, boolean configured, boolean rndisFunction) {
+ private void sendUsbBroadcast(boolean connected, boolean configured, boolean function,
+ int type) {
final Intent intent = new Intent(UsbManager.ACTION_USB_STATE);
intent.putExtra(USB_CONNECTED, connected);
intent.putExtra(USB_CONFIGURED, configured);
- intent.putExtra(USB_FUNCTION_RNDIS, rndisFunction);
+ if (type == TETHERING_USB) {
+ intent.putExtra(USB_FUNCTION_RNDIS, function);
+ } else {
+ intent.putExtra(USB_FUNCTION_NCM, function);
+ }
mServiceContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
@@ -578,6 +591,15 @@
verifyNoMoreInteractions(mWifiManager);
}
+ private void prepareNcmTethering() {
+ // Emulate startTethering(TETHERING_NCM) called
+ mTethering.startTethering(createTetheringRquestParcel(TETHERING_NCM), null);
+ mLooper.dispatchAll();
+ verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NCM);
+
+ mTethering.interfaceStatusChanged(TEST_NCM_IFNAME, true);
+ }
+
private void prepareUsbTethering(UpstreamNetworkState upstreamState) {
when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
@@ -600,7 +622,7 @@
verifyNoMoreInteractions(mNetd);
// Pretend we then receive USB configured broadcast.
- sendUsbBroadcast(true, true, true);
+ sendUsbBroadcast(true, true, true, TETHERING_USB);
mLooper.dispatchAll();
// Now we should see the start of tethering mechanics (in this case:
// tetherMatchingInterfaces() which starts by fetching all interfaces).
@@ -691,7 +713,7 @@
private void runUsbTethering(UpstreamNetworkState upstreamState) {
prepareUsbTethering(upstreamState);
- sendUsbBroadcast(true, true, true);
+ sendUsbBroadcast(true, true, true, TETHERING_USB);
mLooper.dispatchAll();
}
@@ -814,6 +836,29 @@
verify(mUpstreamNetworkMonitor, times(1)).setCurrentUpstream(upstreamState.network);
}
+ private void runNcmTethering() {
+ prepareNcmTethering();
+ sendUsbBroadcast(true, true, true, TETHERING_NCM);
+ mLooper.dispatchAll();
+ }
+
+ @Test
+ public void workingNcmTethering() throws Exception {
+ runNcmTethering();
+
+ verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
+ }
+
+ @Test
+ public void workingNcmTethering_LegacyDhcp() {
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ true);
+ sendConfigurationChanged();
+ runNcmTethering();
+
+ verify(mIpServerDependencies, never()).makeDhcpServer(any(), any(), any());
+ }
+
@Test
public void workingLocalOnlyHotspotEnrichedApBroadcastWithIfaceChanged() throws Exception {
workingLocalOnlyHotspotEnrichedApBroadcast(true);
@@ -827,12 +872,12 @@
// TODO: Test with and without interfaceStatusChanged().
@Test
public void failingWifiTetheringLegacyApBroadcast() throws Exception {
- when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+ when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startSoftAp(null);
+ verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
@@ -854,12 +899,12 @@
// TODO: Test with and without interfaceStatusChanged().
@Test
public void workingWifiTetheringEnrichedApBroadcast() throws Exception {
- when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+ when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startSoftAp(null);
+ verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
@@ -930,13 +975,13 @@
// TODO: Test with and without interfaceStatusChanged().
@Test
public void failureEnablingIpForwarding() throws Exception {
- when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+ when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(createTetheringRquestParcel(TETHERING_WIFI), null);
mLooper.dispatchAll();
- verify(mWifiManager, times(1)).startSoftAp(null);
+ verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
verifyNoMoreInteractions(mNetd);
@@ -1193,7 +1238,7 @@
when(mUpstreamNetworkMonitor.getCurrentPreferredUpstream()).thenReturn(upstreamState);
when(mUpstreamNetworkMonitor.selectPreferredUpstreamType(any()))
.thenReturn(upstreamState);
- when(mWifiManager.startSoftAp(any(WifiConfiguration.class))).thenReturn(true);
+ when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
mTethering.interfaceStatusChanged(TEST_WLAN_IFNAME, true);
mLooper.dispatchAll();
tetherState = callback.pollTetherStatesChanged();