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

Commit 67fb9f15 authored by Jack He's avatar Jack He Committed by Gerrit Code Review
Browse files

Merge changes from topic "bt-framework-test-lib" into main

* changes:
  Migrate bluetooth-test-util-lib from cts to p/m/B
  Cert: attach logger before checking if process is alive
  Cert: Fix local run script to use Python 3.11 and venv
  Cert: Add missing libraries to Android.mk
  Cert: Remove dependency to libgrpc++_unsecure
parents 27bd31f4 45f8c6a0
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.

+3 −2
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ LOCAL_host_python_extension_libraries := \

LOCAL_host_libraries := \
	$(HOST_OUT_SHARED_LIBRARIES)/libbase.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libbinder.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libbinder_ndk.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libbluetooth.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libbluetooth_gd.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libc++.so \
@@ -37,7 +39,6 @@ LOCAL_host_libraries := \
	$(HOST_OUT_SHARED_LIBRARIES)/libcutils.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libevent-host.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libflatbuffers-cpp.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libgrpc++_unsecure.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libgrpc++.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libgrpc_wrap.so \
	$(HOST_OUT_SHARED_LIBRARIES)/liblog.so \
@@ -45,6 +46,7 @@ LOCAL_host_libraries := \
	$(HOST_OUT_SHARED_LIBRARIES)/libz-host.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libprotobuf-cpp-full.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libunwindstack.so \
	$(HOST_OUT_SHARED_LIBRARIES)/libutils.so \
	$(HOST_OUT_SHARED_LIBRARIES)/liblzma.so

LOCAL_target_executables := \
@@ -61,7 +63,6 @@ LOCAL_target_libraries := \
	$(TARGET_OUT_SHARED_LIBRARIES)/libcrypto.so \
	$(TARGET_OUT_SHARED_LIBRARIES)/libcutils.so \
	$(TARGET_OUT_SHARED_LIBRARIES)/libgrpc_wrap.so \
	$(TARGET_OUT_SHARED_LIBRARIES)/libgrpc++_unsecure.so \
	$(TARGET_OUT_SHARED_LIBRARIES)/libgrpc++.so \
	$(TARGET_OUT_SHARED_LIBRARIES)/libhidlbase.so \
	$(TARGET_OUT_SHARED_LIBRARIES)/liblog.so \
Loading