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_START = 66 "com.android.cts.verifier.bluetooth.action.CONNECTION_PRIORITY_LOW_POWER"; 67 68 public static final String ACTION_CONNECTION_PRIORITY_FINISH = 69 "com.android.cts.verifier.bluetooth.action.CONNECTION_PRIORITY_FINISH"; 70 71 public static final String ACTION_CLIENT_CONNECT_SECURE = 72 "com.android.cts.verifier.bluetooth.action.CLIENT_CONNECT_SECURE"; 73 74 public static final String ACTION_DISCONNECT = 75 "com.android.cts.verifier.bluetooth.action.DISCONNECT"; 76 public static final String ACTION_FINISH_DISCONNECT = 77 "com.android.cts.verifier.bluetooth.action.FINISH_DISCONNECT"; 78 79 public static final int NOT_UNDER_TEST = -1; 80 81 private BluetoothManager mBluetoothManager; 82 private BluetoothAdapter mBluetoothAdapter; 83 private BluetoothGatt mBluetoothGatt; 84 private BluetoothLeScanner mScanner; 85 private Handler mHandler; 86 private Timer mConnectionTimer; 87 private Context mContext; 88 89 private String mAction; 90 private boolean mSecure; 91 92 private int mPriority = NOT_UNDER_TEST; 93 private int interval_low = 0; 94 private int interval_balanced = 0; 95 private int interval_high = 0; 96 97 private TestTaskQueue mTaskQueue; 98 99 @Override onCreate()100 public void onCreate() { 101 super.onCreate(); 102 103 mTaskQueue = new TestTaskQueue(getClass().getName() + "__taskHandlerThread"); 104 105 mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); 106 mBluetoothAdapter = mBluetoothManager.getAdapter(); 107 mScanner = mBluetoothAdapter.getBluetoothLeScanner(); 108 mHandler = new Handler(); 109 mContext = this; 110 111 startScan(); 112 } 113 114 @Override onDestroy()115 public void onDestroy() { 116 super.onDestroy(); 117 if (mBluetoothGatt != null) { 118 try { 119 mBluetoothGatt.disconnect(); 120 mBluetoothGatt.close(); 121 } catch (Exception e) {} 122 finally { 123 mBluetoothGatt = null; 124 } 125 } 126 stopScan(); 127 128 mTaskQueue.quit(); 129 } 130 131 @Override onBind(Intent intent)132 public IBinder onBind(Intent intent) { 133 return null; 134 } 135 notifyBluetoothDisabled()136 private void notifyBluetoothDisabled() { 137 Intent intent = new Intent(ACTION_BLUETOOTH_DISABLED); 138 sendBroadcast(intent); 139 } 140 141 @Override onStartCommand(Intent intent, int flags, int startId)142 public int onStartCommand(Intent intent, int flags, int startId) { 143 if (intent != null) { 144 mAction = intent.getAction(); 145 if (mAction != null) { 146 switch (mAction) { 147 case ACTION_CLIENT_CONNECT_SECURE: 148 mSecure = true; 149 break; 150 case ACTION_CONNECTION_PRIORITY_START: 151 myRequestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER); 152 break; 153 case ACTION_DISCONNECT: 154 if (mBluetoothGatt != null) { 155 mBluetoothGatt.disconnect(); 156 } else { 157 notifyDisconnect(); 158 } 159 break; 160 } 161 } 162 } 163 return START_NOT_STICKY; 164 } 165 myRequestConnectionPriority(final int priority)166 private void myRequestConnectionPriority(final int priority) { 167 mTaskQueue.addTask(new Runnable() { 168 @Override 169 public void run() { 170 mPriority = priority; 171 mBluetoothGatt.requestConnectionPriority(mPriority); 172 //continue in onConnectionUpdated() callback 173 } 174 }); 175 } 176 177 sleep(int millis)178 private void sleep(int millis) { 179 try { 180 Thread.sleep(millis); 181 } catch (InterruptedException e) { 182 Log.e(TAG, "Error in thread sleep", e); 183 } 184 } 185 showMessage(final String msg)186 private void showMessage(final String msg) { 187 mHandler.post(new Runnable() { 188 public void run() { 189 Toast.makeText(BleConnectionPriorityClientService.this, msg, Toast.LENGTH_SHORT).show(); 190 } 191 }); 192 } 193 194 private final BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() { 195 @Override 196 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 197 if (DEBUG) Log.d(TAG, "onConnectionStateChange"); 198 if (status == BluetoothGatt.GATT_SUCCESS) { 199 if (newState == BluetoothProfile.STATE_CONNECTED) { 200 int bond = gatt.getDevice().getBondState(); 201 boolean bonded = false; 202 BluetoothDevice target = gatt.getDevice(); 203 Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices(); 204 if (pairedDevices.size() > 0) { 205 for (BluetoothDevice device : pairedDevices) { 206 if (device.getAddress().equals(target.getAddress())) { 207 bonded = true; 208 break; 209 } 210 } 211 } 212 if (mSecure && ((bond == BluetoothDevice.BOND_NONE) || !bonded)) { 213 // not pairing and execute Secure Test 214 mBluetoothGatt.disconnect(); 215 notifyMismatchSecure(); 216 } else if (!mSecure && ((bond != BluetoothDevice.BOND_NONE) || bonded)) { 217 // already pairing nad execute Insecure Test 218 mBluetoothGatt.disconnect(); 219 notifyMismatchInsecure(); 220 } else { 221 showMessage("Bluetooth LE connected"); 222 mBluetoothGatt.discoverServices(); 223 } 224 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 225 showMessage("Bluetooth LE disconnected"); 226 227 notifyDisconnect(); 228 } 229 } else { 230 showMessage("Failed to connect"); 231 mBluetoothGatt.close(); 232 mBluetoothGatt = null; 233 } 234 } 235 236 @Override 237 public void onServicesDiscovered(BluetoothGatt gatt, int status) { 238 if (DEBUG){ 239 Log.d(TAG, "onServiceDiscovered"); 240 } 241 if (status == BluetoothGatt.GATT_SUCCESS) { 242 showMessage("Service discovered"); 243 Intent intent = new Intent(ACTION_CONNECTION_SERVICES_DISCOVERED); 244 sendBroadcast(intent); 245 } 246 247 //onConnectionUpdated is hidden callback, can't be marked as @Override. 248 // We must have a call to it, otherwise compiler will delete it during optimization. 249 if (status == 0xFFEFFEE) { 250 // This should never execute, but will make compiler not remove onConnectionUpdated 251 onConnectionUpdated(null, 0, 0, 0, 0); 252 throw new IllegalStateException("This should never happen!"); 253 } 254 } 255 256 // @Override uncomment once this becomes public API 257 public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout, 258 int status) { 259 if (mPriority == NOT_UNDER_TEST) return; 260 261 if (status != 0) { 262 showMessage("onConnectionUpdated() error, status=" + status ); 263 Log.e(TAG, "onConnectionUpdated() status=" + status); 264 return; 265 } 266 267 Log.i(TAG, "onConnectionUpdated() status=" + status + ", interval=" + interval); 268 if (mPriority == BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER) { 269 interval_low = interval; 270 myRequestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_BALANCED); 271 } else if (mPriority == BluetoothGatt.CONNECTION_PRIORITY_BALANCED) { 272 interval_balanced = interval; 273 myRequestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH); 274 } else if (mPriority == BluetoothGatt.CONNECTION_PRIORITY_HIGH) { 275 interval_high = interval; 276 277 if (interval_low < interval_balanced || interval_balanced < interval_high) { 278 showMessage("interval value should be descending - failure!"); 279 Log.e(TAG, "interval values should be descending: interval_low=" + interval_low + 280 ", interval_balanced=" + interval_balanced + ", interval_high=" + interval_high); 281 return; 282 } 283 284 showMessage("intervals: " + interval_low +" > " + interval_balanced + " > " + interval_high); 285 286 Intent intent = new Intent(); 287 intent.setAction(ACTION_CONNECTION_PRIORITY_FINISH); 288 sendBroadcast(intent); 289 290 mPriority = NOT_UNDER_TEST; 291 } 292 } 293 }; 294 295 private final ScanCallback mScanCallback = new ScanCallback() { 296 @Override 297 public void onScanResult(int callbackType, ScanResult result) { 298 if (mBluetoothGatt == null) { 299 stopScan(); 300 mBluetoothGatt = BleClientService.connectGatt(result.getDevice(), mContext, false, mSecure, mGattCallbacks); 301 } 302 } 303 }; 304 startScan()305 private void startScan() { 306 if (DEBUG) { 307 Log.d(TAG, "startScan"); 308 } 309 if (!mBluetoothAdapter.isEnabled()) { 310 notifyBluetoothDisabled(); 311 } else { 312 List<ScanFilter> filter = Arrays.asList(new ScanFilter.Builder().setServiceUuid( 313 new ParcelUuid(BleConnectionPriorityServerService.ADV_SERVICE_UUID)).build()); 314 ScanSettings setting = new ScanSettings.Builder() 315 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(); 316 mScanner.startScan(filter, setting, mScanCallback); 317 } 318 } 319 stopScan()320 private void stopScan() { 321 if (DEBUG) { 322 Log.d(TAG, "stopScan"); 323 } 324 if (mScanner != null) { 325 mScanner.stopScan(mScanCallback); 326 } 327 } 328 notifyMismatchSecure()329 private void notifyMismatchSecure() { 330 Intent intent = new Intent(ACTION_BLUETOOTH_MISMATCH_SECURE); 331 sendBroadcast(intent); 332 } 333 notifyMismatchInsecure()334 private void notifyMismatchInsecure() { 335 Intent intent = new Intent(ACTION_BLUETOOTH_MISMATCH_INSECURE); 336 sendBroadcast(intent); 337 } 338 notifyDisconnect()339 private void notifyDisconnect() { 340 Intent intent = new Intent(ACTION_FINISH_DISCONNECT); 341 sendBroadcast(intent); 342 } 343 } 344