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.BluetoothDevice; 21 import android.bluetooth.BluetoothGatt; 22 import android.bluetooth.BluetoothGattCharacteristic; 23 import android.bluetooth.BluetoothGattDescriptor; 24 import android.bluetooth.BluetoothGattServer; 25 import android.bluetooth.BluetoothGattServerCallback; 26 import android.bluetooth.BluetoothGattService; 27 import android.bluetooth.BluetoothManager; 28 import android.bluetooth.BluetoothProfile; 29 import android.bluetooth.le.AdvertiseCallback; 30 import android.bluetooth.le.AdvertiseData; 31 import android.bluetooth.le.AdvertiseSettings; 32 import android.bluetooth.le.BluetoothLeAdvertiser; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.os.Handler; 36 import android.os.IBinder; 37 import android.os.ParcelUuid; 38 import android.util.Log; 39 import android.widget.Toast; 40 41 import java.util.Timer; 42 import java.util.TimerTask; 43 import java.util.UUID; 44 45 public class BleConnectionPriorityServerService extends Service { 46 public static final boolean DEBUG = true; 47 public static final String TAG = "BlePriorityServer"; 48 private static final String RESET_COUNT_VALUE = "RESET"; 49 private static final String START_VALUE = "START"; 50 private static final String STOP_VALUE = "STOP"; 51 public static final String CONNECTION_PRIORITY_HIGH = "PR_H"; 52 public static final String CONNECTION_PRIORITY_BALANCED = "PR_B"; 53 public static final String CONNECTION_PRIORITY_LOW_POWER = "PR_L"; 54 55 public static final String ACTION_BLUETOOTH_DISABLED = 56 "com.android.cts.verifier.bluetooth.action.BLUETOOTH_DISABLED"; 57 58 public static final String ACTION_CONNECTION_WRITE_REQUEST = 59 "com.android.cts.verifier.bluetooth.action.CONNECTION_WRITE_REQUEST"; 60 public static final String EXTRA_REQUEST_COUNT = 61 "com.android.cts.verifier.bluetooth.intent.EXTRA_REQUEST_COUNT"; 62 public static final String ACTION_FINICH_CONNECTION_PRIORITY_HIGHT = 63 "com.android.cts.verifier.bluetooth.action.ACTION_FINICH_CONNECTION_PRIORITY_HIGHT"; 64 public static final String ACTION_FINICH_CONNECTION_PRIORITY_BALANCED = 65 "com.android.cts.verifier.bluetooth.action.ACTION_FINICH_CONNECTION_PRIORITY_BALANCED"; 66 public static final String ACTION_FINICH_CONNECTION_PRIORITY_LOW = 67 "com.android.cts.verifier.bluetooth.action.ACTION_FINICH_CONNECTION_PRIORITY_LOW"; 68 69 public static final String ACTION_START_CONNECTION_PRIORITY_TEST = 70 "com.android.cts.verifier.bluetooth.action.ACTION_START_CONNECTION_PRIORITY_TEST"; 71 72 public static final String EXTRA_AVERAGE = 73 "com.android.cts.verifier.bluetooth.intent.EXTRA_AVERAGE"; 74 75 private static final UUID SERVICE_UUID = 76 UUID.fromString("00009999-0000-1000-8000-00805f9b34fb"); 77 private static final UUID CHARACTERISTIC_UUID = 78 UUID.fromString("00009998-0000-1000-8000-00805f9b34fb"); 79 private static final UUID START_CHARACTERISTIC_UUID = 80 UUID.fromString("00009997-0000-1000-8000-00805f9b34fb"); 81 private static final UUID STOP_CHARACTERISTIC_UUID = 82 UUID.fromString("00009995-0000-1000-8000-00805f9b34fb"); 83 private static final UUID DESCRIPTOR_UUID = 84 UUID.fromString("00009996-0000-1000-8000-00805f9b34fb"); 85 public static final UUID ADV_SERVICE_UUID= 86 UUID.fromString("00002222-0000-1000-8000-00805f9b34fb"); 87 88 private BluetoothManager mBluetoothManager; 89 private BluetoothGattServer mGattServer; 90 private BluetoothGattService mService; 91 private BluetoothDevice mDevice; 92 private Handler mHandler; 93 private BluetoothLeAdvertiser mAdvertiser; 94 private long mReceiveWriteCount; 95 private Timer mTimeoutTimer; 96 private TimerTask mTimeoutTimerTask; 97 98 @Override onCreate()99 public void onCreate() { 100 super.onCreate(); 101 102 mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 103 mAdvertiser = mBluetoothManager.getAdapter().getBluetoothLeAdvertiser(); 104 mGattServer = mBluetoothManager.openGattServer(this, mCallbacks); 105 mService = createService(); 106 if ((mGattServer != null) && (mAdvertiser != null)) { 107 mGattServer.addService(mService); 108 } 109 mDevice = null; 110 mHandler = new Handler(); 111 112 if (!mBluetoothManager.getAdapter().isEnabled()) { 113 notifyBluetoothDisabled(); 114 } else if (mGattServer == null) { 115 notifyOpenFail(); 116 } else if (mAdvertiser == null) { 117 notifyAdvertiseUnsupported(); 118 } else { 119 startAdvertise(); 120 } 121 } 122 123 @Override onDestroy()124 public void onDestroy() { 125 super.onDestroy(); 126 127 cancelTimeoutTimer(false); 128 129 if (mTimeoutTimer != null) { 130 mTimeoutTimer.cancel(); 131 mTimeoutTimer = null; 132 } 133 mTimeoutTimerTask = null; 134 135 stopAdvertise(); 136 if (mGattServer == null) { 137 return; 138 } 139 if (mDevice != null) { 140 mGattServer.cancelConnection(mDevice); 141 } 142 mGattServer.clearServices(); 143 mGattServer.close(); 144 } 145 146 @Override onBind(Intent intent)147 public IBinder onBind(Intent intent) { 148 return null; 149 } 150 151 @Override onStartCommand(Intent intent, int flags, int startId)152 public int onStartCommand(Intent intent, int flags, int startId) { 153 return START_NOT_STICKY; 154 } 155 notifyBluetoothDisabled()156 private void notifyBluetoothDisabled() { 157 if (DEBUG) { 158 Log.d(TAG, "notifyBluetoothDisabled"); 159 } 160 Intent intent = new Intent(ACTION_BLUETOOTH_DISABLED); 161 sendBroadcast(intent); 162 } 163 notifyTestStart()164 private void notifyTestStart() { 165 Intent intent = new Intent(BleConnectionPriorityServerService.ACTION_START_CONNECTION_PRIORITY_TEST); 166 sendBroadcast(intent); 167 } 168 notifyOpenFail()169 private void notifyOpenFail() { 170 if (DEBUG) { 171 Log.d(TAG, "notifyOpenFail"); 172 } 173 Intent intent = new Intent(BleServerService.BLE_OPEN_FAIL); 174 sendBroadcast(intent); 175 } 176 notifyAdvertiseUnsupported()177 private void notifyAdvertiseUnsupported() { 178 if (DEBUG) { 179 Log.d(TAG, "notifyAdvertiseUnsupported"); 180 } 181 Intent intent = new Intent(BleServerService.BLE_ADVERTISE_UNSUPPORTED); 182 sendBroadcast(intent); 183 } 184 notifyConnected()185 private void notifyConnected() { 186 if (DEBUG) { 187 Log.d(TAG, "notifyConnected"); 188 } 189 } 190 notifyDisconnected()191 private void notifyDisconnected() { 192 if (DEBUG) { 193 Log.d(TAG, "notifyDisconnected"); 194 } 195 } 196 notifyServiceAdded()197 private void notifyServiceAdded() { 198 if (DEBUG) { 199 Log.d(TAG, "notifyServiceAdded"); 200 } 201 } 202 notifyCharacteristicWriteRequest()203 private void notifyCharacteristicWriteRequest() { 204 if (DEBUG) { 205 Log.d(TAG, "notifyCharacteristicWriteRequest"); 206 } 207 Intent intent = new Intent(ACTION_CONNECTION_WRITE_REQUEST); 208 intent.putExtra(EXTRA_REQUEST_COUNT, String.valueOf(mReceiveWriteCount)); 209 sendBroadcast(intent); 210 } 211 showMessage(final String msg)212 private void showMessage(final String msg) { 213 mHandler.post(new Runnable() { 214 @Override 215 public void run() { 216 Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); 217 } 218 }); 219 } 220 cancelTimeoutTimer(boolean runTimeout)221 private synchronized void cancelTimeoutTimer(boolean runTimeout) { 222 if (mTimeoutTimerTask != null) { 223 mTimeoutTimer.cancel(); 224 if (runTimeout) { 225 mTimeoutTimerTask.run(); 226 } 227 mTimeoutTimerTask = null; 228 mTimeoutTimer = null; 229 } 230 } 231 createService()232 private BluetoothGattService createService() { 233 BluetoothGattService service = 234 new BluetoothGattService(SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY); 235 // add characteristic to service 236 // property: 0x0A (read, write) 237 // permission: 0x11 (read, write) 238 BluetoothGattCharacteristic characteristic = 239 new BluetoothGattCharacteristic(CHARACTERISTIC_UUID, 0x0A, 0x11); 240 BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(DESCRIPTOR_UUID, 0x11); 241 characteristic.addDescriptor(descriptor); 242 service.addCharacteristic(characteristic); 243 characteristic = new BluetoothGattCharacteristic(START_CHARACTERISTIC_UUID, 0x0A, 0x11); 244 characteristic.addDescriptor(descriptor); 245 service.addCharacteristic(characteristic); 246 characteristic = new BluetoothGattCharacteristic(STOP_CHARACTERISTIC_UUID, 0x0A, 0x11); 247 characteristic.addDescriptor(descriptor); 248 service.addCharacteristic(characteristic); 249 250 return service; 251 } 252 253 private final BluetoothGattServerCallback mCallbacks = new BluetoothGattServerCallback() { 254 @Override 255 public void onConnectionStateChange(BluetoothDevice device, int status, int newState) { 256 if (DEBUG) { 257 Log.d(TAG, "onConnectionStateChange: newState=" + newState); 258 } 259 if (status == BluetoothGatt.GATT_SUCCESS) { 260 if (newState == BluetoothProfile.STATE_CONNECTED) { 261 mDevice = device; 262 notifyConnected(); 263 } else if (status == BluetoothProfile.STATE_DISCONNECTED) { 264 cancelTimeoutTimer(true); 265 notifyDisconnected(); 266 mDevice = null; 267 } 268 } 269 } 270 271 @Override 272 public void onServiceAdded(int status, BluetoothGattService service) { 273 if (DEBUG) { 274 Log.d(TAG, "onServiceAdded()"); 275 } 276 if (status == BluetoothGatt.GATT_SUCCESS) { 277 notifyServiceAdded(); 278 } 279 } 280 281 String mPriority = null; 282 283 @Override 284 public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, 285 BluetoothGattCharacteristic characteristic, 286 boolean preparedWrite, boolean responseNeeded, 287 int offset, byte[] value) { 288 if (mGattServer == null) { 289 if (DEBUG) { 290 Log.d(TAG, "GattServer is null, return"); 291 } 292 return; 293 } 294 if (DEBUG) { 295 Log.d(TAG, "onCharacteristicWriteRequest: preparedWrite=" + preparedWrite); 296 } 297 298 if (characteristic.getUuid().equals(START_CHARACTERISTIC_UUID)) { 299 // time out if previous measurement is running 300 cancelTimeoutTimer(true); 301 302 mPriority = new String(value); 303 Log.d(TAG, "Start Count Up. Priority is " + mPriority); 304 if (BleConnectionPriorityServerService.CONNECTION_PRIORITY_HIGH.equals(mPriority)) { 305 notifyTestStart(); 306 } 307 308 // start timeout timer 309 mTimeoutTimer = new Timer(getClass().getName() + "_TimeoutTimer"); 310 mTimeoutTimerTask = new TimerTask() { 311 @Override 312 public void run() { 313 // measurement timed out 314 mTimeoutTimerTask = null; 315 mTimeoutTimer = null; 316 mReceiveWriteCount = 0; 317 notifyMeasurementFinished(mPriority, Long.MAX_VALUE); 318 } 319 }; 320 mTimeoutTimer.schedule(mTimeoutTimerTask, (BleConnectionPriorityClientService.DEFAULT_PERIOD * 2)); 321 322 mReceiveWriteCount = 0; 323 } else if (characteristic.getUuid().equals(STOP_CHARACTERISTIC_UUID)) { 324 boolean isRunning = (mTimeoutTimerTask != null); 325 cancelTimeoutTimer(false); 326 327 String valeStr = new String(value); 328 String priority = null; 329 int writeCount = -1; 330 int sep = valeStr.indexOf(","); 331 if (sep > 0) { 332 priority = valeStr.substring(0, sep); 333 writeCount = Integer.valueOf(valeStr.substring(sep + 1)); 334 } 335 336 if ((mPriority != null) && isRunning) { 337 if (mPriority.equals(priority)) { 338 long averageTime = BleConnectionPriorityClientService.DEFAULT_PERIOD / mReceiveWriteCount; 339 notifyMeasurementFinished(mPriority, averageTime); 340 Log.d(TAG, "Received " + mReceiveWriteCount + " of " + writeCount + " messages"); 341 } else { 342 Log.d(TAG, "Connection priority does not match"); 343 showMessage("Connection priority does not match"); 344 } 345 } else { 346 Log.d(TAG, "Not Start Count UP."); 347 } 348 mReceiveWriteCount = 0; 349 } else { 350 if (mTimeoutTimerTask != null) { 351 ++mReceiveWriteCount; 352 } 353 if (!preparedWrite) { 354 characteristic.setValue(value); 355 } 356 } 357 358 if (responseNeeded) { 359 mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null); 360 } 361 } 362 }; 363 notifyMeasurementFinished(String priority, long averageTime)364 private void notifyMeasurementFinished(String priority, long averageTime) { 365 Intent intent = new Intent(); 366 intent.putExtra(EXTRA_AVERAGE, averageTime); 367 switch (priority) { 368 case CONNECTION_PRIORITY_HIGH: 369 intent.setAction(ACTION_FINICH_CONNECTION_PRIORITY_HIGHT); 370 break; 371 case CONNECTION_PRIORITY_BALANCED: 372 intent.setAction(ACTION_FINICH_CONNECTION_PRIORITY_BALANCED); 373 break; 374 case CONNECTION_PRIORITY_LOW_POWER: 375 intent.setAction(ACTION_FINICH_CONNECTION_PRIORITY_LOW); 376 break; 377 } 378 sendBroadcast(intent); 379 } 380 381 private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() { 382 @Override 383 public void onStartFailure(int errorCode) { 384 super.onStartFailure(errorCode); 385 if (errorCode == ADVERTISE_FAILED_FEATURE_UNSUPPORTED) { 386 notifyAdvertiseUnsupported(); 387 } else { 388 notifyOpenFail(); 389 } 390 } 391 }; 392 startAdvertise()393 private void startAdvertise() { 394 if (DEBUG) { 395 Log.d(TAG, "startAdvertise"); 396 } 397 AdvertiseData data = new AdvertiseData.Builder() 398 .addServiceData(new ParcelUuid(ADV_SERVICE_UUID), new byte[]{1, 2, 3}) 399 .addServiceUuid(new ParcelUuid(ADV_SERVICE_UUID)) 400 .build(); 401 AdvertiseSettings setting = new AdvertiseSettings.Builder() 402 .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) 403 .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) 404 .setConnectable(true) 405 .build(); 406 mAdvertiser.startAdvertising(setting, data, mAdvertiseCallback); 407 } 408 stopAdvertise()409 private void stopAdvertise() { 410 if (DEBUG) { 411 Log.d(TAG, "stopAdvertise"); 412 } 413 if (mAdvertiser != null) { 414 mAdvertiser.stopAdvertising(mAdvertiseCallback); 415 } 416 } 417 418 } 419