1 /* 2 * Copyright 2021 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.server.nearby.common.bluetooth.gatt; 18 19 import android.bluetooth.BluetoothGatt; 20 import android.bluetooth.BluetoothGattCharacteristic; 21 import android.bluetooth.BluetoothGattDescriptor; 22 import android.bluetooth.le.ScanFilter; 23 import android.bluetooth.le.ScanSettings; 24 import android.content.Context; 25 import android.os.ParcelUuid; 26 import android.util.Log; 27 28 import androidx.annotation.IntDef; 29 import androidx.annotation.VisibleForTesting; 30 31 import com.android.server.nearby.common.bluetooth.BluetoothException; 32 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter; 33 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothDevice; 34 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattCallback; 35 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothGattWrapper; 36 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.BluetoothLeScanner; 37 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.ScanCallback; 38 import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le.ScanResult; 39 import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor; 40 import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.BluetoothOperationTimeoutException; 41 import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor.Operation; 42 43 import com.google.common.base.Preconditions; 44 45 import java.lang.annotation.Retention; 46 import java.lang.annotation.RetentionPolicy; 47 import java.util.Arrays; 48 import java.util.Locale; 49 import java.util.Objects; 50 import java.util.Optional; 51 import java.util.UUID; 52 import java.util.concurrent.ConcurrentHashMap; 53 import java.util.concurrent.ConcurrentMap; 54 import java.util.concurrent.TimeUnit; 55 56 import javax.annotation.Nullable; 57 import javax.annotation.concurrent.GuardedBy; 58 59 /** 60 * Wrapper of {@link BluetoothGattWrapper} that provides blocking methods, errors and timeout 61 * handling. 62 */ 63 @SuppressWarnings("Guava") // java.util.Optional is not available until API 24 64 public class BluetoothGattHelper { 65 66 private static final String TAG = BluetoothGattHelper.class.getSimpleName(); 67 68 @VisibleForTesting 69 static final long LOW_LATENCY_SCAN_MILLIS = TimeUnit.SECONDS.toMillis(5); 70 private static final long POLL_INTERVAL_MILLIS = 5L /* milliseconds */; 71 72 /** 73 * BT operation types that can be in flight. 74 */ 75 @Retention(RetentionPolicy.SOURCE) 76 @IntDef( 77 value = { 78 OperationType.SCAN, 79 OperationType.CONNECT, 80 OperationType.DISCOVER_SERVICES, 81 OperationType.DISCOVER_SERVICES_INTERNAL, 82 OperationType.NOTIFICATION_CHANGE, 83 OperationType.READ_CHARACTERISTIC, 84 OperationType.WRITE_CHARACTERISTIC, 85 OperationType.READ_DESCRIPTOR, 86 OperationType.WRITE_DESCRIPTOR, 87 OperationType.READ_RSSI, 88 OperationType.WRITE_RELIABLE, 89 OperationType.CHANGE_MTU, 90 OperationType.DISCONNECT, 91 }) 92 public @interface OperationType { 93 int SCAN = 0; 94 int CONNECT = 1; 95 int DISCOVER_SERVICES = 2; 96 int DISCOVER_SERVICES_INTERNAL = 3; 97 int NOTIFICATION_CHANGE = 4; 98 int READ_CHARACTERISTIC = 5; 99 int WRITE_CHARACTERISTIC = 6; 100 int READ_DESCRIPTOR = 7; 101 int WRITE_DESCRIPTOR = 8; 102 int READ_RSSI = 9; 103 int WRITE_RELIABLE = 10; 104 int CHANGE_MTU = 11; 105 int DISCONNECT = 12; 106 } 107 108 @VisibleForTesting 109 final ScanCallback mScanCallback = new InternalScanCallback(); 110 @VisibleForTesting 111 final BluetoothGattCallback mBluetoothGattCallback = 112 new InternalBluetoothGattCallback(); 113 @VisibleForTesting 114 final ConcurrentMap<BluetoothGattWrapper, BluetoothGattConnection> mConnections = 115 new ConcurrentHashMap<>(); 116 117 private final Context mApplicationContext; 118 private final BluetoothAdapter mBluetoothAdapter; 119 private final BluetoothOperationExecutor mBluetoothOperationExecutor; 120 121 @VisibleForTesting BluetoothGattHelper( Context applicationContext, BluetoothAdapter bluetoothAdapter, BluetoothOperationExecutor bluetoothOperationExecutor)122 BluetoothGattHelper( 123 Context applicationContext, 124 BluetoothAdapter bluetoothAdapter, 125 BluetoothOperationExecutor bluetoothOperationExecutor) { 126 mApplicationContext = applicationContext; 127 mBluetoothAdapter = bluetoothAdapter; 128 mBluetoothOperationExecutor = bluetoothOperationExecutor; 129 } 130 BluetoothGattHelper(Context applicationContext, BluetoothAdapter bluetoothAdapter)131 public BluetoothGattHelper(Context applicationContext, BluetoothAdapter bluetoothAdapter) { 132 this( 133 Preconditions.checkNotNull(applicationContext), 134 Preconditions.checkNotNull(bluetoothAdapter), 135 new BluetoothOperationExecutor(5)); 136 } 137 138 /** 139 * Auto-connects a serice Uuid. 140 */ autoConnect(final UUID serviceUuid)141 public BluetoothGattConnection autoConnect(final UUID serviceUuid) throws BluetoothException { 142 Log.d(TAG, String.format("Starting autoconnection to a device advertising service %s.", 143 serviceUuid)); 144 BluetoothDevice device = null; 145 int retries = 3; 146 final BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner(); 147 if (scanner == null) { 148 throw new BluetoothException("Bluetooth is disabled or LE is not supported."); 149 } 150 final ScanFilter serviceFilter = new ScanFilter.Builder() 151 .setServiceUuid(new ParcelUuid(serviceUuid)) 152 .build(); 153 ScanSettings.Builder scanSettingsBuilder = new ScanSettings.Builder() 154 .setReportDelay(0); 155 final ScanSettings scanSettingsLowLatency = scanSettingsBuilder 156 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) 157 .build(); 158 final ScanSettings scanSettingsLowPower = scanSettingsBuilder 159 .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER) 160 .build(); 161 while (true) { 162 long startTimeMillis = System.currentTimeMillis(); 163 try { 164 Log.d(TAG, "Starting low latency scanning."); 165 device = 166 mBluetoothOperationExecutor.executeNonnull( 167 new Operation<BluetoothDevice>(OperationType.SCAN) { 168 @Override 169 public void run() throws BluetoothException { 170 scanner.startScan(Arrays.asList(serviceFilter), 171 scanSettingsLowLatency, mScanCallback); 172 } 173 }, LOW_LATENCY_SCAN_MILLIS); 174 } catch (BluetoothOperationTimeoutException e) { 175 Log.d(TAG, String.format( 176 "Cannot find a nearby device in low latency scanning after %s ms.", 177 LOW_LATENCY_SCAN_MILLIS)); 178 } finally { 179 scanner.stopScan(mScanCallback); 180 } 181 if (device == null) { 182 Log.d(TAG, "Starting low power scanning."); 183 try { 184 device = mBluetoothOperationExecutor.executeNonnull( 185 new Operation<BluetoothDevice>(OperationType.SCAN) { 186 @Override 187 public void run() throws BluetoothException { 188 scanner.startScan(Arrays.asList(serviceFilter), 189 scanSettingsLowPower, mScanCallback); 190 } 191 }); 192 } finally { 193 scanner.stopScan(mScanCallback); 194 } 195 } 196 Log.d(TAG, String.format("Scanning done in %d ms. Found device %s.", 197 System.currentTimeMillis() - startTimeMillis, device)); 198 199 try { 200 return connect(device); 201 } catch (BluetoothException e) { 202 retries--; 203 if (retries == 0) { 204 throw e; 205 } else { 206 Log.d(TAG, String.format( 207 "Connection failed: %s. Retrying %d more times.", e, retries)); 208 } 209 } 210 } 211 } 212 213 /** 214 * Connects to a device using default connection options. 215 */ connect(BluetoothDevice bluetoothDevice)216 public BluetoothGattConnection connect(BluetoothDevice bluetoothDevice) 217 throws BluetoothException { 218 return connect(bluetoothDevice, ConnectionOptions.builder().build()); 219 } 220 221 /** 222 * Connects to a device using specifies connection options. 223 */ connect( BluetoothDevice bluetoothDevice, ConnectionOptions options)224 public BluetoothGattConnection connect( 225 BluetoothDevice bluetoothDevice, ConnectionOptions options) throws BluetoothException { 226 Log.d(TAG, String.format("Connecting to device %s.", bluetoothDevice)); 227 long startTimeMillis = System.currentTimeMillis(); 228 229 Operation<BluetoothGattConnection> connectOperation = 230 new Operation<BluetoothGattConnection>(OperationType.CONNECT, bluetoothDevice) { 231 private final Object mLock = new Object(); 232 233 @GuardedBy("mLock") 234 private boolean mIsCanceled = false; 235 236 @GuardedBy("mLock") 237 @Nullable(/* null before operation is executed */) 238 private BluetoothGattWrapper mBluetoothGatt; 239 240 @Override 241 public void run() throws BluetoothException { 242 synchronized (mLock) { 243 if (mIsCanceled) { 244 return; 245 } 246 BluetoothGattWrapper bluetoothGattWrapper; 247 Log.d(TAG, "Use LE transport"); 248 bluetoothGattWrapper = 249 bluetoothDevice.connectGatt( 250 mApplicationContext, 251 options.autoConnect(), 252 mBluetoothGattCallback, 253 android.bluetooth.BluetoothDevice.TRANSPORT_LE); 254 if (bluetoothGattWrapper == null) { 255 throw new BluetoothException("connectGatt() returned null."); 256 } 257 258 try { 259 // Set connection priority without waiting for connection callback. 260 // Per code, btif_gatt_client.c, when priority is set before 261 // connection, this sets preferred connection parameters that will 262 // be used during the connection establishment. 263 Optional<Integer> connectionPriorityOption = 264 options.connectionPriority(); 265 if (connectionPriorityOption.isPresent()) { 266 // requestConnectionPriority can only be called when 267 // BluetoothGatt is connected to the system BluetoothGatt 268 // service (see android/bluetooth/BluetoothGatt.java code). 269 // However, there is no callback to the app to inform when this 270 // is done. requestConnectionPriority will returns false with no 271 // side-effect before the service is connected, so we just poll 272 // here until true is returned. 273 int connectionPriority = connectionPriorityOption.get(); 274 long startTimeMillis = System.currentTimeMillis(); 275 while (!bluetoothGattWrapper.requestConnectionPriority( 276 connectionPriority)) { 277 if (System.currentTimeMillis() - startTimeMillis 278 > options.connectionTimeoutMillis()) { 279 throw new BluetoothException( 280 String.format( 281 Locale.US, 282 "Failed to set connectionPriority " 283 + "after %dms.", 284 options.connectionTimeoutMillis())); 285 } 286 try { 287 Thread.sleep(POLL_INTERVAL_MILLIS); 288 } catch (InterruptedException e) { 289 Thread.currentThread().interrupt(); 290 throw new BluetoothException( 291 "connect() operation interrupted."); 292 } 293 } 294 } 295 } catch (Exception e) { 296 // Make sure to clean connection. 297 bluetoothGattWrapper.disconnect(); 298 bluetoothGattWrapper.close(); 299 throw e; 300 } 301 302 BluetoothGattConnection connection = new BluetoothGattConnection( 303 bluetoothGattWrapper, mBluetoothOperationExecutor, options); 304 mConnections.put(bluetoothGattWrapper, connection); 305 mBluetoothGatt = bluetoothGattWrapper; 306 } 307 } 308 309 @Override 310 public void cancel() { 311 // Clean connection if connection times out. 312 synchronized (mLock) { 313 if (mIsCanceled) { 314 return; 315 } 316 mIsCanceled = true; 317 BluetoothGattWrapper bluetoothGattWrapper = mBluetoothGatt; 318 if (bluetoothGattWrapper == null) { 319 return; 320 } 321 mConnections.remove(bluetoothGattWrapper); 322 bluetoothGattWrapper.disconnect(); 323 bluetoothGattWrapper.close(); 324 } 325 } 326 }; 327 BluetoothGattConnection result; 328 if (options.autoConnect()) { 329 result = mBluetoothOperationExecutor.executeNonnull(connectOperation); 330 } else { 331 result = 332 mBluetoothOperationExecutor.executeNonnull( 333 connectOperation, options.connectionTimeoutMillis()); 334 } 335 Log.d(TAG, String.format("Connection success in %d ms.", 336 System.currentTimeMillis() - startTimeMillis)); 337 return result; 338 } 339 getConnectionByGatt(BluetoothGattWrapper gatt)340 private BluetoothGattConnection getConnectionByGatt(BluetoothGattWrapper gatt) 341 throws BluetoothException { 342 BluetoothGattConnection connection = mConnections.get(gatt); 343 if (connection == null) { 344 throw new BluetoothException("Receive callback on unexpected device: " + gatt); 345 } 346 return connection; 347 } 348 349 private class InternalBluetoothGattCallback extends BluetoothGattCallback { 350 351 @Override onConnectionStateChange(BluetoothGattWrapper gatt, int status, int newState)352 public void onConnectionStateChange(BluetoothGattWrapper gatt, int status, int newState) { 353 BluetoothGattConnection connection; 354 BluetoothDevice device = gatt.getDevice(); 355 switch (newState) { 356 case BluetoothGatt.STATE_CONNECTED: { 357 connection = mConnections.get(gatt); 358 if (connection == null) { 359 Log.w(TAG, String.format( 360 "Received unexpected successful connection for dev %s! Ignoring.", 361 device)); 362 break; 363 } 364 365 Operation<BluetoothGattConnection> operation = 366 new Operation<>(OperationType.CONNECT, device); 367 if (status != BluetoothGatt.GATT_SUCCESS) { 368 mConnections.remove(gatt); 369 gatt.disconnect(); 370 gatt.close(); 371 mBluetoothOperationExecutor.notifyCompletion(operation, status, null); 372 break; 373 } 374 375 // Process connection options 376 ConnectionOptions options = connection.getConnectionOptions(); 377 Optional<Integer> mtuOption = options.mtu(); 378 if (mtuOption.isPresent()) { 379 // Requesting MTU and waiting for MTU callback. 380 boolean success = gatt.requestMtu(mtuOption.get()); 381 if (!success) { 382 mBluetoothOperationExecutor.notifyFailure(operation, 383 new BluetoothException(String.format(Locale.US, 384 "Failed to request MTU of %d for dev %s: " 385 + "returned false.", 386 mtuOption.get(), device))); 387 // Make sure to clean connection. 388 mConnections.remove(gatt); 389 gatt.disconnect(); 390 gatt.close(); 391 } 392 break; 393 } 394 395 // Connection successful 396 connection.onConnected(); 397 mBluetoothOperationExecutor.notifyCompletion(operation, status, connection); 398 break; 399 } 400 case BluetoothGatt.STATE_DISCONNECTED: { 401 connection = mConnections.remove(gatt); 402 if (connection == null) { 403 Log.w(TAG, String.format("Received unexpected disconnection" 404 + " for device %s! Ignoring.", device)); 405 break; 406 } 407 if (!connection.isConnected()) { 408 // This is a failed connection attempt 409 if (status == BluetoothGatt.GATT_SUCCESS) { 410 // This is weird... considering this as a failure 411 Log.w(TAG, String.format( 412 "Received a success for a failed connection " 413 + "attempt for device %s! Ignoring.", device)); 414 status = BluetoothGatt.GATT_FAILURE; 415 } 416 mBluetoothOperationExecutor 417 .notifyCompletion(new Operation<BluetoothGattConnection>( 418 OperationType.CONNECT, device), status, null); 419 // Clean Gatt object in every case. 420 gatt.disconnect(); 421 gatt.close(); 422 break; 423 } 424 connection.onClosed(); 425 mBluetoothOperationExecutor.notifyCompletion( 426 new Operation<>(OperationType.DISCONNECT, device), status); 427 break; 428 } 429 default: 430 Log.e(TAG, "Unexpected connection state: " + newState); 431 } 432 } 433 434 @Override onMtuChanged(BluetoothGattWrapper gatt, int mtu, int status)435 public void onMtuChanged(BluetoothGattWrapper gatt, int mtu, int status) { 436 BluetoothGattConnection connection = mConnections.get(gatt); 437 BluetoothDevice device = gatt.getDevice(); 438 if (connection == null) { 439 Log.w(TAG, String.format( 440 "Received unexpected MTU change for device %s! Ignoring.", device)); 441 return; 442 } 443 if (connection.isConnected()) { 444 // This is the callback for the deprecated BluetoothGattConnection.requestMtu. 445 mBluetoothOperationExecutor.notifyCompletion( 446 new Operation<>(OperationType.CHANGE_MTU, gatt), status, mtu); 447 } else { 448 // This is the callback when requesting MTU right after connecting. 449 connection.onConnected(); 450 mBluetoothOperationExecutor.notifyCompletion( 451 new Operation<>(OperationType.CONNECT, device), status, connection); 452 if (status != BluetoothGatt.GATT_SUCCESS) { 453 Log.w(TAG, String.format( 454 "%s responds MTU change failed, status %s.", device, status)); 455 // Clean connection if it's failed. 456 mConnections.remove(gatt); 457 gatt.disconnect(); 458 gatt.close(); 459 return; 460 } 461 } 462 } 463 464 @Override onServicesDiscovered(BluetoothGattWrapper gatt, int status)465 public void onServicesDiscovered(BluetoothGattWrapper gatt, int status) { 466 mBluetoothOperationExecutor.notifyCompletion( 467 new Operation<Void>(OperationType.DISCOVER_SERVICES_INTERNAL, gatt), status); 468 } 469 470 @Override onCharacteristicRead(BluetoothGattWrapper gatt, BluetoothGattCharacteristic characteristic, int status)471 public void onCharacteristicRead(BluetoothGattWrapper gatt, 472 BluetoothGattCharacteristic characteristic, int status) { 473 mBluetoothOperationExecutor.notifyCompletion( 474 new Operation<byte[]>(OperationType.READ_CHARACTERISTIC, gatt, characteristic), 475 status, characteristic.getValue()); 476 } 477 478 @Override onCharacteristicWrite(BluetoothGattWrapper gatt, BluetoothGattCharacteristic characteristic, int status)479 public void onCharacteristicWrite(BluetoothGattWrapper gatt, 480 BluetoothGattCharacteristic characteristic, int status) { 481 mBluetoothOperationExecutor.notifyCompletion(new Operation<Void>( 482 OperationType.WRITE_CHARACTERISTIC, gatt, characteristic), status); 483 } 484 485 @Override onDescriptorRead(BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor, int status)486 public void onDescriptorRead(BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor, 487 int status) { 488 mBluetoothOperationExecutor.notifyCompletion( 489 new Operation<byte[]>(OperationType.READ_DESCRIPTOR, gatt, descriptor), status, 490 descriptor.getValue()); 491 } 492 493 @Override onDescriptorWrite(BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor, int status)494 public void onDescriptorWrite(BluetoothGattWrapper gatt, BluetoothGattDescriptor descriptor, 495 int status) { 496 Log.d(TAG, String.format("onDescriptorWrite %s, %s, %d", 497 gatt.getDevice(), descriptor.getUuid(), status)); 498 mBluetoothOperationExecutor.notifyCompletion( 499 new Operation<Void>(OperationType.WRITE_DESCRIPTOR, gatt, descriptor), status); 500 } 501 502 @Override onReadRemoteRssi(BluetoothGattWrapper gatt, int rssi, int status)503 public void onReadRemoteRssi(BluetoothGattWrapper gatt, int rssi, int status) { 504 mBluetoothOperationExecutor.notifyCompletion( 505 new Operation<Integer>(OperationType.READ_RSSI, gatt), status, rssi); 506 } 507 508 @Override onReliableWriteCompleted(BluetoothGattWrapper gatt, int status)509 public void onReliableWriteCompleted(BluetoothGattWrapper gatt, int status) { 510 mBluetoothOperationExecutor.notifyCompletion( 511 new Operation<Void>(OperationType.WRITE_RELIABLE, gatt), status); 512 } 513 514 @Override onCharacteristicChanged(BluetoothGattWrapper gatt, BluetoothGattCharacteristic characteristic)515 public void onCharacteristicChanged(BluetoothGattWrapper gatt, 516 BluetoothGattCharacteristic characteristic) { 517 byte[] value = characteristic.getValue(); 518 if (value == null) { 519 // Value is not supposed to be null, but just to be safe... 520 value = new byte[0]; 521 } 522 Log.d(TAG, String.format("Characteristic %s changed, Gatt device: %s", 523 characteristic.getUuid(), gatt.getDevice())); 524 try { 525 getConnectionByGatt(gatt).onCharacteristicChanged(characteristic, value); 526 } catch (BluetoothException e) { 527 Log.e(TAG, "Error in onCharacteristicChanged", e); 528 } 529 } 530 } 531 532 private class InternalScanCallback extends ScanCallback { 533 534 @Override onScanFailed(int errorCode)535 public void onScanFailed(int errorCode) { 536 String errorMessage; 537 switch (errorCode) { 538 case ScanCallback.SCAN_FAILED_ALREADY_STARTED: 539 errorMessage = "SCAN_FAILED_ALREADY_STARTED"; 540 break; 541 case ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED: 542 errorMessage = "SCAN_FAILED_APPLICATION_REGISTRATION_FAILED"; 543 break; 544 case ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED: 545 errorMessage = "SCAN_FAILED_FEATURE_UNSUPPORTED"; 546 break; 547 case ScanCallback.SCAN_FAILED_INTERNAL_ERROR: 548 errorMessage = "SCAN_FAILED_INTERNAL_ERROR"; 549 break; 550 default: 551 errorMessage = "Unknown error code - " + errorCode; 552 } 553 mBluetoothOperationExecutor.notifyFailure( 554 new Operation<BluetoothDevice>(OperationType.SCAN), 555 new BluetoothException("Scan failed: " + errorMessage)); 556 } 557 558 @Override onScanResult(int callbackType, ScanResult result)559 public void onScanResult(int callbackType, ScanResult result) { 560 mBluetoothOperationExecutor.notifySuccess( 561 new Operation<BluetoothDevice>(OperationType.SCAN), result.getDevice()); 562 } 563 } 564 565 /** 566 * Options for {@link #connect}. 567 */ 568 public static class ConnectionOptions { 569 570 private boolean mAutoConnect; 571 private long mConnectionTimeoutMillis; 572 private Optional<Integer> mConnectionPriority; 573 private Optional<Integer> mMtu; 574 ConnectionOptions(boolean autoConnect, long connectionTimeoutMillis, Optional<Integer> connectionPriority, Optional<Integer> mtu)575 private ConnectionOptions(boolean autoConnect, long connectionTimeoutMillis, 576 Optional<Integer> connectionPriority, 577 Optional<Integer> mtu) { 578 this.mAutoConnect = autoConnect; 579 this.mConnectionTimeoutMillis = connectionTimeoutMillis; 580 this.mConnectionPriority = connectionPriority; 581 this.mMtu = mtu; 582 } 583 autoConnect()584 boolean autoConnect() { 585 return mAutoConnect; 586 } 587 connectionTimeoutMillis()588 long connectionTimeoutMillis() { 589 return mConnectionTimeoutMillis; 590 } 591 connectionPriority()592 Optional<Integer> connectionPriority() { 593 return mConnectionPriority; 594 } 595 mtu()596 Optional<Integer> mtu() { 597 return mMtu; 598 } 599 600 @Override toString()601 public String toString() { 602 return "ConnectionOptions{" 603 + "autoConnect=" + mAutoConnect + ", " 604 + "connectionTimeoutMillis=" + mConnectionTimeoutMillis + ", " 605 + "connectionPriority=" + mConnectionPriority + ", " 606 + "mtu=" + mMtu 607 + "}"; 608 } 609 610 @Override equals(Object o)611 public boolean equals(Object o) { 612 if (this == o) { 613 return true; 614 } 615 if (o instanceof ConnectionOptions) { 616 ConnectionOptions that = (ConnectionOptions) o; 617 return this.mAutoConnect == that.autoConnect() 618 && this.mConnectionTimeoutMillis == that.connectionTimeoutMillis() 619 && this.mConnectionPriority.equals(that.connectionPriority()) 620 && this.mMtu.equals(that.mtu()); 621 } 622 return false; 623 } 624 625 @Override hashCode()626 public int hashCode() { 627 return Objects.hash(mAutoConnect, mConnectionTimeoutMillis, mConnectionPriority, mMtu); 628 } 629 630 /** 631 * Creates a builder of ConnectionOptions. 632 */ builder()633 public static Builder builder() { 634 return new ConnectionOptions.Builder() 635 .setAutoConnect(false) 636 .setConnectionTimeoutMillis(TimeUnit.SECONDS.toMillis(5)); 637 } 638 639 /** 640 * Builder for {@link ConnectionOptions}. 641 */ 642 public static class Builder { 643 644 private boolean mAutoConnect; 645 private long mConnectionTimeoutMillis; 646 private Optional<Integer> mConnectionPriority = Optional.empty(); 647 private Optional<Integer> mMtu = Optional.empty(); 648 649 /** 650 * See {@link android.bluetooth.BluetoothDevice#connectGatt}. 651 */ setAutoConnect(boolean autoConnect)652 public Builder setAutoConnect(boolean autoConnect) { 653 this.mAutoConnect = autoConnect; 654 return this; 655 } 656 657 /** 658 * See {@link android.bluetooth.BluetoothGatt#requestConnectionPriority(int)}. 659 */ setConnectionPriority(int connectionPriority)660 public Builder setConnectionPriority(int connectionPriority) { 661 this.mConnectionPriority = Optional.of(connectionPriority); 662 return this; 663 } 664 665 /** 666 * See {@link android.bluetooth.BluetoothGatt#requestMtu(int)}. 667 */ setMtu(int mtu)668 public Builder setMtu(int mtu) { 669 this.mMtu = Optional.of(mtu); 670 return this; 671 } 672 673 /** 674 * Sets the timeout for the GATT connection. 675 */ setConnectionTimeoutMillis(long connectionTimeoutMillis)676 public Builder setConnectionTimeoutMillis(long connectionTimeoutMillis) { 677 this.mConnectionTimeoutMillis = connectionTimeoutMillis; 678 return this; 679 } 680 681 /** 682 * Builds ConnectionOptions. 683 */ build()684 public ConnectionOptions build() { 685 return new ConnectionOptions(mAutoConnect, mConnectionTimeoutMillis, 686 mConnectionPriority, mMtu); 687 } 688 } 689 } 690 } 691