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

Commit 45f8c6a0 authored by Jack He's avatar Jack He
Browse files

Migrate bluetooth-test-util-lib from cts to p/m/B

Currently, the framework SDK level Bluetooth test lib lives in CTS.
And every user of this library has to depend on CTS project. However,
not every user of this library is CTS related.

Hence, we want to move it to a shared location in p/m/B

Note: PREUPLOAD check is omited because it is a pure migration

Bug: 307752636
Test: m bluetooth-test-util-lib
Change-Id: I7d7b7f1d6a64cd11012650036ae4227e015c0dcc
parent d28ca5a6
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package {
    default_applicable_licenses: ["Android-Apache-2.0"],
}

java_library {
    name: "bluetooth-test-util-lib",
    defaults: ["cts_defaults"],
    static_libs: [
        "junit",
        "compatibility-device-util-axt",
        "PlatformProperties",
    ],
    srcs: ["src/**/*.java"],
    sdk_version: "test_current",
    // Keep it public for now to avoid merge conflicts
    visibility: ["//visibility:public"],
}
+292 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.bluetooth.cts;

import static android.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;

import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.util.Log;
import android.util.SparseIntArray;

import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Utility for controlling the Bluetooth adapter from CTS test.
 */
public class BTAdapterUtils {
    private static final String TAG = "BTAdapterUtils";

    // ADAPTER_ENABLE_TIMEOUT_MS = AdapterState.BLE_START_TIMEOUT_DELAY +
    //                             AdapterState.BREDR_START_TIMEOUT_DELAY +
    //                             (10 seconds of additional delay)
    private static final int ADAPTER_ENABLE_TIMEOUT_MS = 18000;
    // ADAPTER_DISABLE_TIMEOUT_MS = AdapterState.BLE_STOP_TIMEOUT_DELAY +
    //                                  AdapterState.BREDR_STOP_TIMEOUT_DELAY
    private static final int ADAPTER_DISABLE_TIMEOUT_MS = 5000;

    public static final int STATE_BLE_TURNING_ON = 14;
    public static final int STATE_BLE_ON = 15;
    public static final int STATE_BLE_TURNING_OFF = 16;

    private static final SparseIntArray sStateTimeouts = new SparseIntArray();
    static {
        sStateTimeouts.put(BluetoothAdapter.STATE_OFF, ADAPTER_DISABLE_TIMEOUT_MS);
        sStateTimeouts.put(BluetoothAdapter.STATE_TURNING_ON, ADAPTER_ENABLE_TIMEOUT_MS);
        sStateTimeouts.put(BluetoothAdapter.STATE_ON, ADAPTER_ENABLE_TIMEOUT_MS);
        sStateTimeouts.put(BluetoothAdapter.STATE_TURNING_OFF, ADAPTER_DISABLE_TIMEOUT_MS);
        sStateTimeouts.put(STATE_BLE_TURNING_ON, ADAPTER_ENABLE_TIMEOUT_MS);
        sStateTimeouts.put(STATE_BLE_ON, ADAPTER_ENABLE_TIMEOUT_MS);
        sStateTimeouts.put(STATE_BLE_TURNING_OFF, ADAPTER_DISABLE_TIMEOUT_MS);
    }

    private static BluetoothAdapterReceiver sAdapterReceiver;

    private static boolean sAdapterVarsInitialized;
    private static ReentrantLock sBluetoothAdapterLock;
    private static Condition sConditionAdapterStateReached;
    private static int sDesiredState;
    private static int sAdapterState;

    /**
     * Handles BluetoothAdapter state changes and signals when we have reached a desired state
     */
    private static class BluetoothAdapterReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals(action)) {
                int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
                Log.d(TAG, "Bluetooth adapter state changed: " + newState);

                // Signal if the state is set to the one we are waiting on
                sBluetoothAdapterLock.lock();
                sAdapterState = newState;
                try {
                    if (sDesiredState == newState) {
                        Log.d(TAG, "Adapter has reached desired state: " + sDesiredState);
                        sConditionAdapterStateReached.signal();
                    }
                } finally {
                    sBluetoothAdapterLock.unlock();
                }
            }
        }
    }

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

    /**
     * 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.
     */
    private static boolean waitForAdapterStateLocked(int desiredState, BluetoothAdapter adapter)
            throws InterruptedException {
        int timeout = sStateTimeouts.get(desiredState, ADAPTER_ENABLE_TIMEOUT_MS);

        Log.d(TAG, "Waiting for adapter state " + desiredState);
        sDesiredState = desiredState;

        // Wait until we have reached the desired state
        while (desiredState != sAdapterState) {
            if (!sConditionAdapterStateReached.await(timeout, TimeUnit.MILLISECONDS)) {
                // Handle situation where state change occurs, but we don't receive the broadcast
                if (desiredState >= BluetoothAdapter.STATE_OFF
                        && desiredState <= BluetoothAdapter.STATE_TURNING_OFF) {
                    int currentState = adapter.getState();
                    Log.d(TAG, "desiredState: " + desiredState + ", currentState: " + currentState);
                    return desiredState == currentState;
                } else if (desiredState == STATE_BLE_ON) {
                    Log.d(TAG, "adapter isLeEnabled: " + adapter.isLeEnabled());
                    return adapter.isLeEnabled();
                }
                Log.e(TAG, "Timeout while waiting for Bluetooth adapter state " + desiredState
                        + " while current state is " + sAdapterState);
                break;
            }
        }

        Log.d(TAG, "Final state while waiting: " + sAdapterState);

        return sAdapterState == desiredState;
    }

    /**
     * Utility method to wait on any specific adapter state
     */
    public static boolean waitForAdapterState(int desiredState, BluetoothAdapter adapter) {
        sBluetoothAdapterLock.lock();
        try {
            return waitForAdapterStateLocked(desiredState, adapter);
        } catch (InterruptedException e) {
            Log.w(TAG, "waitForAdapterState(): interrupted", e);
        } finally {
            sBluetoothAdapterLock.unlock();
        }
        return false;
    }

    /**
     * Enables Bluetooth to a Low Energy only mode
     */
    public static boolean enableBLE(BluetoothAdapter bluetoothAdapter, Context context) {
        if (!sAdapterVarsInitialized) {
            initAdapterStateVariables(context);
        }

        if (bluetoothAdapter.isLeEnabled()) {
            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(STATE_BLE_ON, bluetoothAdapter);
        } catch (InterruptedException e) {
            Log.w(TAG, "enableBLE(): interrupted", e);
        } finally {
            sBluetoothAdapterLock.unlock();
        }
        return false;
    }

    /**
     * Disable Bluetooth Low Energy mode
     */
    public static boolean disableBLE(BluetoothAdapter bluetoothAdapter, Context context) {
        if (!sAdapterVarsInitialized) {
            initAdapterStateVariables(context);
        }

        if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
            return true;
        }

        sBluetoothAdapterLock.lock();
        try {
            Log.d(TAG, "Disabling Bluetooth low energy");
            bluetoothAdapter.disableBLE();
            return waitForAdapterStateLocked(BluetoothAdapter.STATE_OFF, bluetoothAdapter);
        } catch (InterruptedException e) {
            Log.w(TAG, "disableBLE(): interrupted", e);
        } finally {
            sBluetoothAdapterLock.unlock();
        }
        return false;
    }

    /**
     * Enables the Bluetooth Adapter. Return true if it is already enabled or is enabled.
     */
    public static boolean enableAdapter(BluetoothAdapter bluetoothAdapter, Context context) {
        if (!sAdapterVarsInitialized) {
            initAdapterStateVariables(context);
        }

        if (bluetoothAdapter.isEnabled()) {
            return true;
        }

        Set<String> permissionsAdopted = TestUtils.getAdoptedShellPermissions();
        String[] permissionArray = permissionsAdopted.toArray(String[]::new);

        sBluetoothAdapterLock.lock();
        try {
            Log.d(TAG, "Enabling Bluetooth adapter");
            TestUtils.dropPermissionAsShellUid();
            TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
            bluetoothAdapter.enable();
            return waitForAdapterStateLocked(BluetoothAdapter.STATE_ON, bluetoothAdapter);
        } catch (InterruptedException e) {
            Log.w(TAG, "enableAdapter(): interrupted", e);
        } finally {
            TestUtils.dropPermissionAsShellUid();
            TestUtils.adoptPermissionAsShellUid(permissionArray);
            sBluetoothAdapterLock.unlock();
        }
        return false;
    }

    /**
     * Disable the Bluetooth Adapter. Return true if it is already disabled or is disabled.
     */
    public static boolean disableAdapter(BluetoothAdapter bluetoothAdapter, Context context) {
        return disableAdapter(bluetoothAdapter, true, context);
    }

    /**
     * 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.
     */
    public static boolean disableAdapter(BluetoothAdapter bluetoothAdapter, boolean persist,
            Context context) {
        if (!sAdapterVarsInitialized) {
            initAdapterStateVariables(context);
        }

        if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
            return true;
        }

        Set<String> permissionsAdopted = TestUtils.getAdoptedShellPermissions();
        String[] permissionArray = permissionsAdopted.toArray(String[]::new);

        sBluetoothAdapterLock.lock();
        try {
            Log.d(TAG, "Disabling Bluetooth adapter, persist=" + persist);
            TestUtils.dropPermissionAsShellUid();
            TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
            bluetoothAdapter.disable(persist);
            return waitForAdapterStateLocked(BluetoothAdapter.STATE_OFF, bluetoothAdapter);
        } catch (InterruptedException e) {
            Log.w(TAG, "disableAdapter(persist=" + persist + "): interrupted", e);
        } finally {
            TestUtils.dropPermissionAsShellUid();
            TestUtils.adoptPermissionAsShellUid(permissionArray);
            sBluetoothAdapterLock.unlock();
        }
        return false;
    }
}
+105 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.bluetooth.cts;

import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;

import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;

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

import androidx.test.platform.app.InstrumentationRegistry;

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

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 .
 */
public class EnableBluetoothRule extends BeforeAfterRule {
    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
    private final BluetoothManager mBluetoothManager =
            mContext.getSystemService(BluetoothManager.class);
    private final BluetoothAdapter mBluetoothAdapter = mBluetoothManager.getAdapter();
    private final boolean mEnableTestMode;

    private boolean mWasBluetoothAdapterEnabled = true;

    public EnableBluetoothRule() {
        mEnableTestMode = false;
    }

    public EnableBluetoothRule(boolean enableTestMode) {
        mEnableTestMode = enableTestMode;
    }

    private boolean supportsBluetooth() {
        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
    }

    private boolean isBluetoothEnabled() {
        return mBluetoothAdapter.isEnabled();
    }

    private void enableBluetoothAdapter() {
        assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext));
    }

    private void disableBluetoothAdapter() {
        assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
    }

    private void enableBluetoothTestMode() {
        runShellCommandOrThrow("dumpsys activity service"
                + " com.android.bluetooth.btservice.AdapterService set-test-mode enabled");
    }

    private void disableBluetoothTestMode() {
        runShellCommandOrThrow("dumpsys activity service"
                + " com.android.bluetooth.btservice.AdapterService set-test-mode disabled");
    }

    @Override
    protected void onBefore(Statement base, Description description) {
        assumeTrue(supportsBluetooth());
        mWasBluetoothAdapterEnabled = isBluetoothEnabled();
        if (!mWasBluetoothAdapterEnabled) {
            enableBluetoothAdapter();
        }
        if (mEnableTestMode) {
            enableBluetoothTestMode();
        }
    }

    @Override
    protected void onAfter(Statement base, Description description) {
        assumeTrue(supportsBluetooth());
        disableBluetoothTestMode();
        if (!mWasBluetoothAdapterEnabled) {
            disableBluetoothAdapter();
        }
    }
}
+381 −0

File added.

Preview size limit exceeded, changes collapsed.