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 android.bluetooth.cts; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothHearingAid; 22 import android.bluetooth.BluetoothManager; 23 import android.bluetooth.BluetoothProfile; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.PackageManager; 29 import android.test.AndroidTestCase; 30 import android.test.suitebuilder.annotation.MediumTest; 31 import android.util.Log; 32 33 import java.io.IOException; 34 import java.util.List; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.concurrent.locks.Condition; 38 import java.util.concurrent.locks.ReentrantLock; 39 import java.util.concurrent.TimeUnit; 40 41 /** 42 * Unit test cases for {@link BluetoothHearingAid}. 43 * <p> 44 * To run the test, use adb shell am instrument -e class 'android.bluetooth.HearingAidProfileTest' 45 * -w 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner' 46 */ 47 public class HearingAidProfileTest extends AndroidTestCase { 48 private static final String TAG = "HearingAidProfileTest"; 49 50 private static final int WAIT_FOR_INTENT_TIMEOUT_MS = 10000; // ms to wait for intent callback 51 private static final int PROXY_CONNECTION_TIMEOUT_MS = 500; // ms timeout for Proxy Connect 52 // ADAPTER_ENABLE_TIMEOUT_MS = AdapterState.BLE_START_TIMEOUT_DELAY + 53 // AdapterState.BREDR_START_TIMEOUT_DELAY 54 private static final int ADAPTER_ENABLE_TIMEOUT_MS = 8000; 55 // ADAPTER_DISABLE_TIMEOUT_MS = AdapterState.BLE_STOP_TIMEOUT_DELAY + 56 // AdapterState.BREDR_STOP_TIMEOUT_DELAY 57 private static final int ADAPTER_DISABLE_TIMEOUT_MS = 5000; 58 59 private boolean mIsHearingAidSupported; 60 private boolean mIsBleSupported; 61 private BluetoothHearingAid mService; 62 private BluetoothAdapter mBluetoothAdapter; 63 private BroadcastReceiver mIntentReceiver; 64 65 private Condition mConditionProfileIsConnected; 66 private ReentrantLock mProfileConnectedlock; 67 private boolean mIsProfileReady; 68 69 private static List<Integer> mValidConnectionStates = new ArrayList<Integer>( 70 Arrays.asList(BluetoothProfile.STATE_CONNECTING, BluetoothProfile.STATE_CONNECTED, 71 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTING)); 72 73 private List<BluetoothDevice> mIntentCallbackDeviceList; 74 setUp()75 public void setUp() throws Exception { 76 if (!isBleSupported()) return; 77 mIsBleSupported = true; 78 79 BluetoothManager manager = (BluetoothManager) mContext.getSystemService( 80 Context.BLUETOOTH_SERVICE); 81 mBluetoothAdapter = manager.getAdapter(); 82 83 if (!BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext)) { 84 Log.e(TAG, "Unable to enable Bluetooth Adapter!"); 85 assertTrue(mBluetoothAdapter.isEnabled()); 86 } 87 88 mProfileConnectedlock = new ReentrantLock(); 89 mConditionProfileIsConnected = mProfileConnectedlock.newCondition(); 90 mIsProfileReady = false; 91 mService = null; 92 mIsHearingAidSupported = mBluetoothAdapter.getProfileProxy(getContext(), 93 new HearingAidsServiceListener(), 94 BluetoothProfile.HEARING_AID); 95 if (!mIsHearingAidSupported) return; 96 } 97 98 @Override tearDown()99 public void tearDown() { 100 if (!mIsBleSupported) return; 101 102 if (!BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext)) { 103 Log.e(TAG, "Unable to disable Bluetooth Adapter!"); 104 assertTrue(mBluetoothAdapter.isEnabled()); 105 } 106 } 107 108 /** 109 * Basic test case to make sure that Hearing Aid Profile Proxy can connect. 110 */ 111 @MediumTest test_getProxyServiceConnect()112 public void test_getProxyServiceConnect() { 113 if (!(mIsBleSupported && mIsHearingAidSupported)) return; 114 115 waitForProfileConnect(); 116 assertTrue(mIsProfileReady); 117 assertNotNull(mService); 118 } 119 120 /** 121 * Basic test case to make sure that a fictional device is disconnected. 122 */ 123 @MediumTest test_getConnectionState()124 public void test_getConnectionState() { 125 if (!(mIsBleSupported && mIsHearingAidSupported)) { 126 return; 127 } 128 129 waitForProfileConnect(); 130 assertTrue(mIsProfileReady); 131 assertNotNull(mService); 132 133 // Create a dummy device 134 BluetoothDevice device = mBluetoothAdapter.getRemoteDevice("00:11:22:AA:BB:CC"); 135 assertNotNull(device); 136 137 int connectionState = mService.getConnectionState(device); 138 // Dummy device should be disconnected 139 assertEquals(connectionState, BluetoothProfile.STATE_DISCONNECTED); 140 } 141 142 /** 143 * Basic test case to get the list of connected Hearing Aid devices. 144 */ 145 @MediumTest test_getConnectedDevices()146 public void test_getConnectedDevices() { 147 if (!(mIsBleSupported && mIsHearingAidSupported)) { 148 return; 149 } 150 151 waitForProfileConnect(); 152 assertTrue(mIsProfileReady); 153 assertNotNull(mService); 154 155 List<BluetoothDevice> deviceList; 156 157 deviceList = mService.getConnectedDevices(); 158 Log.d(TAG, "getConnectedDevices(): size=" + deviceList.size()); 159 for (BluetoothDevice device : deviceList) { 160 int connectionState = mService.getConnectionState(device); 161 checkValidConnectionState(connectionState); 162 } 163 } 164 165 /** 166 * Basic test case to get the list of matching Hearing Aid devices for each of the 4 connection 167 * states. 168 */ 169 @MediumTest test_getDevicesMatchingConnectionStates()170 public void test_getDevicesMatchingConnectionStates() { 171 if (!(mIsBleSupported && mIsHearingAidSupported)) { 172 return; 173 } 174 175 waitForProfileConnect(); 176 assertTrue(mIsProfileReady); 177 assertNotNull(mService); 178 179 for (int connectionState : mValidConnectionStates) { 180 List<BluetoothDevice> deviceList; 181 182 deviceList = mService.getDevicesMatchingConnectionStates(new int[]{connectionState}); 183 assertNotNull(deviceList); 184 Log.d(TAG, "getDevicesMatchingConnectionStates(" + connectionState + "): size=" 185 + deviceList.size()); 186 checkDeviceListAndStates(deviceList, connectionState); 187 } 188 } 189 190 /** 191 * Test case to make sure that if the connection changed intent is called, the parameters and 192 * device are correct. 193 */ 194 @MediumTest test_getConnectionStateChangedIntent()195 public void test_getConnectionStateChangedIntent() { 196 if (!(mIsBleSupported && mIsHearingAidSupported)) { 197 return; 198 } 199 200 waitForProfileConnect(); 201 assertTrue(mIsProfileReady); 202 assertNotNull(mService); 203 204 // Find out how many Hearing Aid bonded devices 205 List<BluetoothDevice> bondedDeviceList = new ArrayList(); 206 int numDevices = 0; 207 for (int connectionState : mValidConnectionStates) { 208 List<BluetoothDevice> deviceList; 209 210 deviceList = mService.getDevicesMatchingConnectionStates(new int[]{connectionState}); 211 bondedDeviceList.addAll(deviceList); 212 numDevices += deviceList.size(); 213 } 214 215 if (numDevices <= 0) return; 216 Log.d(TAG, "Number Hearing Aids devices bonded=" + numDevices); 217 218 mIntentCallbackDeviceList = new ArrayList(); 219 220 // Set up the Connection State Changed receiver 221 IntentFilter filter = new IntentFilter(); 222 filter.addAction(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED); 223 mIntentReceiver = new HearingAidIntentReceiver(); 224 mContext.registerReceiver(mIntentReceiver, filter); 225 226 Log.d(TAG, "test_getConnectionStateChangedIntent: disable adapter and wait"); 227 assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext)); 228 229 Log.d(TAG, "test_getConnectionStateChangedIntent: enable adapter and wait"); 230 assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext)); 231 232 int sanityCount = WAIT_FOR_INTENT_TIMEOUT_MS; 233 while ((numDevices != mIntentCallbackDeviceList.size()) && (sanityCount > 0)) { 234 final int SLEEP_QUANTUM_MS = 100; 235 sleep(SLEEP_QUANTUM_MS); 236 sanityCount -= SLEEP_QUANTUM_MS; 237 } 238 239 // Tear down 240 mContext.unregisterReceiver(mIntentReceiver); 241 242 Log.d(TAG, "test_getConnectionStateChangedIntent: number of bonded device=" 243 + numDevices + ", mIntentCallbackDeviceList.size()=" 244 + mIntentCallbackDeviceList.size()); 245 for (BluetoothDevice device : mIntentCallbackDeviceList) { 246 assertTrue(bondedDeviceList.contains(device)); 247 } 248 } 249 waitForProfileConnect()250 private boolean waitForProfileConnect() { 251 mProfileConnectedlock.lock(); 252 try { 253 // Wait for the Adapter to be disabled 254 while (!mIsProfileReady) { 255 if (!mConditionProfileIsConnected.await( 256 PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { 257 // Timeout 258 Log.e(TAG, "Timeout while waiting for Profile Connect"); 259 break; 260 } // else spurious wakeups 261 } 262 } catch(InterruptedException e) { 263 Log.e(TAG, "waitForProfileConnect: interrrupted"); 264 } finally { 265 mProfileConnectedlock.unlock(); 266 } 267 return mIsProfileReady; 268 } 269 270 private final class HearingAidsServiceListener 271 implements BluetoothProfile.ServiceListener { 272 onServiceConnected(int profile, BluetoothProfile proxy)273 public void onServiceConnected(int profile, BluetoothProfile proxy) { 274 mProfileConnectedlock.lock(); 275 mService = (BluetoothHearingAid) proxy; 276 mIsProfileReady = true; 277 try { 278 mConditionProfileIsConnected.signal(); 279 } finally { 280 mProfileConnectedlock.unlock(); 281 } 282 } 283 onServiceDisconnected(int profile)284 public void onServiceDisconnected(int profile) { 285 mProfileConnectedlock.lock(); 286 mIsProfileReady = false; 287 mService = null; 288 mProfileConnectedlock.unlock(); 289 } 290 } 291 292 private class HearingAidIntentReceiver extends BroadcastReceiver { 293 @Override onReceive(Context context, Intent intent)294 public void onReceive(Context context, Intent intent) { 295 if (BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { 296 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 297 int previousState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 298 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 299 300 Log.d(TAG,"HearingAidIntentReceiver.onReceive: device=" + device 301 + ", state=" + state + ", previousState=" + previousState); 302 303 checkValidConnectionState(state); 304 checkValidConnectionState(previousState); 305 306 mIntentCallbackDeviceList.add(device); 307 } 308 } 309 } 310 checkDeviceListAndStates(List<BluetoothDevice> deviceList, int connectionState)311 private void checkDeviceListAndStates(List<BluetoothDevice> deviceList, int connectionState) { 312 Log.d(TAG, "checkDeviceListAndStates(): size=" + deviceList.size() 313 + ", connectionState=" + connectionState); 314 for (BluetoothDevice device : deviceList) { 315 int deviceConnectionState = mService.getConnectionState(device); 316 assertEquals("Mismatched connection state for " + device, 317 connectionState, deviceConnectionState); 318 } 319 } 320 checkValidConnectionState(int connectionState)321 private void checkValidConnectionState(int connectionState) { 322 assertTrue(mValidConnectionStates.contains(connectionState)); 323 } 324 325 // Returns whether offloaded scan batching is supported. isBleBatchScanSupported()326 private boolean isBleBatchScanSupported() { 327 return mBluetoothAdapter.isOffloadedScanBatchingSupported(); 328 } 329 330 // Check if Bluetooth LE feature is supported on DUT. isBleSupported()331 private boolean isBleSupported() { 332 return getContext().getPackageManager() 333 .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); 334 } 335 sleep(long t)336 private static void sleep(long t) { 337 try { 338 Thread.sleep(t); 339 } catch (InterruptedException e) {} 340 } 341 } 342