blob: fdf4996cee6619bc71b4dc4d43654c7adeba8d16 [file] [log] [blame]
/*
* Copyright (C) 2019 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 com.android.car;
import static org.junit.Assert.assertTrue;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.provider.Settings;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Contains the utilities to aid in car Bluetooth testing
*/
public class BluetoothAdapterHelper {
private static final String TAG = "BluetoothAdapterHelper";
private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
private Context mContext;
private BluetoothAdapterReceiver mBluetoothAdapterReceiver;
private BluetoothAdapter mBluetoothAdapter;
// Timeout for waiting for an adapter state change
private static final int BT_ADAPTER_TIMEOUT_MS = 8000; // ms
// Objects to block until the adapter has reached a desired state
private ReentrantLock mBluetoothAdapterLock;
private Condition mConditionAdapterStateReached;
private int mDesiredState;
/**
* Handles BluetoothAdapter state changes and signals when we've reached a desired state
*/
private class BluetoothAdapterReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// Decode the intent
String action = intent.getAction();
// Watch for BluetoothAdapter intents only
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
logd("Bluetooth adapter state changed: " + newState);
// Signal if the state is set to the one we're waiting on. If its not and we got a
// STATE_OFF event then handle the unexpected off event. Note that we could
// proactively turn the adapter back on to continue testing. For now we'll just
// log it
mBluetoothAdapterLock.lock();
try {
if (mDesiredState == newState) {
mConditionAdapterStateReached.signal();
} else if (newState == BluetoothAdapter.STATE_OFF) {
logw("Bluetooth turned off unexpectedly while test was running.");
}
} finally {
mBluetoothAdapterLock.unlock();
}
}
}
}
/**
* Create a BluetoothAdapterHelper and tell it how to log
*
* @param tag - The logging tag you wish it to have
*/
public BluetoothAdapterHelper() {
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mDesiredState = -1; // Set and checked by waitForAdapterState()
mBluetoothAdapterLock = new ReentrantLock();
mConditionAdapterStateReached = mBluetoothAdapterLock.newCondition();
mBluetoothAdapterReceiver = new BluetoothAdapterReceiver();
}
/**
* Setup the helper and begin receiving BluetoothAdapter events.
*
* Must be called before functions will work.
*/
public void init() {
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mBluetoothAdapterReceiver, filter);
}
/**
* Release resource in preparation to destroy the object
*/
public void release() {
mContext.unregisterReceiver(mBluetoothAdapterReceiver);
}
/**
* Get the state of the Bluetooth Adapter
*/
public int getAdapterState() {
return mBluetoothAdapter.getState();
}
/**
* Get the persisted Bluetooth state from Settings
*
* @return True if the persisted Bluetooth state is on, false otherwise
*/
public boolean isAdapterPersistedOn() {
return (Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, -1) != 0);
}
/**
* Wait until the adapter is ON. If the adapter is already on it will return immediately.
*/
public synchronized void waitForAdapterOn() {
waitForAdapterState(BluetoothAdapter.STATE_ON);
}
/**
* Wait until the adapter is OFF. If the adapter is already off it will return immediately.
*/
public synchronized void waitForAdapterOff() {
waitForAdapterState(BluetoothAdapter.STATE_OFF);
}
/**
* Wait for the bluetooth adapter to be in a given state
*/
private synchronized void waitForAdapterState(int desiredState) {
logd("Waiting for adapter state " + Utils.getAdapterStateName(desiredState));
mBluetoothAdapterLock.lock();
try {
// Update the desired state so that we'll signal when we get there
mDesiredState = desiredState;
// Wait until we're reached that desired state
while (desiredState != mBluetoothAdapter.getState()) {
if (!mConditionAdapterStateReached.await(
BT_ADAPTER_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
loge("Timeout while waiting for Bluetooth adapter state "
+ Utils.getAdapterStateName(desiredState));
break;
}
}
} catch (InterruptedException e) {
logw("waitForAdapterState(" + Utils.getAdapterStateName(desiredState)
+ "): interrupted, Reason: " + e);
} finally {
mBluetoothAdapterLock.unlock();
}
assertTrue(mBluetoothAdapter.getState() == desiredState);
}
/**
* Request that the adapter be turned on.
*/
public synchronized void forceAdapterOn() {
forceAdapterState(BluetoothAdapter.STATE_ON, true);
}
/**
* Request that the adapter be turned off.
*/
public synchronized void forceAdapterOff() {
forceAdapterState(BluetoothAdapter.STATE_OFF, true);
}
/**
* Request that the adapter be turned off. Do not persist the off state across a reboot
*/
public synchronized void forceAdapterOffDoNotPersist() {
forceAdapterState(BluetoothAdapter.STATE_OFF, false);
}
/**
* Request that the adapter be turned eother on or off.
*/
private void forceAdapterState(int desiredState, boolean persistAcrossReboot) {
logd("Forcing adapter to be " + Utils.getAdapterStateName(desiredState));
// If the current state matches the requested state, these calls return immediately and
// our loop below will simply read the proper state
if (desiredState == BluetoothAdapter.STATE_ON) {
mBluetoothAdapter.enable();
} else {
mBluetoothAdapter.disable(persistAcrossReboot);
}
waitForAdapterState(desiredState);
}
/**
* Log a message to DEBUG if debug is enabled
*/
private void logd(String msg) {
if (DBG) {
Log.d(TAG, msg);
}
}
/**
* Log a message to WARN
*/
private void logw(String msg) {
Log.w(TAG, msg);
}
/**
* Log a message to ERROR
*/
private void loge(String msg) {
Log.e(TAG, msg);
}
}