1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car; 18 19 import static org.junit.Assert.assertTrue; 20 21 import android.bluetooth.BluetoothAdapter; 22 import android.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.provider.Settings; 27 import android.util.Log; 28 29 import androidx.test.InstrumentationRegistry; 30 31 import java.util.concurrent.TimeUnit; 32 import java.util.concurrent.locks.Condition; 33 import java.util.concurrent.locks.ReentrantLock; 34 35 /** 36 * Contains the utilities to aid in car Bluetooth testing 37 */ 38 public class BluetoothAdapterHelper { 39 private static final String TAG = "BluetoothAdapterHelper"; 40 private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 41 private Context mContext; 42 43 private BluetoothAdapterReceiver mBluetoothAdapterReceiver; 44 private BluetoothAdapter mBluetoothAdapter; 45 46 // Timeout for waiting for an adapter state change 47 private static final int BT_ADAPTER_TIMEOUT_MS = 8000; // ms 48 49 // Objects to block until the adapter has reached a desired state 50 private ReentrantLock mBluetoothAdapterLock; 51 private Condition mConditionAdapterStateReached; 52 private int mDesiredState; 53 54 /** 55 * Handles BluetoothAdapter state changes and signals when we've reached a desired state 56 */ 57 private class BluetoothAdapterReceiver extends BroadcastReceiver { 58 @Override onReceive(Context context, Intent intent)59 public void onReceive(Context context, Intent intent) { 60 61 // Decode the intent 62 String action = intent.getAction(); 63 64 // Watch for BluetoothAdapter intents only 65 if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { 66 int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); 67 logd("Bluetooth adapter state changed: " + newState); 68 69 // Signal if the state is set to the one we're waiting on. If its not and we got a 70 // STATE_OFF event then handle the unexpected off event. Note that we could 71 // proactively turn the adapter back on to continue testing. For now we'll just 72 // log it 73 mBluetoothAdapterLock.lock(); 74 try { 75 if (mDesiredState == newState) { 76 mConditionAdapterStateReached.signal(); 77 } else if (newState == BluetoothAdapter.STATE_OFF) { 78 logw("Bluetooth turned off unexpectedly while test was running."); 79 } 80 } finally { 81 mBluetoothAdapterLock.unlock(); 82 } 83 } 84 } 85 } 86 87 /** 88 * Create a BluetoothAdapterHelper and tell it how to log 89 * 90 * @param tag - The logging tag you wish it to have 91 */ BluetoothAdapterHelper()92 public BluetoothAdapterHelper() { 93 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 94 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 95 96 mDesiredState = -1; // Set and checked by waitForAdapterState() 97 mBluetoothAdapterLock = new ReentrantLock(); 98 mConditionAdapterStateReached = mBluetoothAdapterLock.newCondition(); 99 mBluetoothAdapterReceiver = new BluetoothAdapterReceiver(); 100 } 101 102 /** 103 * Setup the helper and begin receiving BluetoothAdapter events. 104 * 105 * Must be called before functions will work. 106 */ init()107 public void init() { 108 IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); 109 mContext.registerReceiver(mBluetoothAdapterReceiver, filter); 110 } 111 112 /** 113 * Release resource in preparation to destroy the object 114 */ release()115 public void release() { 116 mContext.unregisterReceiver(mBluetoothAdapterReceiver); 117 } 118 119 /** 120 * Get the state of the Bluetooth Adapter 121 */ getAdapterState()122 public int getAdapterState() { 123 return mBluetoothAdapter.getState(); 124 } 125 126 /** 127 * Get the persisted Bluetooth state from Settings 128 * 129 * @return True if the persisted Bluetooth state is on, false otherwise 130 */ isAdapterPersistedOn()131 public boolean isAdapterPersistedOn() { 132 return (Settings.Global.getInt( 133 mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, -1) != 0); 134 } 135 136 /** 137 * Wait until the adapter is ON. If the adapter is already on it will return immediately. 138 */ waitForAdapterOn()139 public synchronized void waitForAdapterOn() { 140 waitForAdapterState(BluetoothAdapter.STATE_ON); 141 } 142 143 /** 144 * Wait until the adapter is OFF. If the adapter is already off it will return immediately. 145 */ waitForAdapterOff()146 public synchronized void waitForAdapterOff() { 147 waitForAdapterState(BluetoothAdapter.STATE_OFF); 148 } 149 150 /** 151 * Wait for the bluetooth adapter to be in a given state 152 */ waitForAdapterState(int desiredState)153 private synchronized void waitForAdapterState(int desiredState) { 154 logd("Waiting for adapter state " + Utils.getAdapterStateName(desiredState)); 155 mBluetoothAdapterLock.lock(); 156 try { 157 // Update the desired state so that we'll signal when we get there 158 mDesiredState = desiredState; 159 160 // Wait until we're reached that desired state 161 while (desiredState != mBluetoothAdapter.getState()) { 162 if (!mConditionAdapterStateReached.await( 163 BT_ADAPTER_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 164 loge("Timeout while waiting for Bluetooth adapter state " 165 + Utils.getAdapterStateName(desiredState)); 166 break; 167 } 168 } 169 } catch (InterruptedException e) { 170 logw("waitForAdapterState(" + Utils.getAdapterStateName(desiredState) 171 + "): interrupted, Reason: " + e); 172 } finally { 173 mBluetoothAdapterLock.unlock(); 174 } 175 assertTrue(mBluetoothAdapter.getState() == desiredState); 176 } 177 178 /** 179 * Request that the adapter be turned on. 180 */ forceAdapterOn()181 public synchronized void forceAdapterOn() { 182 forceAdapterState(BluetoothAdapter.STATE_ON, true); 183 } 184 185 /** 186 * Request that the adapter be turned off. 187 */ forceAdapterOff()188 public synchronized void forceAdapterOff() { 189 forceAdapterState(BluetoothAdapter.STATE_OFF, true); 190 } 191 192 /** 193 * Request that the adapter be turned off. Do not persist the off state across a reboot 194 */ forceAdapterOffDoNotPersist()195 public synchronized void forceAdapterOffDoNotPersist() { 196 forceAdapterState(BluetoothAdapter.STATE_OFF, false); 197 } 198 199 /** 200 * Request that the adapter be turned eother on or off. 201 */ forceAdapterState(int desiredState, boolean persistAcrossReboot)202 private void forceAdapterState(int desiredState, boolean persistAcrossReboot) { 203 logd("Forcing adapter to be " + Utils.getAdapterStateName(desiredState)); 204 // If the current state matches the requested state, these calls return immediately and 205 // our loop below will simply read the proper state 206 if (desiredState == BluetoothAdapter.STATE_ON) { 207 mBluetoothAdapter.enable(); 208 } else { 209 mBluetoothAdapter.disable(persistAcrossReboot); 210 } 211 waitForAdapterState(desiredState); 212 } 213 214 /** 215 * Log a message to DEBUG if debug is enabled 216 */ logd(String msg)217 private void logd(String msg) { 218 if (DBG) { 219 Log.d(TAG, msg); 220 } 221 } 222 223 /** 224 * Log a message to WARN 225 */ logw(String msg)226 private void logw(String msg) { 227 Log.w(TAG, msg); 228 } 229 230 /** 231 * Log a message to ERROR 232 */ loge(String msg)233 private void loge(String msg) { 234 Log.e(TAG, msg); 235 } 236 } 237