Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8a4b4d5d authored by William Escande's avatar William Escande
Browse files

Cts Tests: Wait correctly for state transition

Previously the code interpret BLE_ON as being OFF. This is not correct
and generate test flakiness

Test: atest CtsPermissionUiTestCases
Test: atest CtsBluetoothTestCases
Test: atest CtsNearbyFastPairTestCases
Flag: TEST_ONLY
Bug: 339585576
Bug: 340270187
Change-Id: I26c678c1d956916ad86bc676f3ba38f2f64a9951
parent 088248a2
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -31,6 +31,15 @@ java_library {
        "src/BlockingBluetoothAdapter.kt",
    ],
    sdk_version: "test_current",
    // Keep it public for now to avoid merge conflicts
    visibility: ["//visibility:public"],
    visibility: [
        "//cts/tests/tests/bluetooth",
        "//packages/modules/Bluetooth/framework/tests/bumble",

        // TODO: b/339938196 -- remove export for other modules and test
        "//cts/tests/tests/appop",
        "//packages/modules/Connectivity/nearby/tests/cts/fastpair",
        "//packages/modules/Permission/tests/cts/permission",
        "//packages/modules/Permission/tests/cts/permissionui",
        "//test/cts-root/tests/bluetooth",
    ],
}
+148 −226
Original line number Diff line number Diff line
@@ -15,277 +15,199 @@
 */
package android.bluetooth.test_utils

import android.Manifest.permission
import android.Manifest.permission.BLUETOOTH_CONNECT
import android.Manifest.permission.BLUETOOTH_PRIVILEGED
import android.app.UiAutomation
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED
import android.bluetooth.BluetoothAdapter.STATE_BLE_ON
import android.bluetooth.BluetoothAdapter.STATE_OFF
import android.bluetooth.BluetoothAdapter.STATE_ON
import android.bluetooth.BluetoothManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import java.time.Duration
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.Condition
import java.util.concurrent.locks.ReentrantLock
import androidx.test.platform.app.InstrumentationRegistry
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull

private const val TAG: String = "BlockingBluetoothAdapter"

/** Utility for controlling the Bluetooth adapter from CTS test. */
object BlockingBluetoothAdapter {
    private val TAG: String = BlockingBluetoothAdapter::class.java.getSimpleName()
    private val context = InstrumentationRegistry.getInstrumentation().getContext()
    @JvmStatic val adapter = context.getSystemService(BluetoothManager::class.java).getAdapter()

    /**
     * ADAPTER_ENABLE_TIMEOUT_MS = AdapterState.BLE_START_TIMEOUT_DELAY +
     * AdapterState.BREDR_START_TIMEOUT_DELAY + (10 seconds of additional delay)
     */
    private val ADAPTER_ENABLE_TIMEOUT = Duration.ofSeconds(18)

    /**
     * ADAPTER_DISABLE_TIMEOUT_MS = AdapterState.BLE_STOP_TIMEOUT_DELAY +
     * AdapterState.BREDR_STOP_TIMEOUT_DELAY
     */
    private val ADAPTER_DISABLE_TIMEOUT = Duration.ofSeconds(5)
    private val state = AdapterStateListener(context, adapter)
    private val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()

    /** Redefined from [BluetoothAdapter] because of hidden APIs */
    const val STATE_BLE_TURNING_ON = 14
    const val STATE_BLE_TURNING_OFF = 16
    private val sStateTimeouts = HashMap<Int, Duration>()
    // BLE_START_TIMEOUT_DELAY + BREDR_START_TIMEOUT_DELAY + (10 seconds of additional delay)
    private val stateChangeTimeout = 18.seconds

    init {
        sStateTimeouts.put(BluetoothAdapter.STATE_OFF, ADAPTER_DISABLE_TIMEOUT)
        sStateTimeouts.put(BluetoothAdapter.STATE_TURNING_ON, ADAPTER_ENABLE_TIMEOUT)
        sStateTimeouts.put(BluetoothAdapter.STATE_ON, ADAPTER_ENABLE_TIMEOUT)
        sStateTimeouts.put(BluetoothAdapter.STATE_TURNING_OFF, ADAPTER_DISABLE_TIMEOUT)
        sStateTimeouts.put(STATE_BLE_TURNING_ON, ADAPTER_ENABLE_TIMEOUT)
        sStateTimeouts.put(BluetoothAdapter.STATE_BLE_ON, ADAPTER_ENABLE_TIMEOUT)
        sStateTimeouts.put(STATE_BLE_TURNING_OFF, ADAPTER_DISABLE_TIMEOUT)
        Log.d(TAG, "Started with initial state to $state")
    }

    private var sAdapterVarsInitialized = false
    private var sBluetoothAdapterLock: ReentrantLock? = null
    private var sConditionAdapterStateReached: Condition? = null
    private var sDesiredState = 0
    private var sAdapterState = 0

    /** Initialize all static state variables */
    private fun initAdapterStateVariables(context: Context) {
        Log.d(TAG, "Initializing adapter state variables")
        val sAdapterReceiver = BluetoothAdapterReceiver()
        sBluetoothAdapterLock = ReentrantLock()
        sConditionAdapterStateReached = sBluetoothAdapterLock!!.newCondition()
        sDesiredState = -1
        sAdapterState = -1
        val filter: IntentFilter = IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED)
        context.registerReceiver(sAdapterReceiver, filter)
        sAdapterVarsInitialized = true
    }

    /**
     * Helper method to wait for the bluetooth adapter to be in a given state
     *
     * Assumes all state variables are initialized. Assumes it's being run with
     * sBluetoothAdapterLock in the locked state.
     */
    @kotlin.Throws(InterruptedException::class)
    private fun waitForAdapterStateLocked(desiredState: Int, adapter: BluetoothAdapter): Boolean {
        val timeout = sStateTimeouts.getOrDefault(desiredState, ADAPTER_ENABLE_TIMEOUT)
        Log.d(TAG, "Waiting for adapter state $desiredState")
        sDesiredState = desiredState

        // Wait until we have reached the desired state
        // Handle spurious wakeup
        while (desiredState != sAdapterState) {
            if (sConditionAdapterStateReached!!.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
                // Handle spurious wakeup
                continue
            }
            // Handle timeout cases
            // Case 1: situation where state change occurs, but we don't receive the broadcast
            if (
                desiredState >= BluetoothAdapter.STATE_OFF &&
                    desiredState <= BluetoothAdapter.STATE_TURNING_OFF
            ) {
                val currentState = adapter.state
                Log.d(TAG, "desiredState: $desiredState, currentState: $currentState")
                return desiredState == currentState
            } else if (desiredState == BluetoothAdapter.STATE_BLE_ON) {
                Log.d(TAG, "adapter isLeEnabled: " + adapter.isLeEnabled())
                return adapter.isLeEnabled()
    /** Set Bluetooth in BLE mode. Only works if it was OFF before */
    @JvmStatic
    fun enableBLE(): Boolean {
        if (!state.eq(STATE_OFF)) {
            throw IllegalStateException("Invalid call to enableBLE while current state is: $state")
        }
            // Case 2: Actual timeout
            Log.e(
                TAG,
                "Timeout while waiting for Bluetooth adapter state " +
                    desiredState +
                    " while current state is " +
                    sAdapterState,
            )
            break
        Log.d(TAG, "Call to enableBLE")
        if (!withPermission(BLUETOOTH_CONNECT).use { adapter.enableBLE() }) {
            Log.e(TAG, "enableBLE: Failed")
            return false
        }
        Log.d(TAG, "Final state while waiting: " + sAdapterState)
        return sAdapterState == desiredState
        return state.waitForStateWithTimeout(stateChangeTimeout, STATE_BLE_ON)
    }

    /** Utility method to wait on any specific adapter state */
    fun waitForAdapterState(desiredState: Int, adapter: BluetoothAdapter): Boolean {
        sBluetoothAdapterLock!!.lock()
        try {
            return waitForAdapterStateLocked(desiredState, adapter)
        } catch (e: InterruptedException) {
            Log.w(TAG, "waitForAdapterState(): interrupted", e)
        } finally {
            sBluetoothAdapterLock!!.unlock()
    /** Restore Bluetooth to OFF. Only works if it was in BLE_ON due to enableBLE call */
    @JvmStatic
    fun disableBLE(): Boolean {
        if (!state.eq(STATE_BLE_ON)) {
            throw IllegalStateException("Invalid call to disableBLE while current state is: $state")
        }
        Log.d(TAG, "Call to disableBLE")
        if (!withPermission(BLUETOOTH_CONNECT).use { adapter.disableBLE() }) {
            Log.e(TAG, "disableBLE: Failed")
            return false
        }
        return state.waitForStateWithTimeout(stateChangeTimeout, STATE_OFF)
    }

    /** Enables Bluetooth to a Low Energy only mode */
    /** Turn Bluetooth ON and wait for state change */
    @JvmStatic
    fun enableBLE(bluetoothAdapter: BluetoothAdapter, context: Context): Boolean {
        if (!sAdapterVarsInitialized) {
            initAdapterStateVariables(context)
        }
        if (bluetoothAdapter.isLeEnabled()) {
    fun enable(): Boolean {
        if (state.eq(STATE_ON)) {
            Log.i(TAG, "enable: state is already $state")
            return true
        }
        sBluetoothAdapterLock!!.lock()
        try {
            Log.d(TAG, "Enabling Bluetooth low energy only mode")
            if (!bluetoothAdapter.enableBLE()) {
                Log.e(TAG, "Unable to enable Bluetooth low energy only mode")
                return false
            }
            return waitForAdapterStateLocked(BluetoothAdapter.STATE_BLE_ON, bluetoothAdapter)
        } catch (e: InterruptedException) {
            Log.w(TAG, "enableBLE(): interrupted", e)
        } finally {
            sBluetoothAdapterLock!!.unlock()
        Log.d(TAG, "Call to enable")
        if (
            !withPermission(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED).use {
                @Suppress("DEPRECATION") adapter.enable()
            }
        ) {
            Log.e(TAG, "enable: Failed")
            return false
        }
        return state.waitForStateWithTimeout(stateChangeTimeout, STATE_ON)
    }

    /** Disable Bluetooth Low Energy mode */
    /** Turn Bluetooth OFF and wait for state change */
    @JvmStatic
    fun disableBLE(bluetoothAdapter: BluetoothAdapter, context: Context): Boolean {
        if (!sAdapterVarsInitialized) {
            initAdapterStateVariables(context)
        }
        if (bluetoothAdapter.state == BluetoothAdapter.STATE_OFF) {
    fun disable(persist: Boolean = true): Boolean {
        if (state.eq(STATE_OFF)) {
            Log.i(TAG, "disable: state is already $state")
            return true
        }
        sBluetoothAdapterLock!!.lock()
        try {
            Log.d(TAG, "Disabling Bluetooth low energy")
            bluetoothAdapter.disableBLE()
            return waitForAdapterStateLocked(BluetoothAdapter.STATE_OFF, bluetoothAdapter)
        } catch (e: InterruptedException) {
            Log.w(TAG, "disableBLE(): interrupted", e)
        } finally {
            sBluetoothAdapterLock!!.unlock()
        Log.d(TAG, "Call to disable($persist)")
        if (
            !withPermission(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED).use {
                adapter.disable(persist)
            }
        ) {
            Log.e(TAG, "disable: Failed")
            return false
        }

    /** Enables the Bluetooth Adapter. Return true if it is already enabled or is enabled. */
    @JvmStatic
    fun enableAdapter(bluetoothAdapter: BluetoothAdapter, context: Context): Boolean {
        if (!sAdapterVarsInitialized) {
            initAdapterStateVariables(context)
        }
        if (bluetoothAdapter.isEnabled) {
            return true
        // Notify that disable was call.
        state.wasDisabled = true
        return state.waitForStateWithTimeout(stateChangeTimeout, STATE_OFF)
    }
        val permissionsAdopted: Set<String> = TestUtils.getAdoptedShellPermissions()
        sBluetoothAdapterLock!!.lock()
        try {
            Log.d(TAG, "Enabling Bluetooth adapter")
            TestUtils.dropPermissionAsShellUid()
            TestUtils.adoptPermissionAsShellUid(
                permission.BLUETOOTH_CONNECT,
                permission.BLUETOOTH_PRIVILEGED,
            )
            bluetoothAdapter.enable()
            return waitForAdapterStateLocked(BluetoothAdapter.STATE_ON, bluetoothAdapter)
        } catch (e: InterruptedException) {
            Log.w(TAG, "enableAdapter(): interrupted", e)
        } finally {
            TestUtils.dropPermissionAsShellUid()
            if (UiAutomation.ALL_PERMISSIONS.equals(permissionsAdopted)) {
                TestUtils.adoptPermissionAsShellUid()

    private fun restorePermissions(permissions: Set<String>) {
        if (UiAutomation.ALL_PERMISSIONS.equals(permissions)) {
            uiAutomation.adoptShellPermissionIdentity()
        } else {
                TestUtils.adoptPermissionAsShellUid(*permissionsAdopted.map { it }.toTypedArray())
            uiAutomation.adoptShellPermissionIdentity(*permissions.map { it }.toTypedArray())
        }
            sBluetoothAdapterLock!!.unlock()
        }
        return false
    }

    /** Disable the Bluetooth Adapter. Return true if it is already disabled or is disabled. */
    @JvmStatic
    fun disableAdapter(bluetoothAdapter: BluetoothAdapter, context: Context): Boolean {
        return disableAdapter(bluetoothAdapter, true, context)
    private fun replacePermissionsWith(vararg newPermissions: String): Set<String> {
        val currentPermissions = uiAutomation.getAdoptedShellPermissions()
        if (newPermissions.size == 0) {
            // Throw even if the code support it as we are not expecting this by design
            throw IllegalArgumentException("Invalid permissions replacement with no permissions.")
        }

    /**
     * Disable the Bluetooth Adapter with then option to persist the off state or not.
     *
     * Returns true if the adapter is already disabled or was disabled.
     */
    @JvmStatic
    fun disableAdapter(
        bluetoothAdapter: BluetoothAdapter,
        persist: Boolean,
        context: Context,
    ): Boolean {
        if (!sAdapterVarsInitialized) {
            initAdapterStateVariables(context)
        uiAutomation.adoptShellPermissionIdentity(*newPermissions)
        return currentPermissions
    }
        if (bluetoothAdapter.state == BluetoothAdapter.STATE_OFF) {
            return true

    // Set permissions to be used as long as the resource is open.
    // Restore initial permissions after closing resource.
    private fun withPermission(
        vararg newPermissions: String,
    ): AutoCloseable {
        val savedPermissions = replacePermissionsWith(*newPermissions)
        return AutoCloseable { restorePermissions(savedPermissions) }
    }
        val permissionsAdopted: Set<String> = TestUtils.getAdoptedShellPermissions()
        sBluetoothAdapterLock!!.lock()
        try {
            Log.d(TAG, "Disabling Bluetooth adapter, persist=$persist")
            TestUtils.dropPermissionAsShellUid()
            TestUtils.adoptPermissionAsShellUid(
                permission.BLUETOOTH_CONNECT,
                permission.BLUETOOTH_PRIVILEGED,
            )
            bluetoothAdapter.disable(persist)
            return waitForAdapterStateLocked(BluetoothAdapter.STATE_OFF, bluetoothAdapter)
        } catch (e: InterruptedException) {
            Log.w(TAG, "disableAdapter(persist=$persist): interrupted", e)
        } finally {
            TestUtils.dropPermissionAsShellUid()
            if (UiAutomation.ALL_PERMISSIONS.equals(permissionsAdopted)) {
                TestUtils.adoptPermissionAsShellUid()
            } else {
                TestUtils.adoptPermissionAsShellUid(*permissionsAdopted.map { it }.toTypedArray())
}
            sBluetoothAdapterLock!!.unlock()

private class AdapterStateListener(context: Context, private val adapter: BluetoothAdapter) {
    private val STATE_UNKNOWN = -42

    // Set to true once a call to disable is made, in order to force the differentiation between the
    // various state hidden within STATE_OFF (OFF, BLE_TURNING_ON, BLE_TURNING_OFF)
    // Once true, getter will return STATE_OFF when there has not been any callback sent to it
    var wasDisabled = false

    val adapterStateFlow =
        callbackFlow<Intent> {
                val broadcastReceiver =
                    object : BroadcastReceiver() {
                        override fun onReceive(context: Context, intent: Intent) {
                            trySendBlocking(intent)
                        }
        return false
                    }
                context.registerReceiver(broadcastReceiver, IntentFilter(ACTION_BLE_STATE_CHANGED))

    /** Handles BluetoothAdapter state changes and signals when we have reached a desired state */
    private class BluetoothAdapterReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            val action = intent.action
            if (BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(action)) {
                val newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1)
                Log.d(TAG, "Bluetooth adapter state changed: $newState")
                awaitClose { context.unregisterReceiver(broadcastReceiver) }
            }
            .map { it.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) }
            .onEach { Log.d(TAG, "State changed to ${BluetoothAdapter.nameForState(it)}") }
            .shareIn(CoroutineScope(Dispatchers.Default), SharingStarted.Eagerly, 1)

                // Signal if the state is set to the one we are waiting on
                sBluetoothAdapterLock!!.lock()
                try {
                    sAdapterState = newState
                    if (sDesiredState == newState) {
                        Log.d(TAG, "Adapter has reached desired state: " + sDesiredState)
                        sConditionAdapterStateReached!!.signal()
    private fun get(): Int =
        adapterStateFlow.replayCache.getOrElse(0) {
            val state: Int = adapter.getState()
            if (state != STATE_OFF) {
                state
            } else if (adapter.isLeEnabled()) {
                STATE_BLE_ON
            } else if (wasDisabled) {
                STATE_OFF
            } else {
                STATE_UNKNOWN
            }
                } finally {
                    sBluetoothAdapterLock!!.unlock()
        }

    fun eq(state: Int): Boolean = state == get()

    override fun toString(): String {
        val currentState = get()
        return if (currentState == STATE_UNKNOWN) {
            "UNKNOWN: State is uncertain, oneOf(OFF, BLE_TURNING_ON, BLE_TURNING_OFF)"
        } else {
            BluetoothAdapter.nameForState(currentState)
        }
    }

    fun waitForStateWithTimeout(timeout: Duration, state: Int): Boolean = runBlocking {
        withTimeoutOrNull(timeout) { adapterStateFlow.filter { it == state }.first() } != null
    }
}
+5 −5
Original line number Diff line number Diff line
@@ -32,7 +32,7 @@ public class BTAdapterUtils {
     */
    @Deprecated
    public static final boolean enableAdapter(BluetoothAdapter adapter, Context ctx) {
        return BlockingBluetoothAdapter.enableAdapter(adapter, ctx);
        return BlockingBluetoothAdapter.enable();
    }

    /**
@@ -40,7 +40,7 @@ public class BTAdapterUtils {
     */
    @Deprecated
    public static final boolean disableAdapter(BluetoothAdapter adapter, Context ctx) {
        return BlockingBluetoothAdapter.disableAdapter(adapter, ctx);
        return BlockingBluetoothAdapter.disable(true);
    }

    /**
@@ -49,7 +49,7 @@ public class BTAdapterUtils {
    @Deprecated
    public static final boolean disableAdapter(
            BluetoothAdapter adapter, boolean persist, Context ctx) {
        return BlockingBluetoothAdapter.disableAdapter(adapter, persist, ctx);
        return BlockingBluetoothAdapter.disable(persist);
    }

    /**
@@ -57,7 +57,7 @@ public class BTAdapterUtils {
     */
    @Deprecated
    public static final boolean enableBLE(BluetoothAdapter adapter, Context ctx) {
        return BlockingBluetoothAdapter.enableBLE(adapter, ctx);
        return BlockingBluetoothAdapter.enableBLE();
    }

    /**
@@ -65,6 +65,6 @@ public class BTAdapterUtils {
     */
    @Deprecated
    public static final boolean disableBLE(BluetoothAdapter adapter, Context ctx) {
        return BlockingBluetoothAdapter.disableBLE(adapter, ctx);
        return BlockingBluetoothAdapter.disableBLE();
    }
}
+23 −35
Original line number Diff line number Diff line
@@ -23,10 +23,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Context;

import androidx.test.platform.app.InstrumentationRegistry;

import com.android.compatibility.common.util.BeforeAfterRule;

@@ -34,54 +30,46 @@ import org.junit.runner.Description;
import org.junit.runners.model.Statement;

/**
 * This is a test rule that, when used in a test, will enable Bluetooth before the test starts. When
 * the test is done, Bluetooth will be disabled if and only if it was disabled before the test
 * started. If setTestMode is set to true, the Bluetooth scanner will return a hardcoded set of
 * Bluetooth scan results while the test runs .
 * TestRule to activate Bluetooth before the test.
 *
 * <ul>
 *   <li>If Bluetooth was off before the test, it will be disable again at the end of the test.
 *   <li>If enableTestMode is set, Bluetooth scanner will return hardcoded results
 *   <li>If toggleBluetooth is set, Bluetooth will be shutdown before enabling it
 * </ul>
 */
public class EnableBluetoothRule extends BeforeAfterRule {
    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
    private final BluetoothAdapter mBluetoothAdapter =
            mContext.getSystemService(BluetoothManager.class).getAdapter();
    private final boolean mEnableTestMode;
    private final boolean mToggleBluetooth;

    /** Convenient getter to get the Bluetooth adapter associated with the Test instrumentation */
    public final BluetoothAdapter mAdapter = BlockingBluetoothAdapter.getAdapter();

    private boolean mWasBluetoothAdapterEnabled = true;

    /** Empty constructor */
    /** Default constructor with no test mode and no turn off */
    public EnableBluetoothRule() {
        mEnableTestMode = false;
        mToggleBluetooth = false;
        this(false, false);
    }

    /**
     * Constructor that allows test mode
     *
     * @param enableTestMode whether test mode is enabled
     * @param toggleBluetooth whether to toggle Bluetooth at the beginning of the test if it is
     *     already enabled
     */
    public EnableBluetoothRule(boolean enableTestMode, boolean toggleBluetooth) {
        mEnableTestMode = enableTestMode;
        mToggleBluetooth = toggleBluetooth;
    public EnableBluetoothRule(boolean enableTestMode) {
        this(enableTestMode, false);
    }

    /**
     * Constructor that allows test mode
     * Constructor that allows test mode and allow to force shutdown bluetooth
     *
     * @param enableTestMode whether test mode is enabled
     * @param toggleBluetooth whether to shutdown Bluetooth if it was already on
     */
    public EnableBluetoothRule(boolean enableTestMode) {
    public EnableBluetoothRule(boolean enableTestMode, boolean toggleBluetooth) {
        mEnableTestMode = enableTestMode;
        mToggleBluetooth = false;
    }

    private void enableBluetoothAdapter() {
        assertThat(BlockingBluetoothAdapter.enableAdapter(mBluetoothAdapter, mContext)).isTrue();
    }

    private void disableBluetoothAdapter() {
        assertThat(BlockingBluetoothAdapter.disableAdapter(mBluetoothAdapter, mContext)).isTrue();
        mToggleBluetooth = toggleBluetooth;
    }

    private void enableBluetoothTestMode() {
@@ -99,12 +87,12 @@ public class EnableBluetoothRule extends BeforeAfterRule {
    @Override
    protected void onBefore(Statement base, Description description) {
        assumeTrue(TestUtils.hasBluetooth());
        mWasBluetoothAdapterEnabled = mBluetoothAdapter.isEnabled();
        mWasBluetoothAdapterEnabled = mAdapter.isEnabled();
        if (!mWasBluetoothAdapterEnabled) {
            enableBluetoothAdapter();
            assertThat(BlockingBluetoothAdapter.enable()).isTrue();
        } else if (mToggleBluetooth) {
            disableBluetoothAdapter();
            enableBluetoothAdapter();
            assertThat(BlockingBluetoothAdapter.disable(true)).isTrue();
            assertThat(BlockingBluetoothAdapter.enable()).isTrue();
        }
        if (mEnableTestMode) {
            enableBluetoothTestMode();
@@ -116,7 +104,7 @@ public class EnableBluetoothRule extends BeforeAfterRule {
        assumeTrue(TestUtils.hasBluetooth());
        disableBluetoothTestMode();
        if (!mWasBluetoothAdapterEnabled) {
            disableBluetoothAdapter();
            assertThat(BlockingBluetoothAdapter.disable(true)).isTrue();
        }
    }
}