Merge "Use new bluetooth API to enable/disable bt tethering after T"
diff --git a/Tethering/src/android/net/ip/IpServer.java b/Tethering/src/android/net/ip/IpServer.java
index b4228da..2bb19db 100644
--- a/Tethering/src/android/net/ip/IpServer.java
+++ b/Tethering/src/android/net/ip/IpServer.java
@@ -614,10 +614,8 @@
             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 @@
         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);
     }
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 55c24d3..db9a64f 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -134,7 +134,12 @@
 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 @@
     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 @@
         }
     }
 
-    // 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 @@
                             TETHERING_BLUETOOTH);
                 }
                 mPendingPanRequests.clear();
+                mBluetoothIfaceRequest = null;
+                mBluetoothCallback = null;
+                maybeDisableBluetoothIpServing();
             });
         }
 
@@ -779,7 +792,11 @@
 
     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 @@
         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);
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 7df9475..c1a747e 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -18,6 +18,7 @@
 
 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 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 @@
             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);
+    }
 }
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 2f2cde0..267c376 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -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_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 @@
     }
 
     @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 @@
         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 @@
         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 @@
         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 @@
         initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
         dispatchTetherConnectionChanged(UPSTREAM_IFACE);
 
-        assertDhcpStarted(mBluetoothPrefix);
+        if (isAtLeastT()) {
+            assertDhcpStarted(PrefixUtils.asIpPrefix(mTestAddress));
+        } else {
+            assertDhcpStarted(mBluetoothPrefix);
+        }
     }
 
     @Test
@@ -1371,7 +1389,6 @@
         for (String flag : flags) {
             if (flag.equals(match)) return true;
         }
-        fail("Missing flag: " + match);
         return false;
     }
 
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 40d133a..e4dbc7d 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -62,6 +62,7 @@
 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.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.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 @@
     @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 @@
     private PrivateAddressCoordinator mPrivateAddressCoordinator;
     private SoftApCallback mSoftApCallback;
     private UpstreamNetworkMonitor mUpstreamNetworkMonitor;
+    private TetheredInterfaceCallbackShim mTetheredInterfaceCallbackShim;
 
     private TestConnectivityManager mCm;
 
@@ -483,13 +493,23 @@
             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 @@
 
     @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 @@
         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 @@
     }
 
     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 @@
         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 @@
             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);