diff --git a/api/system-current.txt b/api/system-current.txt index 93f962dda03da..b4825bfe36954 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3803,10 +3803,12 @@ package android.hardware.usb { method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long); field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED"; field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE"; + field public static final long FUNCTION_NCM = 1024L; // 0x400L field public static final long FUNCTION_NONE = 0L; // 0x0L field public static final long FUNCTION_RNDIS = 32L; // 0x20L field public static final String USB_CONFIGURED = "configured"; field public static final String USB_CONNECTED = "connected"; + field public static final String USB_FUNCTION_NCM = "ncm"; field public static final String USB_FUNCTION_RNDIS = "rndis"; } @@ -6564,6 +6566,7 @@ package android.net { field public static final String EXTRA_ERRORED_TETHER = "erroredArray"; field public static final int TETHERING_BLUETOOTH = 2; // 0x2 field public static final int TETHERING_INVALID = -1; // 0xffffffff + field public static final int TETHERING_NCM = 4; // 0x4 field public static final int TETHERING_USB = 1; // 0x1 field public static final int TETHERING_WIFI = 0; // 0x0 field public static final int TETHERING_WIFI_P2P = 3; // 0x3 diff --git a/api/test-current.txt b/api/test-current.txt index 200d465c34e56..76af40318fb81 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1759,6 +1759,7 @@ package android.net { field public static final String EXTRA_ERRORED_TETHER = "erroredArray"; field public static final int TETHERING_BLUETOOTH = 2; // 0x2 field public static final int TETHERING_INVALID = -1; // 0xffffffff + field public static final int TETHERING_NCM = 4; // 0x4 field public static final int TETHERING_USB = 1; // 0x1 field public static final int TETHERING_WIFI = 0; // 0x0 field public static final int TETHERING_WIFI_P2P = 3; // 0x3 diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java index 827353b1802c5..086db1010f737 100644 --- a/core/java/android/hardware/usb/UsbManager.java +++ b/core/java/android/hardware/usb/UsbManager.java @@ -261,6 +261,15 @@ public class UsbManager { */ public static final String USB_FUNCTION_ACCESSORY = "accessory"; + /** + * Name of the NCM USB function. + * Used in extras for the {@link #ACTION_USB_STATE} broadcast + * + * {@hide} + */ + @SystemApi + public static final String USB_FUNCTION_NCM = "ncm"; + /** * Name of extra for {@link #ACTION_USB_PORT_CHANGED} * containing the {@link UsbPort} object for the port. @@ -367,8 +376,15 @@ public class UsbManager { */ public static final long FUNCTION_ADB = GadgetFunction.ADB; + /** + * Code for the ncm source usb function. + * {@hide} + */ + @SystemApi + public static final long FUNCTION_NCM = 1 << 10; + private static final long SETTABLE_FUNCTIONS = FUNCTION_MTP | FUNCTION_PTP | FUNCTION_RNDIS - | FUNCTION_MIDI; + | FUNCTION_MIDI | FUNCTION_NCM; private static final Map FUNCTION_NAME_TO_CODE = new HashMap<>(); @@ -380,6 +396,7 @@ public class UsbManager { FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_ACCESSORY, FUNCTION_ACCESSORY); FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_AUDIO_SOURCE, FUNCTION_AUDIO_SOURCE); FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_ADB, FUNCTION_ADB); + FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_NCM, FUNCTION_NCM); } private final Context mContext; @@ -954,6 +971,9 @@ public class UsbManager { if ((functions & FUNCTION_AUDIO_SOURCE) != 0) { joiner.add(UsbManager.USB_FUNCTION_AUDIO_SOURCE); } + if ((functions & FUNCTION_NCM) != 0) { + joiner.add(UsbManager.USB_FUNCTION_NCM); + } if ((functions & FUNCTION_ADB) != 0) { joiner.add(UsbManager.USB_FUNCTION_ADB); } diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 8dacecc4ff2a3..79c6930404168 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -130,6 +130,12 @@ public class TetheringManager { */ 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; diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml index 19e8d696c39a5..c489cbcd5a1c3 100644 --- a/packages/Tethering/res/values/config.xml +++ b/packages/Tethering/res/values/config.xml @@ -28,6 +28,12 @@ "rndis\\d" + + + + diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml index e089d9d19950f..fe025c7ac993c 100644 --- a/packages/Tethering/res/values/overlayable.xml +++ b/packages/Tethering/res/values/overlayable.xml @@ -17,6 +17,7 @@ + diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java index 0491ad7c3413e..57cc4dd554f1b 100644 --- a/packages/Tethering/src/android/net/ip/IpServer.java +++ b/packages/Tethering/src/android/net/ip/IpServer.java @@ -416,7 +416,8 @@ public class IpServer extends StateMachine { 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/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java index c47f2d6d4db21..02ba17e4b48b1 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java @@ -19,6 +19,7 @@ package com.android.server.connectivity.tethering; 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_AVAILABLE_TETHER; 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 @@ public class Tethering { 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 @@ public class Tethering { 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); @@ -805,6 +813,7 @@ public class Tethering { 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 @@ public class Tethering { } 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 @@ public class Tethering { 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 list = new ArrayList(); diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java index 068c346fbfc15..7e9e26f5af40c 100644 --- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -83,6 +83,7 @@ public class TetheringConfiguration { 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 preferredUpstreamIfaceTypes; @@ -103,6 +104,7 @@ public class TetheringConfiguration { 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 @@ public class TetheringConfiguration { 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 @@ public class TetheringConfiguration { 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/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java index 6fc0c659f3979..4710287f33f38 100644 --- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java @@ -18,6 +18,7 @@ package com.android.server.connectivity.tethering; 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.ACTION_TETHER_STATE_CHANGED; 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; @@ -151,6 +153,7 @@ public class TetheringTest { 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 @@ public class TetheringTest { 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 @@ public class TetheringTest { .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 @@ public class TetheringTest { 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 @@ public class TetheringTest { 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 @@ public class TetheringTest { 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 @@ public class TetheringTest { private void runUsbTethering(UpstreamNetworkState upstreamState) { prepareUsbTethering(upstreamState); - sendUsbBroadcast(true, true, true); + sendUsbBroadcast(true, true, true, TETHERING_USB); mLooper.dispatchAll(); } @@ -814,6 +836,29 @@ public class TetheringTest { 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);