Merge "Reuse BluetoothPan object and use it under tethering handler thread"

This commit is contained in:
Mark Chien
2021-12-16 08:40:39 +00:00
committed by Gerrit Code Review
2 changed files with 164 additions and 48 deletions

View File

@@ -123,6 +123,7 @@ import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -267,6 +268,9 @@ public class Tethering {
private String mConfiguredEthernetIface;
private EthernetCallback mEthernetCallback;
private SettingsObserver mSettingsObserver;
private BluetoothPan mBluetoothPan;
private PanServiceListener mBluetoothPanListener;
private ArrayList<Pair<Boolean, IIntResultListener>> mPendingPanRequests;
public Tethering(TetheringDependencies deps) {
mLog.mark("Tethering.constructed");
@@ -276,6 +280,11 @@ public class Tethering {
mLooper = mDeps.getTetheringLooper();
mNotificationUpdater = mDeps.getNotificationUpdater(mContext, mLooper);
// This is intended to ensrure that if something calls startTethering(bluetooth) just after
// bluetooth is enabled. Before onServiceConnected is called, store the calls into this
// list and handle them as soon as onServiceConnected is called.
mPendingPanRequests = new ArrayList<>();
mTetherStates = new ArrayMap<>();
mConnectedClientsTracker = new ConnectedClientsTracker();
@@ -701,35 +710,82 @@ public class Tethering {
return;
}
adapter.getProfileProxy(mContext, new ServiceListener() {
@Override
public void onServiceDisconnected(int profile) { }
if (mBluetoothPanListener != null && mBluetoothPanListener.isConnected()) {
// The PAN service is connected. Enable or disable bluetooth tethering.
// When bluetooth tethering is enabled, any time a PAN client pairs with this
// host, bluetooth will bring up a bt-pan interface and notify tethering to
// enable IP serving.
setBluetoothTetheringSettings(mBluetoothPan, enable, listener);
return;
}
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
// Clear identify is fine because caller already pass tethering permission at
// ConnectivityService#startTethering()(or stopTethering) before the control comes
// here. Bluetooth will check tethering permission again that there is
// Context#getOpPackageName() under BluetoothPan#setBluetoothTethering() to get
// caller's package name for permission check.
// Calling BluetoothPan#setBluetoothTethering() here means the package name always
// be system server. If calling identity is not cleared, that package's uid might
// not match calling uid and end up in permission denied.
final long identityToken = Binder.clearCallingIdentity();
try {
((BluetoothPan) proxy).setBluetoothTethering(enable);
} finally {
Binder.restoreCallingIdentity(identityToken);
// The reference of IIntResultListener should only exist when application want to start
// tethering but tethering is not bound to pan service yet. Even if the calling process
// dies, the referenice of IIntResultListener would still keep in mPendingPanRequests. Once
// tethering bound to pan service (onServiceConnected) or bluetooth just crash
// (onServiceDisconnected), all the references from mPendingPanRequests would be cleared.
mPendingPanRequests.add(new Pair(enable, listener));
// Bluetooth tethering is not a popular feature. To avoid bind to bluetooth pan service all
// the time but user never use bluetooth tethering. mBluetoothPanListener is created first
// time someone calls a bluetooth tethering method (even if it's just to disable tethering
// when it's already disabled) and never unset after that.
if (mBluetoothPanListener == null) {
mBluetoothPanListener = new PanServiceListener();
adapter.getProfileProxy(mContext, mBluetoothPanListener, BluetoothProfile.PAN);
}
}
private class PanServiceListener implements ServiceListener {
private boolean mIsConnected = false;
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
// Posting this to handling onServiceConnected in tethering handler thread may have
// race condition that bluetooth service may disconnected when tethering thread
// actaully handle onServiceconnected. If this race happen, calling
// BluetoothPan#setBluetoothTethering would silently fail. It is fine because pan
// service is unreachable and both bluetooth and bluetooth tethering settings are off.
mHandler.post(() -> {
mBluetoothPan = (BluetoothPan) proxy;
mIsConnected = true;
for (Pair<Boolean, IIntResultListener> request : mPendingPanRequests) {
setBluetoothTetheringSettings(mBluetoothPan, request.first, request.second);
}
// TODO: Enabling bluetooth tethering can fail asynchronously here.
// We should figure out a way to bubble up that failure instead of sending success.
final int result = (((BluetoothPan) proxy).isTetheringOn() == enable)
? TETHER_ERROR_NO_ERROR
: TETHER_ERROR_INTERNAL_ERROR;
sendTetherResult(listener, result, TETHERING_BLUETOOTH);
adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);
}
}, BluetoothProfile.PAN);
mPendingPanRequests.clear();
});
}
@Override
public void onServiceDisconnected(int profile) {
mHandler.post(() -> {
// onServiceDisconnected means Bluetooth is off (or crashed) and is not
// reachable before next onServiceConnected.
mIsConnected = false;
for (Pair<Boolean, IIntResultListener> request : mPendingPanRequests) {
sendTetherResult(request.second, TETHER_ERROR_SERVICE_UNAVAIL,
TETHERING_BLUETOOTH);
}
mPendingPanRequests.clear();
});
}
public boolean isConnected() {
return mIsConnected;
}
}
private void setBluetoothTetheringSettings(@NonNull final BluetoothPan bluetoothPan,
final boolean enable, final IIntResultListener listener) {
bluetoothPan.setBluetoothTethering(enable);
// Enabling bluetooth tethering settings can silently fail. Send internal error if the
// result is not expected.
final int result = bluetoothPan.isTetheringOn() == enable
? TETHER_ERROR_NO_ERROR : TETHER_ERROR_INTERNAL_ERROR;
sendTetherResult(listener, result, TETHERING_BLUETOOTH);
}
private int setEthernetTethering(final boolean enable) {

View File

@@ -2558,10 +2558,10 @@ public class TetheringTest {
@Test
public void testBluetoothTethering() throws Exception {
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
mLooper.dispatchAll();
verifySetBluetoothTethering(true);
verifySetBluetoothTethering(true /* enable */, true /* bindToPanService */);
result.assertHasResult();
mTethering.interfaceAdded(TEST_BT_IFNAME);
@@ -2574,6 +2574,64 @@ public class TetheringTest {
mLooper.dispatchAll();
tetherResult.assertHasResult();
verifyNetdCommandForBtSetup();
// Turning tethering on a second time does not bind to the PAN service again, since it's
// already bound.
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
final ResultListener secondResult = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), secondResult);
mLooper.dispatchAll();
verifySetBluetoothTethering(true /* enable */, false /* bindToPanService */);
secondResult.assertHasResult();
mockBluetoothSettings(true /* bluetoothOn */, false /* tetheringOn */);
mTethering.stopTethering(TETHERING_BLUETOOTH);
mLooper.dispatchAll();
final ResultListener untetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.untether(TEST_BT_IFNAME, untetherResult);
mLooper.dispatchAll();
untetherResult.assertHasResult();
verifySetBluetoothTethering(false /* enable */, false /* bindToPanService */);
verifyNetdCommandForBtTearDown();
}
@Test
public void testBluetoothServiceDisconnects() throws Exception {
final ResultListener result = new ResultListener(TETHER_ERROR_NO_ERROR);
mockBluetoothSettings(true /* bluetoothOn */, true /* tetheringOn */);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_BLUETOOTH), result);
mLooper.dispatchAll();
ServiceListener panListener = verifySetBluetoothTethering(true /* enable */,
true /* bindToPanService */);
result.assertHasResult();
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();
verifyNetdCommandForBtSetup();
panListener.onServiceDisconnected(BluetoothProfile.PAN);
mTethering.interfaceStatusChanged(TEST_BT_IFNAME, false);
mLooper.dispatchAll();
verifyNetdCommandForBtTearDown();
}
private void mockBluetoothSettings(boolean bluetoothOn, boolean tetheringOn) {
when(mBluetoothAdapter.isEnabled()).thenReturn(bluetoothOn);
when(mBluetoothPan.isTetheringOn()).thenReturn(tetheringOn);
}
private void verifyNetdCommandForBtSetup() throws Exception {
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),
@@ -2584,39 +2642,41 @@ public class TetheringTest {
anyString(), anyString());
verifyNoMoreInteractions(mNetd);
reset(mNetd);
}
when(mBluetoothAdapter.isEnabled()).thenReturn(true);
mTethering.stopTethering(TETHERING_BLUETOOTH);
mLooper.dispatchAll();
final ResultListener untetherResult = new ResultListener(TETHER_ERROR_NO_ERROR);
mTethering.untether(TEST_BT_IFNAME, untetherResult);
mLooper.dispatchAll();
untetherResult.assertHasResult();
verifySetBluetoothTethering(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));
verify(mNetd).tetherStop();
verify(mNetd).ipfwdDisableForwarding(TETHERING_NAME);
verifyNoMoreInteractions(mNetd);
}
private void verifySetBluetoothTethering(final boolean enable) {
final ArgumentCaptor<ServiceListener> listenerCaptor =
ArgumentCaptor.forClass(ServiceListener.class);
// 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) {
ServiceListener listener = null;
verify(mBluetoothAdapter).isEnabled();
verify(mBluetoothAdapter).getProfileProxy(eq(mServiceContext), listenerCaptor.capture(),
eq(BluetoothProfile.PAN));
final ServiceListener listener = listenerCaptor.getValue();
when(mBluetoothPan.isTetheringOn()).thenReturn(enable);
listener.onServiceConnected(BluetoothProfile.PAN, mBluetoothPan);
if (bindToPanService) {
final ArgumentCaptor<ServiceListener> listenerCaptor =
ArgumentCaptor.forClass(ServiceListener.class);
verify(mBluetoothAdapter).getProfileProxy(eq(mServiceContext), listenerCaptor.capture(),
eq(BluetoothProfile.PAN));
listener = listenerCaptor.getValue();
listener.onServiceConnected(BluetoothProfile.PAN, mBluetoothPan);
mLooper.dispatchAll();
} else {
verify(mBluetoothAdapter, never()).getProfileProxy(eq(mServiceContext), any(),
anyInt());
}
verify(mBluetoothPan).setBluetoothTethering(enable);
verify(mBluetoothPan).isTetheringOn();
verify(mBluetoothAdapter).closeProfileProxy(eq(BluetoothProfile.PAN), eq(mBluetoothPan));
verifyNoMoreInteractions(mBluetoothAdapter, mBluetoothPan);
reset(mBluetoothAdapter, mBluetoothPan);
return listener;
}
private void runDualStackUsbTethering(final String expectedIface) throws Exception {