1 /* 2 * Copyright (C) 2016 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.cts.verifier.bluetooth; 18 19 import android.app.Service; 20 import android.bluetooth.BluetoothAdapter; 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothGatt; 23 import android.bluetooth.BluetoothGattCallback; 24 import android.bluetooth.BluetoothGattCharacteristic; 25 import android.bluetooth.BluetoothGattService; 26 import android.bluetooth.BluetoothManager; 27 import android.bluetooth.BluetoothProfile; 28 import android.bluetooth.le.BluetoothLeScanner; 29 import android.bluetooth.le.ScanCallback; 30 import android.bluetooth.le.ScanFilter; 31 import android.bluetooth.le.ScanResult; 32 import android.bluetooth.le.ScanSettings; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.os.DeadObjectException; 36 import android.os.Handler; 37 import android.os.IBinder; 38 import android.os.ParcelUuid; 39 import android.util.Log; 40 import android.widget.Toast; 41 42 import java.util.Arrays; 43 import java.util.Date; 44 import java.util.List; 45 import java.util.Set; 46 import java.util.Timer; 47 import java.util.TimerTask; 48 import java.util.UUID; 49 50 public class BleConnectionPriorityClientService extends Service { 51 public static final boolean DEBUG = true; 52 public static final String TAG = "BlePriorityClient"; 53 54 public static final String ACTION_BLUETOOTH_DISABLED = 55 "com.android.cts.verifier.bluetooth.action.BLUETOOTH_DISABLED"; 56 57 public static final String ACTION_CONNECTION_SERVICES_DISCOVERED = 58 "com.android.cts.verifier.bluetooth.action.CONNECTION_SERVICES_DISCOVERED"; 59 60 public static final String ACTION_BLUETOOTH_MISMATCH_SECURE = 61 "com.android.cts.verifier.bluetooth.action.ACTION_BLUETOOTH_MISMATCH_SECURE"; 62 public static final String ACTION_BLUETOOTH_MISMATCH_INSECURE = 63 "com.android.cts.verifier.bluetooth.action.ACTION_BLUETOOTH_MISMATCH_INSECURE"; 64 65 public static final String ACTION_CONNECTION_PRIORITY_BALANCED = 66 "com.android.cts.verifier.bluetooth.action.CONNECTION_PRIORITY_BALANCED"; 67 public static final String ACTION_CONNECTION_PRIORITY_HIGH = 68 "com.android.cts.verifier.bluetooth.action.CONNECTION_PRIORITY_HIGH"; 69 public static final String ACTION_CONNECTION_PRIORITY_LOW_POWER = 70 "com.android.cts.verifier.bluetooth.action.CONNECTION_PRIORITY_LOW_POWER"; 71 72 public static final String ACTION_FINISH_CONNECTION_PRIORITY_BALANCED = 73 "com.android.cts.verifier.bluetooth.action.FINISH_CONNECTION_PRIORITY_BALANCED"; 74 public static final String ACTION_FINISH_CONNECTION_PRIORITY_HIGH = 75 "com.android.cts.verifier.bluetooth.action.FINISH_CONNECTION_PRIORITY_HIGH"; 76 public static final String ACTION_FINISH_CONNECTION_PRIORITY_LOW_POWER = 77 "com.android.cts.verifier.bluetooth.action.FINISH_CONNECTION_PRIORITY_LOW_POWER"; 78 79 public static final String ACTION_CLIENT_CONNECT_SECURE = 80 "com.android.cts.verifier.bluetooth.action.CLIENT_CONNECT_SECURE"; 81 82 public static final String ACTION_DISCONNECT = 83 "com.android.cts.verifier.bluetooth.action.DISCONNECT"; 84 public static final String ACTION_FINISH_DISCONNECT = 85 "com.android.cts.verifier.bluetooth.action.FINISH_DISCONNECT"; 86 87 public static final long DEFAULT_INTERVAL = 100L; 88 public static final long DEFAULT_PERIOD = 10000L; 89 90 // this string will be used at writing test and connection priority test. 91 private static final String WRITE_VALUE = "TEST"; 92 93 private static final UUID SERVICE_UUID = 94 UUID.fromString("00009999-0000-1000-8000-00805f9b34fb"); 95 private static final UUID CHARACTERISTIC_UUID = 96 UUID.fromString("00009998-0000-1000-8000-00805f9b34fb"); 97 private static final UUID START_CHARACTERISTIC_UUID = 98 UUID.fromString("00009997-0000-1000-8000-00805f9b34fb"); 99 private static final UUID STOP_CHARACTERISTIC_UUID = 100 UUID.fromString("00009995-0000-1000-8000-00805f9b34fb"); 101 102 private BluetoothManager mBluetoothManager; 103 private BluetoothAdapter mBluetoothAdapter; 104 private BluetoothGatt mBluetoothGatt; 105 private BluetoothLeScanner mScanner; 106 private Handler mHandler; 107 private Timer mConnectionTimer; 108 private Context mContext; 109 110 private String mAction; 111 private long mInterval; 112 private long mPeriod; 113 private Date mStartDate; 114 private int mWriteCount; 115 private boolean mSecure; 116 117 private String mPriority; 118 119 private TestTaskQueue mTaskQueue; 120 121 @Override 122 public void onCreate() { 123 super.onCreate(); 124 125 mTaskQueue = new TestTaskQueue(getClass().getName() + "__taskHandlerThread"); 126 127 mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 128 mBluetoothAdapter = mBluetoothManager.getAdapter(); 129 mScanner = mBluetoothAdapter.getBluetoothLeScanner(); 130 mHandler = new Handler(); 131 mContext = this; 132 mInterval = DEFAULT_INTERVAL; 133 mPeriod = DEFAULT_PERIOD; 134 135 startScan(); 136 } 137 138 @Override 139 public void onDestroy() { 140 super.onDestroy(); 141 if (mBluetoothGatt != null) { 142 try { 143 mBluetoothGatt.disconnect(); 144 mBluetoothGatt.close(); 145 } catch (Exception e) {} 146 finally { 147 mBluetoothGatt = null; 148 } 149 } 150 stopScan(); 151 152 mTaskQueue.quit(); 153 } 154 155 @Override 156 public IBinder onBind(Intent intent) { 157 return null; 158 } 159 160 private void notifyBluetoothDisabled() { 161 Intent intent = new Intent(ACTION_BLUETOOTH_DISABLED); 162 sendBroadcast(intent); 163 } 164 165 @Override 166 public int onStartCommand(Intent intent, int flags, int startId) { 167 if (intent != null) { 168 mAction = intent.getAction(); 169 if (mAction != null) { 170 switch (mAction) { 171 case ACTION_CLIENT_CONNECT_SECURE: 172 mSecure = true; 173 break; 174 case ACTION_CONNECTION_PRIORITY_BALANCED: 175 case ACTION_CONNECTION_PRIORITY_HIGH: 176 case ACTION_CONNECTION_PRIORITY_LOW_POWER: 177 mTaskQueue.addTask(new Runnable() { 178 @Override 179 public void run() { 180 startPeriodicTransmission(); 181 } 182 }); 183 break; 184 case ACTION_DISCONNECT: 185 if (mBluetoothGatt != null) { 186 mBluetoothGatt.disconnect(); 187 } else { 188 notifyDisconnect(); 189 } 190 break; 191 } 192 } 193 } 194 return START_NOT_STICKY; 195 } 196 197 private void startPeriodicTransmission() { 198 mWriteCount = 0; 199 200 // Set connection priority 201 switch (mAction) { 202 case ACTION_CONNECTION_PRIORITY_BALANCED: 203 mPriority = BleConnectionPriorityServerService.CONNECTION_PRIORITY_BALANCED; 204 mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_BALANCED); 205 break; 206 case ACTION_CONNECTION_PRIORITY_HIGH: 207 mPriority = BleConnectionPriorityServerService.CONNECTION_PRIORITY_HIGH; 208 mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH); 209 break; 210 case ACTION_CONNECTION_PRIORITY_LOW_POWER: 211 mPriority = BleConnectionPriorityServerService.CONNECTION_PRIORITY_LOW_POWER; 212 mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER); 213 break; 214 default: 215 mPriority = BleConnectionPriorityServerService.CONNECTION_PRIORITY_BALANCED; 216 mBluetoothGatt.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_BALANCED); 217 break; 218 } 219 220 // Create Timer for Periodic transmission 221 mStartDate = new Date(); 222 TimerTask task = new TimerTask() { 223 @Override 224 public void run() { 225 if (mBluetoothGatt == null) { 226 if (DEBUG) { 227 Log.d(TAG, "BluetoothGatt is null, return"); 228 } 229 return; 230 } 231 232 Date currentData = new Date(); 233 if ((currentData.getTime() - mStartDate.getTime()) >= mPeriod) { 234 if (mConnectionTimer != null) { 235 mConnectionTimer.cancel(); 236 mConnectionTimer = null; 237 } 238 // The STOP_CHARACTERISTIC_UUID is critical in syncing the client and server 239 // states. Delay the write by 2 seconds to improve the chance of this 240 // characteristic going through. Consider changing the code to use callbacks 241 // in the future to make it more robust. 242 sleep(2000); 243 // write termination data (contains current priority and number of messages wrote) 244 String msg = "" + mPriority + "," + mWriteCount; 245 writeCharacteristic(STOP_CHARACTERISTIC_UUID, msg); 246 sleep(1000); 247 Intent intent = new Intent(); 248 switch (mPriority) { 249 case BleConnectionPriorityServerService.CONNECTION_PRIORITY_BALANCED: 250 intent.setAction(ACTION_FINISH_CONNECTION_PRIORITY_BALANCED); 251 break; 252 case BleConnectionPriorityServerService.CONNECTION_PRIORITY_HIGH: 253 intent.setAction(ACTION_FINISH_CONNECTION_PRIORITY_HIGH); 254 break; 255 case BleConnectionPriorityServerService.CONNECTION_PRIORITY_LOW_POWER: 256 intent.setAction(ACTION_FINISH_CONNECTION_PRIORITY_LOW_POWER); 257 break; 258 } 259 sendBroadcast(intent); 260 } 261 262 if (mConnectionTimer != null) { 263 // write testing data 264 ++mWriteCount; 265 writeCharacteristic(CHARACTERISTIC_UUID, WRITE_VALUE); 266 } 267 } 268 }; 269 270 // write starting data 271 writeCharacteristic(START_CHARACTERISTIC_UUID, mPriority); 272 273 // start sending 274 sleep(1000); 275 mConnectionTimer = new Timer(); 276 mConnectionTimer.schedule(task, 0, mInterval); 277 } 278 279 private BluetoothGattService getService() { 280 BluetoothGattService service = null; 281 282 if (mBluetoothGatt != null) { 283 service = mBluetoothGatt.getService(SERVICE_UUID); 284 if (service == null) { 285 showMessage("Service not found"); 286 } 287 } 288 return service; 289 } 290 291 private BluetoothGattCharacteristic getCharacteristic(UUID uuid) { 292 BluetoothGattCharacteristic characteristic = null; 293 294 BluetoothGattService service = getService(); 295 if (service != null) { 296 characteristic = service.getCharacteristic(uuid); 297 if (characteristic == null) { 298 showMessage("Characteristic not found"); 299 } 300 } 301 return characteristic; 302 } 303 304 private void writeCharacteristic(UUID uid, String writeValue) { 305 BluetoothGattCharacteristic characteristic = getCharacteristic(uid); 306 if (characteristic != null){ 307 characteristic.setValue(writeValue); 308 mBluetoothGatt.writeCharacteristic(characteristic); 309 } 310 } 311 312 private void sleep(int millis) { 313 try { 314 Thread.sleep(millis); 315 } catch (InterruptedException e) { 316 Log.e(TAG, "Error in thread sleep", e); 317 } 318 } 319 320 private void showMessage(final String msg) { 321 mHandler.post(new Runnable() { 322 public void run() { 323 Toast.makeText(BleConnectionPriorityClientService.this, msg, Toast.LENGTH_SHORT).show(); 324 } 325 }); 326 } 327 328 private final BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() { 329 @Override 330 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 331 if (DEBUG) Log.d(TAG, "onConnectionStateChange"); 332 if (status == BluetoothGatt.GATT_SUCCESS) { 333 if (newState == BluetoothProfile.STATE_CONNECTED) { 334 int bond = gatt.getDevice().getBondState(); 335 boolean bonded = false; 336 BluetoothDevice target = gatt.getDevice(); 337 Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); 338 if (pairedDevices.size() > 0) { 339 for (BluetoothDevice device : pairedDevices) { 340 if (device.getAddress().equals(target.getAddress())) { 341 bonded = true; 342 break; 343 } 344 } 345 } 346 if (mSecure && ((bond == BluetoothDevice.BOND_NONE) || !bonded)) { 347 // not pairing and execute Secure Test 348 mBluetoothGatt.disconnect(); 349 notifyMismatchSecure(); 350 } else if (!mSecure && ((bond != BluetoothDevice.BOND_NONE) || bonded)) { 351 // already pairing nad execute Insecure Test 352 mBluetoothGatt.disconnect(); 353 notifyMismatchInsecure(); 354 } else { 355 showMessage("Bluetooth LE connected"); 356 mBluetoothGatt.discoverServices(); 357 } 358 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 359 showMessage("Bluetooth LE disconnected"); 360 361 notifyDisconnect(); 362 } 363 } else { 364 showMessage("Failed to connect"); 365 mBluetoothGatt.close(); 366 mBluetoothGatt = null; 367 } 368 } 369 370 @Override 371 public void onServicesDiscovered(BluetoothGatt gatt, int status) { 372 if (DEBUG){ 373 Log.d(TAG, "onServiceDiscovered"); 374 } 375 if ((status == BluetoothGatt.GATT_SUCCESS) && (mBluetoothGatt.getService(SERVICE_UUID) != null)) { 376 showMessage("Service discovered"); 377 Intent intent = new Intent(ACTION_CONNECTION_SERVICES_DISCOVERED); 378 sendBroadcast(intent); 379 } 380 } 381 382 @Override 383 public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { 384 String value = characteristic.getStringValue(0); 385 UUID uid = characteristic.getUuid(); 386 if (DEBUG) { 387 Log.d(TAG, "onCharacteristicWrite: characteristic.val=" + value + " status=" + status + " uid=" + uid); 388 } 389 } 390 }; 391 392 private final ScanCallback mScanCallback = new ScanCallback() { 393 @Override 394 public void onScanResult(int callbackType, ScanResult result) { 395 if (mBluetoothGatt == null) { 396 stopScan(); 397 mBluetoothGatt = BleClientService.connectGatt(result.getDevice(), mContext, false, mSecure, mGattCallbacks); 398 } 399 } 400 }; 401 402 private void startScan() { 403 if (DEBUG) { 404 Log.d(TAG, "startScan"); 405 } 406 if (!mBluetoothAdapter.isEnabled()) { 407 notifyBluetoothDisabled(); 408 } else { 409 List<ScanFilter> filter = Arrays.asList(new ScanFilter.Builder().setServiceUuid( 410 new ParcelUuid(BleConnectionPriorityServerService.ADV_SERVICE_UUID)).build()); 411 ScanSettings setting = new ScanSettings.Builder() 412 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(); 413 mScanner.startScan(filter, setting, mScanCallback); 414 } 415 } 416 417 private void stopScan() { 418 if (DEBUG) { 419 Log.d(TAG, "stopScan"); 420 } 421 if (mScanner != null) { 422 mScanner.stopScan(mScanCallback); 423 } 424 } 425 426 private void notifyMismatchSecure() { 427 Intent intent = new Intent(ACTION_BLUETOOTH_MISMATCH_SECURE); 428 sendBroadcast(intent); 429 } 430 431 private void notifyMismatchInsecure() { 432 Intent intent = new Intent(ACTION_BLUETOOTH_MISMATCH_INSECURE); 433 sendBroadcast(intent); 434 } 435 436 private void notifyDisconnect() { 437 Intent intent = new Intent(ACTION_FINISH_DISCONNECT); 438 sendBroadcast(intent); 439 } 440 } 441