Merge "Use new bluetooth API to enable/disable bt tethering after T"

This commit is contained in:
Mark Chien 2022-01-21 08:43:53 +00:00 • committed by Gerrit Code Review
commit ceeebae37e
5 changed files with 226 additions and 27 deletions

View file

@ -614,10 +614,8 @@ public class IpServer extends StateMachine {
return false;
}
if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
// BT configures the interface elsewhere: only start DHCP.
// TODO: make all tethering types behave the same way, and delete the bluetooth
// code that calls into NetworkManagementService directly.
if (shouldNotConfigureBluetoothInterface()) {
// Interface was already configured elsewhere, only start DHCP.
return configureDhcp(enabled, mIpv4Address, null /* clientAddress */);
}
@ -651,12 +649,15 @@ public class IpServer extends StateMachine {
return configureDhcp(enabled, mIpv4Address, mStaticIpv4ClientAddr);
}
private boolean shouldNotConfigureBluetoothInterface() {
// Before T, bluetooth tethering configures the interface elsewhere.
return (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) && !SdkLevel.isAtLeastT();
}
private LinkAddress requestIpv4Address(final boolean useLastAddress) {
if (mStaticIpv4ServerAddr != null) return mStaticIpv4ServerAddr;
if (mInterfaceType == TetheringManager.TETHERING_BLUETOOTH) {
return new LinkAddress(BLUETOOTH_IFACE_ADDR);
}
if (shouldNotConfigureBluetoothInterface()) return new LinkAddress(BLUETOOTH_IFACE_ADDR);
return mPrivateAddressCoordinator.requestDownstreamAddress(this, useLastAddress);
}

View file

@ -134,7 +134,12 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.tethering.util.InterfaceSet;
import com.android.networkstack.tethering.util.PrefixUtils;
import com.android.networkstack.tethering.util.TetheringUtils;
@ -265,8 +270,11 @@ public class Tethering {
private int mOffloadStatus = TETHER_HARDWARE_OFFLOAD_STOPPED;
private EthernetManager.TetheredInterfaceRequest mEthernetIfaceRequest;
private TetheredInterfaceRequestShim mBluetoothIfaceRequest;
private String mConfiguredEthernetIface;
private String mConfiguredBluetoothIface;
private EthernetCallback mEthernetCallback;
private TetheredInterfaceCallbackShim mBluetoothCallback;
private SettingsObserver mSettingsObserver;
private BluetoothPan mBluetoothPan;
private PanServiceListener mBluetoothPanListener;
@ -533,14 +541,16 @@ public class Tethering {
}
}
// This method needs to exist because TETHERING_BLUETOOTH and TETHERING_WIGIG can't use
// enableIpServing.
// This method needs to exist because TETHERING_BLUETOOTH before Android T and TETHERING_WIGIG
// can't use enableIpServing.
private void processInterfaceStateChange(final String iface, boolean enabled) {
// Do not listen to USB interface state changes or USB interface add/removes. USB tethering
// is driven only by USB_ACTION broadcasts.
final int type = ifaceNameToType(iface);
if (type == TETHERING_USB || type == TETHERING_NCM) return;
if (type == TETHERING_BLUETOOTH && SdkLevel.isAtLeastT()) return;
if (enabled) {
ensureIpServerStarted(iface);
} else {
@ -769,6 +779,9 @@ public class Tethering {
TETHERING_BLUETOOTH);
}
mPendingPanRequests.clear();
mBluetoothIfaceRequest = null;
mBluetoothCallback = null;
maybeDisableBluetoothIpServing();
});
}
@ -779,7 +792,11 @@ public class Tethering {
private void setBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
final boolean enable, final IIntResultListener listener) {
bluetoothPan.setBluetoothTethering(enable);
if (SdkLevel.isAtLeastT()) {
changeBluetoothTetheringSettings(bluetoothPan, enable);
} else {
changeBluetoothTetheringSettingsPreT(bluetoothPan, enable);
}
// Enabling bluetooth tethering settings can silently fail. Send internal error if the
// result is not expected.
@ -788,6 +805,68 @@ public class Tethering {
sendTetherResult(listener, result, TETHERING_BLUETOOTH);
}
private void changeBluetoothTetheringSettingsPreT(@NonNull final BluetoothPan bluetoothPan,
final boolean enable) {
bluetoothPan.setBluetoothTethering(enable);
}
private void changeBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
final boolean enable) {
final BluetoothPanShim panShim = mDeps.getBluetoothPanShim(bluetoothPan);
if (enable) {
if (mBluetoothIfaceRequest != null) {
Log.d(TAG, "Bluetooth tethering settings already enabled");
return;
}
mBluetoothCallback = new BluetoothCallback();
try {
mBluetoothIfaceRequest = panShim.requestTetheredInterface(mExecutor,
mBluetoothCallback);
} catch (UnsupportedApiLevelException e) {
Log.wtf(TAG, "Use unsupported API, " + e);
}
} else {
if (mBluetoothIfaceRequest == null) {
Log.d(TAG, "Bluetooth tethering settings already disabled");
return;
}
mBluetoothIfaceRequest.release();
mBluetoothIfaceRequest = null;
mBluetoothCallback = null;
// If bluetooth request is released, tethering won't able to receive
// onUnavailable callback, explicitly disable bluetooth IpServer manually.
maybeDisableBluetoothIpServing();
}
}
// BluetoothCallback is only called after T. Before T, PanService would call tether/untether to
// notify bluetooth interface status.
private class BluetoothCallback implements TetheredInterfaceCallbackShim {
@Override
public void onAvailable(String iface) {
if (this != mBluetoothCallback) return;
enableIpServing(TETHERING_BLUETOOTH, iface, getRequestedState(TETHERING_BLUETOOTH));
mConfiguredBluetoothIface = iface;
}
@Override
public void onUnavailable() {
if (this != mBluetoothCallback) return;
maybeDisableBluetoothIpServing();
}
}
private void maybeDisableBluetoothIpServing() {
if (mConfiguredBluetoothIface == null) return;
ensureIpServerStopped(mConfiguredBluetoothIface);
mConfiguredBluetoothIface = null;
}
private int setEthernetTethering(final boolean enable) {
final EthernetManager em = (EthernetManager) mContext.getSystemService(
Context.ETHERNET_SERVICE);

View file

@ -18,6 +18,7 @@ package com.android.networkstack.tethering;
import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.content.Context;
import android.net.INetd;
import android.net.ip.IpServer;
@ -31,6 +32,8 @@ import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.android.internal.util.StateMachine;
import com.android.networkstack.apishim.BluetoothPanShimImpl;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import java.util.ArrayList;
@ -158,4 +161,13 @@ public abstract class TetheringDependencies {
TetheringConfiguration cfg) {
return new PrivateAddressCoordinator(ctx, cfg);
}
/**
* Get BluetoothPanShim object to enable/disable bluetooth tethering.
*
* TODO: use BluetoothPan directly when mainline module is built with API 32.
*/
public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
return BluetoothPanShimImpl.newInstance(pan);
}
}

View file

@ -16,6 +16,7 @@
package android.net.ip;
import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.TetheringManager.TETHERING_BLUETOOTH;
@ -33,6 +34,7 @@ import static android.net.ip.IpServer.STATE_TETHERED;
import static android.net.ip.IpServer.STATE_UNAVAILABLE;
import static android.system.OsConstants.ETH_P_IPV6;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH;
@ -400,11 +402,16 @@ public class IpServerTest {
}
@Test
public void canBeTethered() throws Exception {
public void canBeTetheredAsBluetooth() throws Exception {
initStateMachine(TETHERING_BLUETOOTH);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
InOrder inOrder = inOrder(mCallback, mNetd);
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
if (isAtLeastT()) {
inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
}
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
// One for ipv4 route, one for ipv6 link local route.
@ -426,7 +433,13 @@ public class IpServerTest {
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
// One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only
// happen after T. Before T, the interface configuration control in bluetooth side.
if (isAtLeastT()) {
inOrder.verify(mNetd).interfaceSetCfg(
argThat(cfg -> assertContainsFlag(cfg.flags, IF_STATE_DOWN)));
}
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> cfg.flags.length == 0));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
@ -443,7 +456,7 @@ public class IpServerTest {
InOrder inOrder = inOrder(mCallback, mNetd, mAddressCoordinator);
inOrder.verify(mAddressCoordinator).requestDownstreamAddress(any(), eq(true));
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
@ -587,7 +600,8 @@ public class IpServerTest {
inOrder.verify(mNetd).tetherApplyDnsInterfaces();
inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mAddressCoordinator).releaseDownstream(any());
inOrder.verify(mBpfCoordinator).tetherOffloadClientClear(mIpServer);
inOrder.verify(mBpfCoordinator).stopMonitoring(mIpServer);
@ -683,7 +697,11 @@ public class IpServerTest {
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
assertDhcpStarted(mBluetoothPrefix);
if (isAtLeastT()) {
assertDhcpStarted(PrefixUtils.asIpPrefix(mTestAddress));
} else {
assertDhcpStarted(mBluetoothPrefix);
}
}
@Test
@ -1371,7 +1389,6 @@ public class IpServerTest {
for (String flag : flags) {
if (flag.equals(match)) return true;
}
fail("Missing flag: " + match);
return false;
}

View file

@ -62,6 +62,7 @@ import static android.system.OsConstants.RT_SCOPE_UNIVERSE;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.modules.utils.build.SdkLevel.isAtLeastS;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
import static com.android.networkstack.tethering.OffloadHardwareInterface.OFFLOAD_HAL_VERSION_1_0;
@ -81,6 +82,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Matchers.anyInt;
@ -182,6 +185,10 @@ import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.net.module.util.CollectionUtils;
import com.android.networkstack.apishim.common.BluetoothPanShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceCallbackShim;
import com.android.networkstack.apishim.common.BluetoothPanShim.TetheredInterfaceRequestShim;
import com.android.networkstack.apishim.common.UnsupportedApiLevelException;
import com.android.networkstack.tethering.TestConnectivityManager.TestNetworkAgent;
import com.android.testutils.MiscAsserts;
@ -261,6 +268,8 @@ public class TetheringTest {
@Mock private PackageManager mPackageManager;
@Mock private BluetoothAdapter mBluetoothAdapter;
@Mock private BluetoothPan mBluetoothPan;
@Mock private BluetoothPanShim mBluetoothPanShim;
@Mock private TetheredInterfaceRequestShim mTetheredInterfaceRequestShim;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@ -285,6 +294,7 @@ public class TetheringTest {
private PrivateAddressCoordinator mPrivateAddressCoordinator;
private SoftApCallback mSoftApCallback;
private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
private TestConnectivityManager mCm;
@ -483,13 +493,23 @@ public class TetheringTest {
return false;
}
@Override
public PrivateAddressCoordinator getPrivateAddressCoordinator(Context ctx,
TetheringConfiguration cfg) {
mPrivateAddressCoordinator = super.getPrivateAddressCoordinator(ctx, cfg);
return mPrivateAddressCoordinator;
}
@Override
public BluetoothPanShim getBluetoothPanShim(BluetoothPan pan) {
try {
when(mBluetoothPanShim.requestTetheredInterface(
any(), any())).thenReturn(mTetheredInterfaceRequestShim);
} catch (UnsupportedApiLevelException e) {
fail("BluetoothPan#requestTetheredInterface is not supported");
}
return mBluetoothPanShim;
}
}
private static LinkProperties buildUpstreamLinkProperties(String interfaceName,
@ -2557,6 +2577,44 @@ public class TetheringTest {
@Test
public void testBluetoothTethering() throws Exception {
// Switch to @IgnoreUpTo(Build.VERSION_CODES.S_V2) when it is available for AOSP.
assumeTrue(isAtLeastT());
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
result.assertHasResult();
mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
mLooper.dispatchAll();
verifyNetdCommandForBtSetup();
// If PAN disconnect, tethering should also be stopped.
mTetheredInterfaceCallbackShim.onUnavailable();
mLooper.dispatchAll();
verifyNetdCommandForBtTearDown();
// Tethering could restart if PAN reconnect.
mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
mLooper.dispatchAll();
verifyNetdCommandForBtSetup();
// Pretend that bluetooth tethering was disabled.
mockBluetoothSettings(true /* bluetoothOn */, false /* tetheringOn */);
mTethering.stopTethering(TETHERING_BLUETOOTH);
mLooper.dispatchAll();
verifySetBluetoothTethering(false /* enable */, false /* bindToPanService */);
verifyNetdCommandForBtTearDown();
}
@Test
public void testBluetoothTetheringBeforeT() throws Exception {
// Switch to @IgnoreAfter(Build.VERSION_CODES.S_V2) when it is available for AOSP.
assumeFalse(isAtLeastT());
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
@ -2610,12 +2668,17 @@ public class TetheringTest {
mTethering.interfaceAdded(TEST_BT_IFNAME);
mLooper.dispatchAll();
mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
mTethering.interfaceStatusChanged(TEST_BT_IFNAME, true);
final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.tether(TEST_BT_IFNAME, IpServer.STATE_TETHERED, tetherResult);
mLooper.dispatchAll();
tetherResult.assertHasResult();
if (isAtLeastT()) {
mTetheredInterfaceCallbackShim.onAvailable(TEST_BT_IFNAME);
mLooper.dispatchAll();
} else {
mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
mTethering.interfaceStatusChanged(TEST_BT_IFNAME, true);
final ResultListener tetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.tether(TEST_BT_IFNAME, IpServer.STATE_TETHERED, tetherResult);
mLooper.dispatchAll();
tetherResult.assertHasResult();
}
verifyNetdCommandForBtSetup();
@ -2632,6 +2695,10 @@ public class TetheringTest {
}
private void verifyNetdCommandForBtSetup() throws Exception {
if (isAtLeastT()) {
verify(mNetd).interfaceSetCfg(argThat(cfg -> TEST_BT_IFNAME.equals(cfg.ifName)
&& assertContainsFlag(cfg.flags, INetd.IF_STATE_UP)));
}
verify(mNetd).tetherInterfaceAdd(TEST_BT_IFNAME);
verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_BT_IFNAME),
@ -2644,19 +2711,30 @@ public class TetheringTest {
reset(mNetd);
}
private boolean assertContainsFlag(String[] flags, String match) {
for (String flag : flags) {
if (flag.equals(match)) return true;
}
return false;
}
private void verifyNetdCommandForBtTearDown() throws Exception {
verify(mNetd).tetherApplyDnsInterfaces();
verify(mNetd).tetherInterfaceRemove(TEST_BT_IFNAME);
verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_BT_IFNAME);
verify(mNetd).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
// One is ipv4 address clear (set to 0.0.0.0), another is set interface down which only
// happen after T. Before T, the interface configuration control in bluetooth side.
verify(mNetd, times(isAtLeastT() ? 2 : 1)).interfaceSetCfg(
any(InterfaceConfigurationParcel.class));
verify(mNetd).tetherStop();
verify(mNetd).ipfwdDisableForwarding(TETHERING_NAME);
reset(mNetd);
}
// If bindToPanService is true, this function would return ServiceListener which could notify
// PanService is connected or disconnected.
private ServiceListener verifySetBluetoothTethering(final boolean enable,
final boolean bindToPanService) {
final boolean bindToPanService) throws Exception {
ServiceListener listener = null;
verify(mBluetoothAdapter).isEnabled();
if (bindToPanService) {
@ -2671,7 +2749,19 @@ public class TetheringTest {
verify(mBluetoothAdapter, never()).getProfileProxy(eq(mServiceContext), any(),
anyInt());
}
verify(mBluetoothPan).setBluetoothTethering(enable);
if (isAtLeastT()) {
if (enable) {
final ArgumentCaptor<TetheredInterfaceCallbackShim> callbackCaptor =
ArgumentCaptor.forClass(TetheredInterfaceCallbackShim.class);
verify(mBluetoothPanShim).requestTetheredInterface(any(), callbackCaptor.capture());
mTetheredInterfaceCallbackShim = callbackCaptor.getValue();
} else {
verify(mTetheredInterfaceRequestShim).release();
}
} else {
verify(mBluetoothPan).setBluetoothTethering(enable);
}
verify(mBluetoothPan).isTetheringOn();
verifyNoMoreInteractions(mBluetoothAdapter, mBluetoothPan);
reset(mBluetoothAdapter, mBluetoothPan);