1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * Copyright (C) 2016-2017 The Linux Foundation 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.bluetooth.btservice; 19 20 import static android.Manifest.permission.BLUETOOTH_CONNECT; 21 import static android.Manifest.permission.BLUETOOTH_SCAN; 22 import static android.bluetooth.BluetoothProfile.STATE_CONNECTED; 23 import static android.bluetooth.BluetoothProfile.STATE_CONNECTING; 24 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED; 25 import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTING; 26 27 import static com.android.bluetooth.Utils.BD_ADDR_LEN; 28 29 import android.annotation.NonNull; 30 import android.app.BroadcastOptions; 31 import android.bluetooth.BluetoothA2dp; 32 import android.bluetooth.BluetoothAdapter; 33 import android.bluetooth.BluetoothClass; 34 import android.bluetooth.BluetoothDevice; 35 import android.bluetooth.BluetoothManager; 36 import android.bluetooth.BluetoothMap; 37 import android.bluetooth.BluetoothProfile; 38 import android.bluetooth.BluetoothSap; 39 import android.bluetooth.BluetoothUtils; 40 import android.bluetooth.BufferConstraint; 41 import android.bluetooth.BufferConstraints; 42 import android.content.Context; 43 import android.content.Intent; 44 import android.os.Bundle; 45 import android.os.Handler; 46 import android.os.Looper; 47 import android.os.ParcelUuid; 48 import android.os.SystemProperties; 49 import android.os.UserHandle; 50 import android.util.Log; 51 import android.util.Pair; 52 53 import androidx.annotation.VisibleForTesting; 54 55 import com.android.bluetooth.BluetoothStatsLog; 56 import com.android.bluetooth.Utils; 57 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties; 58 import com.android.modules.utils.build.SdkLevel; 59 60 import java.io.FileDescriptor; 61 import java.io.PrintWriter; 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.HashMap; 65 import java.util.List; 66 import java.util.concurrent.CompletableFuture; 67 import java.util.concurrent.CopyOnWriteArrayList; 68 69 class AdapterProperties { 70 private static final String TAG = AdapterProperties.class.getSimpleName(); 71 72 private static final String MAX_CONNECTED_AUDIO_DEVICES_PROPERTY = 73 "persist.bluetooth.maxconnectedaudiodevices"; 74 private static final int MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOUND = 1; 75 private static final int MAX_CONNECTED_AUDIO_DEVICES_UPPER_BOUND = 5; 76 private static final String A2DP_OFFLOAD_SUPPORTED_PROPERTY = 77 "ro.bluetooth.a2dp_offload.supported"; 78 private static final String A2DP_OFFLOAD_DISABLED_PROPERTY = 79 "persist.bluetooth.a2dp_offload.disabled"; 80 81 private static final long DEFAULT_DISCOVERY_TIMEOUT_MS = 12800; 82 @VisibleForTesting static final int BLUETOOTH_NAME_MAX_LENGTH_BYTES = 248; 83 private static final int SYSTEM_CONNECTION_LATENCY_METRIC = 65536; 84 85 private volatile String mName; 86 private volatile byte[] mAddress; 87 private volatile BluetoothClass mBluetoothClass; 88 private volatile int mScanMode; 89 private volatile int mDiscoverableTimeout; 90 private volatile ParcelUuid[] mUuids; 91 92 private final CopyOnWriteArrayList<BluetoothDevice> mBondedDevices = 93 new CopyOnWriteArrayList<>(); 94 95 private int mProfilesConnecting, mProfilesConnected, mProfilesDisconnecting; 96 private final HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState = 97 new HashMap<>(); 98 99 private final CompletableFuture<List<BufferConstraint>> mBufferConstraintList = 100 new CompletableFuture<>(); 101 102 private volatile int mConnectionState = BluetoothAdapter.STATE_DISCONNECTED; 103 private volatile int mState = BluetoothAdapter.STATE_OFF; 104 private int mMaxConnectedAudioDevices = 1; 105 private boolean mA2dpOffloadEnabled = false; 106 107 private final AdapterService mService; 108 private final BluetoothAdapter mAdapter; 109 private final RemoteDevices mRemoteDevices; 110 private final Handler mHandler; 111 112 private boolean mDiscovering; 113 private long mDiscoveryEndMs; // < Time (ms since epoch) that discovery ended or will end. 114 // TODO - all hw capabilities to be exposed as a class 115 private int mNumOfAdvertisementInstancesSupported; 116 private boolean mRpaOffloadSupported; 117 private int mNumOfOffloadedIrkSupported; 118 private int mNumOfOffloadedScanFilterSupported; 119 private int mOffloadedScanResultStorageBytes; 120 private int mVersSupported; 121 private int mTotNumOfTrackableAdv; 122 private boolean mIsExtendedScanSupported; 123 private boolean mIsDebugLogSupported; 124 private boolean mIsActivityAndEnergyReporting; 125 private boolean mIsLe2MPhySupported; 126 private boolean mIsLeCodedPhySupported; 127 private boolean mIsLeExtendedAdvertisingSupported; 128 private boolean mIsLePeriodicAdvertisingSupported; 129 private int mLeMaximumAdvertisingDataLength; 130 private boolean mIsOffloadedTransportDiscoveryDataScanSupported; 131 132 private int mIsDynamicAudioBufferSizeSupported; 133 private int mDynamicAudioBufferSizeSupportedCodecsGroup1; 134 private int mDynamicAudioBufferSizeSupportedCodecsGroup2; 135 136 private boolean mIsLePeriodicAdvertisingSyncTransferSenderSupported; 137 private boolean mIsLePeriodicAdvertisingSyncTransferRecipientSupported; 138 private boolean mIsLeConnectedIsochronousStreamCentralSupported; 139 private boolean mIsLeIsochronousBroadcasterSupported; 140 private boolean mIsLeChannelSoundingSupported; 141 142 private int mNumberOfSupportedOffloadedLeCocSockets; 143 private int mNumberOfSupportedOffloadedRfcommSockets; 144 145 // Lock for all getters and setters. 146 // If finer grained locking is needer, more locks 147 // can be added here. 148 private final Object mObject = new Object(); 149 AdapterProperties(AdapterService service, RemoteDevices remoteDevices, Looper looper)150 AdapterProperties(AdapterService service, RemoteDevices remoteDevices, Looper looper) { 151 mAdapter = ((Context) service).getSystemService(BluetoothManager.class).getAdapter(); 152 mRemoteDevices = remoteDevices; 153 mService = service; 154 mHandler = new Handler(looper); 155 invalidateBluetoothCaches(); 156 } 157 init()158 public void init() { 159 mProfileConnectionState.clear(); 160 161 // Get default max connected audio devices from config.xml 162 int configDefaultMaxConnectedAudioDevices = 163 mService.getResources() 164 .getInteger( 165 com.android.bluetooth.R.integer 166 .config_bluetooth_max_connected_audio_devices); 167 // Override max connected audio devices if MAX_CONNECTED_AUDIO_DEVICES_PROPERTY is set 168 int propertyOverlayedMaxConnectedAudioDevices = 169 SystemProperties.getInt( 170 MAX_CONNECTED_AUDIO_DEVICES_PROPERTY, 171 configDefaultMaxConnectedAudioDevices); 172 // Make sure the final value of max connected audio devices is within allowed range 173 mMaxConnectedAudioDevices = 174 Math.min( 175 Math.max( 176 propertyOverlayedMaxConnectedAudioDevices, 177 MAX_CONNECTED_AUDIO_DEVICES_LOWER_BOUND), 178 MAX_CONNECTED_AUDIO_DEVICES_UPPER_BOUND); 179 Log.i( 180 TAG, 181 "init(), maxConnectedAudioDevices, default=" 182 + configDefaultMaxConnectedAudioDevices 183 + ", propertyOverlayed=" 184 + propertyOverlayedMaxConnectedAudioDevices 185 + ", finalValue=" 186 + mMaxConnectedAudioDevices); 187 188 mA2dpOffloadEnabled = 189 SystemProperties.getBoolean(A2DP_OFFLOAD_SUPPORTED_PROPERTY, false) 190 && !SystemProperties.getBoolean(A2DP_OFFLOAD_DISABLED_PROPERTY, false); 191 192 invalidateBluetoothCaches(); 193 } 194 cleanup()195 public void cleanup() { 196 mProfileConnectionState.clear(); 197 198 mBondedDevices.clear(); 199 invalidateBluetoothCaches(); 200 } 201 invalidateGetProfileConnectionStateCache()202 private static void invalidateGetProfileConnectionStateCache() { 203 BluetoothAdapter.invalidateGetProfileConnectionStateCache(); 204 } 205 invalidateIsOffloadedFilteringSupportedCache()206 private static void invalidateIsOffloadedFilteringSupportedCache() { 207 BluetoothAdapter.invalidateIsOffloadedFilteringSupportedCache(); 208 } 209 invalidateBluetoothGetConnectionStateCache()210 private static void invalidateBluetoothGetConnectionStateCache() { 211 BluetoothMap.invalidateBluetoothGetConnectionStateCache(); 212 BluetoothSap.invalidateBluetoothGetConnectionStateCache(); 213 } 214 invalidateGetConnectionStateCache()215 private static void invalidateGetConnectionStateCache() { 216 BluetoothAdapter.invalidateGetAdapterConnectionStateCache(); 217 } 218 invalidateGetBondStateCache()219 private static void invalidateGetBondStateCache() { 220 BluetoothDevice.invalidateBluetoothGetBondStateCache(); 221 } 222 invalidateBluetoothCaches()223 private static void invalidateBluetoothCaches() { 224 invalidateGetProfileConnectionStateCache(); 225 invalidateIsOffloadedFilteringSupportedCache(); 226 invalidateGetConnectionStateCache(); 227 invalidateGetBondStateCache(); 228 invalidateBluetoothGetConnectionStateCache(); 229 } 230 231 @Override clone()232 public Object clone() throws CloneNotSupportedException { 233 throw new CloneNotSupportedException(); 234 } 235 236 /** 237 * @return the mName 238 */ getName()239 String getName() { 240 return mName; 241 } 242 243 /** 244 * Set the local adapter property - name 245 * 246 * @param name the name to set 247 */ setName(String name)248 boolean setName(String name) { 249 synchronized (mObject) { 250 return mService.getNative() 251 .setAdapterProperty( 252 AbstractionLayer.BT_PROPERTY_BDNAME, 253 Utils.truncateStringForUtf8Storage( 254 name, BLUETOOTH_NAME_MAX_LENGTH_BYTES) 255 .getBytes()); 256 } 257 } 258 259 /** 260 * @return the mUuids 261 */ getUuids()262 ParcelUuid[] getUuids() { 263 return mUuids; 264 } 265 266 /** 267 * @return the mAddress 268 */ getAddress()269 byte[] getAddress() { 270 return mAddress; 271 } 272 273 /** 274 * @param connectionState the mConnectionState to set 275 */ setConnectionState(int connectionState)276 void setConnectionState(int connectionState) { 277 mConnectionState = connectionState; 278 invalidateGetConnectionStateCache(); 279 } 280 281 /** 282 * @return the mConnectionState 283 */ getConnectionState()284 int getConnectionState() { 285 return mConnectionState; 286 } 287 288 /** 289 * @param state the mState to set 290 */ setState(int state)291 void setState(int state) { 292 debugLog("Setting state to " + BluetoothAdapter.nameForState(state)); 293 mState = state; 294 } 295 296 /** 297 * @return the mState 298 */ getState()299 int getState() { 300 return mState; 301 } 302 303 /** 304 * @return the mNumOfAdvertisementInstancesSupported 305 */ getNumOfAdvertisementInstancesSupported()306 int getNumOfAdvertisementInstancesSupported() { 307 return mNumOfAdvertisementInstancesSupported; 308 } 309 310 /** 311 * @return the mRpaOffloadSupported 312 */ isRpaOffloadSupported()313 boolean isRpaOffloadSupported() { 314 return mRpaOffloadSupported; 315 } 316 317 /** 318 * @return the mNumOfOffloadedIrkSupported 319 */ getNumOfOffloadedIrkSupported()320 int getNumOfOffloadedIrkSupported() { 321 return mNumOfOffloadedIrkSupported; 322 } 323 324 /** 325 * @return the mNumOfOffloadedScanFilterSupported 326 */ getNumOfOffloadedScanFilterSupported()327 int getNumOfOffloadedScanFilterSupported() { 328 return mNumOfOffloadedScanFilterSupported; 329 } 330 331 /** 332 * @return the mOffloadedScanResultStorageBytes 333 */ getOffloadedScanResultStorage()334 int getOffloadedScanResultStorage() { 335 return mOffloadedScanResultStorageBytes; 336 } 337 338 /** 339 * @return tx/rx/idle activity and energy info 340 */ isActivityAndEnergyReportingSupported()341 boolean isActivityAndEnergyReportingSupported() { 342 return mIsActivityAndEnergyReporting; 343 } 344 345 /** 346 * @return the mIsLe2MPhySupported 347 */ isLe2MPhySupported()348 boolean isLe2MPhySupported() { 349 return mIsLe2MPhySupported; 350 } 351 352 /** 353 * @return the mIsLeCodedPhySupported 354 */ isLeCodedPhySupported()355 boolean isLeCodedPhySupported() { 356 return mIsLeCodedPhySupported; 357 } 358 359 /** 360 * @return the mIsLeExtendedAdvertisingSupported 361 */ isLeExtendedAdvertisingSupported()362 boolean isLeExtendedAdvertisingSupported() { 363 return mIsLeExtendedAdvertisingSupported; 364 } 365 366 /** 367 * @return the mIsLePeriodicAdvertisingSupported 368 */ isLePeriodicAdvertisingSupported()369 boolean isLePeriodicAdvertisingSupported() { 370 return mIsLePeriodicAdvertisingSupported; 371 } 372 373 /** 374 * @return the mIsLePeriodicAdvertisingSyncTransferSenderSupported 375 */ isLePeriodicAdvertisingSyncTransferSenderSupported()376 boolean isLePeriodicAdvertisingSyncTransferSenderSupported() { 377 return mIsLePeriodicAdvertisingSyncTransferSenderSupported; 378 } 379 380 /** 381 * @return the mIsLePeriodicAdvertisingSyncTransferRecipientSupported 382 */ isLePeriodicAdvertisingSyncTransferRecipientSupported()383 boolean isLePeriodicAdvertisingSyncTransferRecipientSupported() { 384 return mIsLePeriodicAdvertisingSyncTransferRecipientSupported; 385 } 386 387 /** 388 * @return the mIsLeConnectedIsochronousStreamCentralSupported 389 */ isLeConnectedIsochronousStreamCentralSupported()390 boolean isLeConnectedIsochronousStreamCentralSupported() { 391 return mIsLeConnectedIsochronousStreamCentralSupported; 392 } 393 394 /** 395 * @return the mIsLeIsochronousBroadcasterSupported 396 */ isLeIsochronousBroadcasterSupported()397 boolean isLeIsochronousBroadcasterSupported() { 398 return mIsLeIsochronousBroadcasterSupported; 399 } 400 401 /** 402 * @return the mIsLeChannelSoundingSupported 403 */ isLeChannelSoundingSupported()404 boolean isLeChannelSoundingSupported() { 405 return mIsLeChannelSoundingSupported; 406 } 407 408 /** 409 * @return the getLeMaximumAdvertisingDataLength 410 */ getLeMaximumAdvertisingDataLength()411 int getLeMaximumAdvertisingDataLength() { 412 return mLeMaximumAdvertisingDataLength; 413 } 414 415 /** 416 * @return total number of trackable advertisements 417 */ getTotalNumOfTrackableAdvertisements()418 int getTotalNumOfTrackableAdvertisements() { 419 return mTotNumOfTrackableAdv; 420 } 421 422 /** 423 * @return the isOffloadedTransportDiscoveryDataScanSupported 424 */ isOffloadedTransportDiscoveryDataScanSupported()425 public boolean isOffloadedTransportDiscoveryDataScanSupported() { 426 return mIsOffloadedTransportDiscoveryDataScanSupported; 427 } 428 429 /** 430 * @return the maximum number of connected audio devices 431 */ getMaxConnectedAudioDevices()432 int getMaxConnectedAudioDevices() { 433 return mMaxConnectedAudioDevices; 434 } 435 436 /** 437 * @return A2DP offload support 438 */ isA2dpOffloadEnabled()439 boolean isA2dpOffloadEnabled() { 440 return mA2dpOffloadEnabled; 441 } 442 443 /** 444 * @return Dynamic Audio Buffer support 445 */ getDynamicBufferSupport()446 int getDynamicBufferSupport() { 447 if (!mA2dpOffloadEnabled) { 448 // TODO: Enable Dynamic Audio Buffer for A2DP software encoding when ready. 449 mIsDynamicAudioBufferSizeSupported = BluetoothA2dp.DYNAMIC_BUFFER_SUPPORT_NONE; 450 } else { 451 if ((mDynamicAudioBufferSizeSupportedCodecsGroup1 != 0) 452 || (mDynamicAudioBufferSizeSupportedCodecsGroup2 != 0)) { 453 mIsDynamicAudioBufferSizeSupported = 454 BluetoothA2dp.DYNAMIC_BUFFER_SUPPORT_A2DP_OFFLOAD; 455 } else { 456 mIsDynamicAudioBufferSizeSupported = BluetoothA2dp.DYNAMIC_BUFFER_SUPPORT_NONE; 457 } 458 } 459 return mIsDynamicAudioBufferSizeSupported; 460 } 461 462 /** 463 * @return Dynamic Audio Buffer Capability 464 */ getBufferConstraints()465 BufferConstraints getBufferConstraints() { 466 return new BufferConstraints(mBufferConstraintList.join()); 467 } 468 469 /** 470 * Set the dynamic audio buffer size 471 * 472 * @param codec the codecs to set 473 * @param size the size to set 474 */ setBufferLengthMillis(int codec, int size)475 boolean setBufferLengthMillis(int codec, int size) { 476 return mService.getNative().setBufferLengthMillis(codec, size); 477 } 478 479 /** 480 * @return the mBondedDevices 481 */ getBondedDevices()482 BluetoothDevice[] getBondedDevices() { 483 BluetoothDevice[] bondedDeviceList = new BluetoothDevice[0]; 484 try { 485 bondedDeviceList = mBondedDevices.toArray(bondedDeviceList); 486 } catch (ArrayStoreException ee) { 487 Log.e(TAG, "Error retrieving bonded device array"); 488 } 489 infoLog("getBondedDevices: length=" + bondedDeviceList.length); 490 return bondedDeviceList; 491 } 492 493 // This function shall be invoked from BondStateMachine whenever the bond 494 // state changes. 495 @VisibleForTesting onBondStateChanged(BluetoothDevice device, int state)496 void onBondStateChanged(BluetoothDevice device, int state) { 497 if (device == null) { 498 Log.w(TAG, "onBondStateChanged, device is null"); 499 return; 500 } 501 try { 502 byte[] addrByte = Utils.getByteAddress(device); 503 DeviceProperties prop = mRemoteDevices.getDeviceProperties(device); 504 if (prop == null) { 505 prop = mRemoteDevices.addDeviceProperties(addrByte); 506 } 507 device = prop.getDevice(); 508 prop.setBondState(state); 509 510 if (state == BluetoothDevice.BOND_BONDED) { 511 // add if not already in list 512 if (!mBondedDevices.contains(device)) { 513 debugLog("Adding bonded device:" + device); 514 mBondedDevices.add(device); 515 cleanupPrevBondRecordsFor(device); 516 } 517 } else if (state == BluetoothDevice.BOND_NONE) { 518 // remove device from list 519 if (mBondedDevices.remove(device)) { 520 debugLog("Removing bonded device:" + device); 521 } else { 522 debugLog("Failed to remove device: " + device); 523 } 524 } 525 invalidateGetBondStateCache(); 526 } catch (Exception ee) { 527 Log.w(TAG, "onBondStateChanged: Exception ", ee); 528 } 529 } 530 cleanupPrevBondRecordsFor(BluetoothDevice device)531 void cleanupPrevBondRecordsFor(BluetoothDevice device) { 532 String address = device.getAddress(); 533 String identityAddress = Utils.getBrEdrAddress(device, mService); 534 int deviceType = mRemoteDevices.getDeviceProperties(device).getDeviceType(); 535 debugLog("cleanupPrevBondRecordsFor: " + device + ", device type: " + deviceType); 536 if (identityAddress == null) { 537 return; 538 } 539 540 if (deviceType != BluetoothDevice.DEVICE_TYPE_LE) { 541 return; 542 } 543 544 for (BluetoothDevice existingDevice : mBondedDevices) { 545 String existingAddress = existingDevice.getAddress(); 546 String existingIdentityAddress = Utils.getBrEdrAddress(existingDevice, mService); 547 int existingDeviceType = 548 mRemoteDevices.getDeviceProperties(existingDevice).getDeviceType(); 549 550 boolean removeExisting = false; 551 if (identityAddress.equals(existingIdentityAddress) 552 && !address.equals(existingAddress)) { 553 // Existing device record should be removed only if the device type is LE-only 554 removeExisting = (existingDeviceType == BluetoothDevice.DEVICE_TYPE_LE); 555 } 556 557 if (removeExisting) { 558 // Found an existing LE-only device with the same identity address but different 559 // pseudo address 560 if (mService.getNative().removeBond(Utils.getBytesFromAddress(existingAddress))) { 561 mBondedDevices.remove(existingDevice); 562 infoLog( 563 "Removing old bond record: " 564 + existingDevice 565 + " for the device: " 566 + device); 567 } else { 568 Log.e( 569 TAG, 570 "Unexpected error while removing old bond record:" 571 + existingDevice 572 + " for the device: " 573 + device); 574 } 575 break; 576 } 577 } 578 } 579 getDiscoverableTimeout()580 int getDiscoverableTimeout() { 581 return mDiscoverableTimeout; 582 } 583 setDiscoverableTimeout(int timeout)584 boolean setDiscoverableTimeout(int timeout) { 585 synchronized (mObject) { 586 return mService.getNative() 587 .setAdapterProperty( 588 AbstractionLayer.BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT, 589 Utils.intToByteArray(timeout)); 590 } 591 } 592 getProfileConnectionState(int profile)593 int getProfileConnectionState(int profile) { 594 synchronized (mObject) { 595 Pair<Integer, Integer> p = mProfileConnectionState.get(profile); 596 if (p != null) { 597 return p.first; 598 } 599 return STATE_DISCONNECTED; 600 } 601 } 602 discoveryEndMillis()603 long discoveryEndMillis() { 604 return mDiscoveryEndMs; 605 } 606 isDiscovering()607 boolean isDiscovering() { 608 return mDiscovering; 609 } 610 611 updateOnProfileConnectionChanged( BluetoothDevice device, int profile, int newState, int prevState)612 void updateOnProfileConnectionChanged( 613 BluetoothDevice device, int profile, int newState, int prevState) { 614 String logInfo = 615 ("profile=" + BluetoothProfile.getProfileName(profile)) 616 + (" device=" + device) 617 + (" state [" + prevState + " -> " + newState + "]"); 618 Log.d(TAG, "updateOnProfileConnectionChanged: " + logInfo); 619 if (!isNormalStateTransition(prevState, newState)) { 620 Log.w(TAG, "updateOnProfileConnectionChanged: Unexpected transition. " + logInfo); 621 } 622 BluetoothStatsLog.write( 623 BluetoothStatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, 624 newState, 625 0 /* deprecated */, 626 profile, 627 mService.obfuscateAddress(device), 628 mService.getMetricId(device), 629 0, 630 SYSTEM_CONNECTION_LATENCY_METRIC); 631 if (!validateProfileConnectionState(newState) 632 || !validateProfileConnectionState(prevState)) { 633 // Previously, an invalid state was broadcast anyway, 634 // with the invalid state converted to -1 in the intent. 635 // Better to log an error and not send an intent with 636 // invalid contents or set mAdapterConnectionState to -1. 637 Log.e(TAG, "updateOnProfileConnectionChanged: Invalid transition. " + logInfo); 638 return; 639 } 640 641 synchronized (mObject) { 642 updateProfileConnectionState(profile, newState, prevState); 643 644 if (updateCountersAndCheckForConnectionStateChange(newState, prevState)) { 645 int newAdapterState = convertToAdapterState(newState); 646 int prevAdapterState = convertToAdapterState(prevState); 647 setConnectionState(newAdapterState); 648 649 Intent intent = 650 new Intent(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED) 651 .putExtra(BluetoothDevice.EXTRA_DEVICE, device) 652 .putExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, newAdapterState) 653 .putExtra( 654 BluetoothAdapter.EXTRA_PREVIOUS_CONNECTION_STATE, 655 prevAdapterState) 656 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 657 MetricsLogger.getInstance() 658 .logProfileConnectionStateChange(device, profile, newState, prevState); 659 Log.d(TAG, "updateOnProfileConnectionChanged: " + logInfo); 660 mService.sendBroadcastAsUser( 661 intent, 662 UserHandle.ALL, 663 BLUETOOTH_CONNECT, 664 Utils.getTempBroadcastOptions().toBundle()); 665 } 666 } 667 } 668 validateProfileConnectionState(int state)669 private static boolean validateProfileConnectionState(int state) { 670 return (state == STATE_DISCONNECTED 671 || state == STATE_CONNECTING 672 || state == STATE_CONNECTED 673 || state == STATE_DISCONNECTING); 674 } 675 convertToAdapterState(int state)676 private static int convertToAdapterState(int state) { 677 switch (state) { 678 case STATE_DISCONNECTED: 679 return BluetoothAdapter.STATE_DISCONNECTED; 680 case STATE_DISCONNECTING: 681 return BluetoothAdapter.STATE_DISCONNECTING; 682 case STATE_CONNECTED: 683 return BluetoothAdapter.STATE_CONNECTED; 684 case STATE_CONNECTING: 685 return BluetoothAdapter.STATE_CONNECTING; 686 } 687 Log.e(TAG, "convertToAdapterState, unknown state " + state); 688 return -1; 689 } 690 isNormalStateTransition(int prevState, int nextState)691 private static boolean isNormalStateTransition(int prevState, int nextState) { 692 switch (prevState) { 693 case STATE_DISCONNECTED: 694 return nextState == STATE_CONNECTING; 695 case STATE_CONNECTED: 696 return nextState == STATE_DISCONNECTING; 697 case STATE_DISCONNECTING: 698 case STATE_CONNECTING: 699 return (nextState == STATE_DISCONNECTED) || (nextState == STATE_CONNECTED); 700 default: 701 return false; 702 } 703 } 704 updateCountersAndCheckForConnectionStateChange(int state, int prevState)705 private boolean updateCountersAndCheckForConnectionStateChange(int state, int prevState) { 706 switch (prevState) { 707 case STATE_CONNECTING: 708 if (mProfilesConnecting > 0) { 709 mProfilesConnecting--; 710 } else { 711 Log.e(TAG, "mProfilesConnecting " + mProfilesConnecting); 712 throw new IllegalStateException( 713 "Invalid state transition, " + prevState + " -> " + state); 714 } 715 break; 716 717 case STATE_CONNECTED: 718 if (mProfilesConnected > 0) { 719 mProfilesConnected--; 720 } else { 721 Log.e(TAG, "mProfilesConnected " + mProfilesConnected); 722 throw new IllegalStateException( 723 "Invalid state transition, " + prevState + " -> " + state); 724 } 725 break; 726 727 case STATE_DISCONNECTING: 728 if (mProfilesDisconnecting > 0) { 729 mProfilesDisconnecting--; 730 } else { 731 Log.e(TAG, "mProfilesDisconnecting " + mProfilesDisconnecting); 732 throw new IllegalStateException( 733 "Invalid state transition, " + prevState + " -> " + state); 734 } 735 break; 736 } 737 738 switch (state) { 739 case STATE_CONNECTING: 740 mProfilesConnecting++; 741 return (mProfilesConnected == 0 && mProfilesConnecting == 1); 742 743 case STATE_CONNECTED: 744 mProfilesConnected++; 745 return (mProfilesConnected == 1); 746 747 case STATE_DISCONNECTING: 748 mProfilesDisconnecting++; 749 return (mProfilesConnected == 0 && mProfilesDisconnecting == 1); 750 751 case STATE_DISCONNECTED: 752 return (mProfilesConnected == 0 && mProfilesConnecting == 0); 753 754 default: 755 return true; 756 } 757 } 758 updateProfileConnectionState(int profile, int newState, int oldState)759 private void updateProfileConnectionState(int profile, int newState, int oldState) { 760 // mProfileConnectionState is a hashmap - 761 // <Integer, Pair<Integer, Integer>> 762 // The key is the profile, the value is a pair. first element 763 // is the state and the second element is the number of devices 764 // in that state. 765 int numDev = 1; 766 int newHashState = newState; 767 boolean update = true; 768 769 // The following conditions are considered in this function: 770 // 1. If there is no record of profile and state - update 771 // 2. If a new device's state is current hash state - increment 772 // number of devices in the state. 773 // 3. If a state change has happened to Connected or Connecting 774 // (if current state is not connected), update. 775 // 4. If numDevices is 1 and that device state is being updated, update 776 // 5. If numDevices is > 1 and one of the devices is changing state, 777 // decrement numDevices but maintain oldState if it is Connected or 778 // Connecting 779 Pair<Integer, Integer> stateNumDev = mProfileConnectionState.get(profile); 780 if (stateNumDev != null) { 781 int currHashState = stateNumDev.first; 782 numDev = stateNumDev.second; 783 784 if (newState == currHashState) { 785 numDev++; 786 } else if (newState == STATE_CONNECTED 787 || (newState == STATE_CONNECTING && currHashState != STATE_CONNECTED)) { 788 numDev = 1; 789 } else if (numDev == 1 && oldState == currHashState) { 790 update = true; 791 } else if (numDev > 1 && oldState == currHashState) { 792 numDev--; 793 794 if (currHashState == STATE_CONNECTED || currHashState == STATE_CONNECTING) { 795 newHashState = currHashState; 796 } 797 } else { 798 update = false; 799 } 800 } 801 802 if (update) { 803 mProfileConnectionState.put(profile, new Pair<Integer, Integer>(newHashState, numDev)); 804 invalidateGetProfileConnectionStateCache(); 805 } 806 } 807 adapterPropertyChangedCallback(int[] types, byte[][] values)808 void adapterPropertyChangedCallback(int[] types, byte[][] values) { 809 mHandler.post(() -> adapterPropertyChangedCallbackInternal(types, values)); 810 } 811 adapterPropertyChangedCallbackInternal(int[] types, byte[][] values)812 private void adapterPropertyChangedCallbackInternal(int[] types, byte[][] values) { 813 int type; 814 byte[] val; 815 for (int i = 0; i < types.length; i++) { 816 val = values[i]; 817 type = types[i]; 818 infoLog("adapterPropertyChangedCallback with type:" + type + " len:" + val.length); 819 synchronized (mObject) { 820 switch (type) { 821 case AbstractionLayer.BT_PROPERTY_BDNAME: 822 String name = new String(val); 823 if (name.equals(mName)) { 824 debugLog("Name already set: " + mName); 825 break; 826 } 827 mName = name; 828 mService.updateAdapterName(mName); 829 break; 830 case AbstractionLayer.BT_PROPERTY_BDADDR: 831 if (Arrays.equals(mAddress, val)) { 832 debugLog("Address already set"); 833 break; 834 } 835 mAddress = val; 836 String address = Utils.getAddressStringFromByte(mAddress); 837 mService.updateAdapterAddress(address); 838 break; 839 case AbstractionLayer.BT_PROPERTY_CLASS_OF_DEVICE: 840 if (val == null || val.length != 3) { 841 debugLog("Invalid BT CoD value from stack."); 842 return; 843 } 844 int bluetoothClass = 845 ((int) val[0] << 16) + ((int) val[1] << 8) + (int) val[2]; 846 if (bluetoothClass != 0) { 847 mBluetoothClass = new BluetoothClass(bluetoothClass); 848 } 849 debugLog("BT Class:" + mBluetoothClass); 850 break; 851 case AbstractionLayer.BT_PROPERTY_UUIDS: 852 mUuids = Utils.byteArrayToUuid(val); 853 break; 854 case AbstractionLayer.BT_PROPERTY_ADAPTER_BONDED_DEVICES: 855 int number = val.length / BD_ADDR_LEN; 856 byte[] addrByte = new byte[BD_ADDR_LEN]; 857 for (int j = 0; j < number; j++) { 858 System.arraycopy(val, j * BD_ADDR_LEN, addrByte, 0, BD_ADDR_LEN); 859 onBondStateChanged( 860 mAdapter.getRemoteDevice( 861 Utils.getAddressStringFromByte(addrByte)), 862 BluetoothDevice.BOND_BONDED); 863 } 864 break; 865 case AbstractionLayer.BT_PROPERTY_ADAPTER_DISCOVERABLE_TIMEOUT: 866 mDiscoverableTimeout = Utils.byteArrayToInt(val, 0); 867 debugLog("Discoverable Timeout:" + mDiscoverableTimeout); 868 break; 869 870 case AbstractionLayer.BT_PROPERTY_LOCAL_LE_FEATURES: 871 updateFeatureSupport(val); 872 mService.updateLeAudioProfileServiceState(); 873 break; 874 875 case AbstractionLayer.BT_PROPERTY_DYNAMIC_AUDIO_BUFFER: 876 updateDynamicAudioBufferSupport(val); 877 break; 878 879 case AbstractionLayer.BT_PROPERTY_LPP_OFFLOAD_FEATURES: 880 updateLppOffloadFeatureSupport(val); 881 break; 882 883 default: 884 Log.e(TAG, "Property change not handled in Java land:" + type); 885 } 886 } 887 } 888 } 889 updateFeatureSupport(byte[] val)890 private void updateFeatureSupport(byte[] val) { 891 mVersSupported = ((0xFF & ((int) val[1])) << 8) + (0xFF & ((int) val[0])); 892 mNumOfAdvertisementInstancesSupported = (0xFF & ((int) val[3])); 893 mRpaOffloadSupported = ((0xFF & ((int) val[4])) != 0); 894 mNumOfOffloadedIrkSupported = (0xFF & ((int) val[5])); 895 mNumOfOffloadedScanFilterSupported = (0xFF & ((int) val[6])); 896 mIsActivityAndEnergyReporting = ((0xFF & ((int) val[7])) != 0); 897 mOffloadedScanResultStorageBytes = ((0xFF & ((int) val[9])) << 8) + (0xFF & ((int) val[8])); 898 mTotNumOfTrackableAdv = ((0xFF & ((int) val[11])) << 8) + (0xFF & ((int) val[10])); 899 mIsExtendedScanSupported = ((0xFF & ((int) val[12])) != 0); 900 mIsDebugLogSupported = ((0xFF & ((int) val[13])) != 0); 901 mIsLe2MPhySupported = ((0xFF & ((int) val[14])) != 0); 902 mIsLeCodedPhySupported = ((0xFF & ((int) val[15])) != 0); 903 mIsLeExtendedAdvertisingSupported = ((0xFF & ((int) val[16])) != 0); 904 mIsLePeriodicAdvertisingSupported = ((0xFF & ((int) val[17])) != 0); 905 mLeMaximumAdvertisingDataLength = 906 (0xFF & ((int) val[18])) + ((0xFF & ((int) val[19])) << 8); 907 mDynamicAudioBufferSizeSupportedCodecsGroup1 = 908 ((0xFF & ((int) val[21])) << 8) + (0xFF & ((int) val[20])); 909 mDynamicAudioBufferSizeSupportedCodecsGroup2 = 910 ((0xFF & ((int) val[23])) << 8) + (0xFF & ((int) val[22])); 911 mIsLePeriodicAdvertisingSyncTransferSenderSupported = ((0xFF & ((int) val[24])) != 0); 912 mIsLeConnectedIsochronousStreamCentralSupported = ((0xFF & ((int) val[25])) != 0); 913 mIsLeIsochronousBroadcasterSupported = ((0xFF & ((int) val[26])) != 0); 914 mIsLePeriodicAdvertisingSyncTransferRecipientSupported = ((0xFF & ((int) val[27])) != 0); 915 mIsOffloadedTransportDiscoveryDataScanSupported = ((0x01 & ((int) val[28])) != 0); 916 mIsLeChannelSoundingSupported = ((0xFF & ((int) val[30])) != 0); 917 918 Log.d( 919 TAG, 920 "BT_PROPERTY_LOCAL_LE_FEATURES: update from BT controller" 921 + " mNumOfAdvertisementInstancesSupported = " 922 + mNumOfAdvertisementInstancesSupported 923 + " mRpaOffloadSupported = " 924 + mRpaOffloadSupported 925 + " mNumOfOffloadedIrkSupported = " 926 + mNumOfOffloadedIrkSupported 927 + " mNumOfOffloadedScanFilterSupported = " 928 + mNumOfOffloadedScanFilterSupported 929 + " mOffloadedScanResultStorageBytes= " 930 + mOffloadedScanResultStorageBytes 931 + " mIsActivityAndEnergyReporting = " 932 + mIsActivityAndEnergyReporting 933 + " mVersSupported = " 934 + mVersSupported 935 + " mTotNumOfTrackableAdv = " 936 + mTotNumOfTrackableAdv 937 + " mIsExtendedScanSupported = " 938 + mIsExtendedScanSupported 939 + " mIsDebugLogSupported = " 940 + mIsDebugLogSupported 941 + " mIsLe2MPhySupported = " 942 + mIsLe2MPhySupported 943 + " mIsLeCodedPhySupported = " 944 + mIsLeCodedPhySupported 945 + " mIsLeExtendedAdvertisingSupported = " 946 + mIsLeExtendedAdvertisingSupported 947 + " mIsLePeriodicAdvertisingSupported = " 948 + mIsLePeriodicAdvertisingSupported 949 + " mLeMaximumAdvertisingDataLength = " 950 + mLeMaximumAdvertisingDataLength 951 + " mDynamicAudioBufferSizeSupportedCodecsGroup1 = " 952 + mDynamicAudioBufferSizeSupportedCodecsGroup1 953 + " mDynamicAudioBufferSizeSupportedCodecsGroup2 = " 954 + mDynamicAudioBufferSizeSupportedCodecsGroup2 955 + " mIsLePeriodicAdvertisingSyncTransferSenderSupported = " 956 + mIsLePeriodicAdvertisingSyncTransferSenderSupported 957 + " mIsLeConnectedIsochronousStreamCentralSupported = " 958 + mIsLeConnectedIsochronousStreamCentralSupported 959 + " mIsLeIsochronousBroadcasterSupported = " 960 + mIsLeIsochronousBroadcasterSupported 961 + " mIsLePeriodicAdvertisingSyncTransferRecipientSupported = " 962 + mIsLePeriodicAdvertisingSyncTransferRecipientSupported 963 + " mIsOffloadedTransportDiscoveryDataScanSupported = " 964 + mIsOffloadedTransportDiscoveryDataScanSupported 965 + " mIsLeChannelSoundingSupported = " 966 + mIsLeChannelSoundingSupported); 967 invalidateIsOffloadedFilteringSupportedCache(); 968 } 969 updateDynamicAudioBufferSupport(byte[] val)970 private void updateDynamicAudioBufferSupport(byte[] val) { 971 if (mBufferConstraintList.isDone()) { 972 return; 973 } 974 975 // bufferConstraints is the table indicates the capability of all the codecs 976 // with buffer time. The raw is codec number, and the column is buffer type. There are 3 977 // buffer types - default/maximum/minimum. 978 // The maximum number of raw is BUFFER_CODEC_MAX_NUM(32). 979 // The maximum number of column is BUFFER_TYPE_MAX(3). 980 // The array element indicates the buffer time, the size is two octet. 981 List<BufferConstraint> bufferConstraintList = new ArrayList<BufferConstraint>(); 982 983 for (int i = 0; i < BufferConstraints.BUFFER_CODEC_MAX_NUM; i++) { 984 int defaultBufferTime = 985 ((0xFF & ((int) val[i * 6 + 1])) << 8) + (0xFF & ((int) val[i * 6])); 986 int maximumBufferTime = 987 ((0xFF & ((int) val[i * 6 + 3])) << 8) + (0xFF & ((int) val[i * 6 + 2])); 988 int minimumBufferTime = 989 ((0xFF & ((int) val[i * 6 + 5])) << 8) + (0xFF & ((int) val[i * 6 + 4])); 990 bufferConstraintList.add( 991 new BufferConstraint(defaultBufferTime, maximumBufferTime, minimumBufferTime)); 992 } 993 994 mBufferConstraintList.complete(bufferConstraintList); 995 } 996 997 /** 998 * @return the mNumberOfSupportedOffloadedLeCocSockets 999 */ getNumberOfSupportedOffloadedLeCocSockets()1000 int getNumberOfSupportedOffloadedLeCocSockets() { 1001 return mNumberOfSupportedOffloadedLeCocSockets; 1002 } 1003 1004 /** 1005 * @return the mNumberOfSupportedOffloadedRfcommSockets 1006 */ getNumberOfSupportedOffloadedRfcommSockets()1007 int getNumberOfSupportedOffloadedRfcommSockets() { 1008 return mNumberOfSupportedOffloadedRfcommSockets; 1009 } 1010 updateLppOffloadFeatureSupport(byte[] val)1011 private void updateLppOffloadFeatureSupport(byte[] val) { 1012 if (val == null || val.length < 2) { 1013 Log.e(TAG, "BT_PROPERTY_LPP_OFFLOAD_FEATURES: invalid value length"); 1014 return; 1015 } 1016 mNumberOfSupportedOffloadedLeCocSockets = (0xFF & ((int) val[0])); 1017 mNumberOfSupportedOffloadedRfcommSockets = (0xFF & ((int) val[1])); 1018 1019 Log.d( 1020 TAG, 1021 "BT_PROPERTY_LPP_OFFLOAD_FEATURES: update from Offload HAL" 1022 + " mNumberOfSupportedOffloadedLeCocSockets = " 1023 + mNumberOfSupportedOffloadedLeCocSockets 1024 + " mNumberOfSupportedOffloadedRfcommSockets = " 1025 + mNumberOfSupportedOffloadedRfcommSockets); 1026 } 1027 onBluetoothReady()1028 void onBluetoothReady() { 1029 debugLog( 1030 "onBluetoothReady, state=" 1031 + BluetoothAdapter.nameForState(getState()) 1032 + ", ScanMode=" 1033 + mScanMode); 1034 1035 synchronized (mObject) { 1036 // Reset adapter and profile connection states 1037 setConnectionState(BluetoothAdapter.STATE_DISCONNECTED); 1038 mProfileConnectionState.clear(); 1039 invalidateGetProfileConnectionStateCache(); 1040 mProfilesConnected = 0; 1041 mProfilesConnecting = 0; 1042 mProfilesDisconnecting = 0; 1043 // This keeps NV up-to date on first-boot after flash. 1044 setDiscoverableTimeout(mDiscoverableTimeout); 1045 } 1046 } 1047 discoveryStateChangeCallback(int state)1048 void discoveryStateChangeCallback(int state) { 1049 infoLog("Callback:discoveryStateChangeCallback with state:" + state); 1050 synchronized (mObject) { 1051 Intent intent; 1052 if (state == AbstractionLayer.BT_DISCOVERY_STOPPED) { 1053 mDiscovering = false; 1054 mService.clearDiscoveringPackages(); 1055 mDiscoveryEndMs = System.currentTimeMillis(); 1056 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 1057 mService.sendBroadcast( 1058 intent, BLUETOOTH_SCAN, getBroadcastOptionsForDiscoveryFinished()); 1059 } else if (state == AbstractionLayer.BT_DISCOVERY_STARTED) { 1060 mDiscovering = true; 1061 mDiscoveryEndMs = System.currentTimeMillis() + DEFAULT_DISCOVERY_TIMEOUT_MS; 1062 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED); 1063 mService.sendBroadcast( 1064 intent, BLUETOOTH_SCAN, Utils.getTempBroadcastOptions().toBundle()); 1065 } 1066 } 1067 } 1068 1069 /** 1070 * @return broadcast options for ACTION_DISCOVERY_FINISHED broadcast 1071 */ getBroadcastOptionsForDiscoveryFinished()1072 private static @NonNull Bundle getBroadcastOptionsForDiscoveryFinished() { 1073 final BroadcastOptions options = Utils.getTempBroadcastOptions(); 1074 if (SdkLevel.isAtLeastU()) { 1075 options.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); 1076 options.setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); 1077 } 1078 return options.toBundle(); 1079 } 1080 dump(FileDescriptor fd, PrintWriter writer, String[] args)1081 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 1082 writer.println(TAG); 1083 writer.println(" " + "Name: " + getName()); 1084 writer.println(" " + "Address: " + Utils.getRedactedAddressStringFromByte(mAddress)); 1085 writer.println(" " + "ConnectionState: " + dumpConnectionState(getConnectionState())); 1086 writer.println(" " + "State: " + BluetoothAdapter.nameForState(getState())); 1087 writer.println(" " + "MaxConnectedAudioDevices: " + getMaxConnectedAudioDevices()); 1088 writer.println(" " + "A2dpOffloadEnabled: " + mA2dpOffloadEnabled); 1089 writer.println(" " + "Discovering: " + mDiscovering); 1090 writer.println(" " + "DiscoveryEndMs: " + mDiscoveryEndMs); 1091 1092 writer.println(" " + "Bonded devices:"); 1093 StringBuilder sb = new StringBuilder(); 1094 for (BluetoothDevice device : mBondedDevices) { 1095 String address = device.getAddress(); 1096 String brEdrAddress = Utils.getBrEdrAddress(device); 1097 if (brEdrAddress.equals(address)) { 1098 writer.println( 1099 " " 1100 + BluetoothUtils.toAnonymizedAddress(address) 1101 + " [" 1102 + dumpDeviceType(mRemoteDevices.getType(device)) 1103 + "][ 0x" 1104 + String.format("%06X", mRemoteDevices.getBluetoothClass(device)) 1105 + " ] " 1106 + Utils.getName(device)); 1107 } else { 1108 sb.append(" ") 1109 .append(BluetoothUtils.toAnonymizedAddress(address)) 1110 .append(" => ") 1111 .append(BluetoothUtils.toAnonymizedAddress(brEdrAddress)) 1112 .append(" [") 1113 .append(dumpDeviceType(mRemoteDevices.getType(device))) 1114 .append("][ 0x") 1115 .append(String.format("%06X", mRemoteDevices.getBluetoothClass(device))) 1116 .append(" ] ") 1117 .append(Utils.getName(device)) 1118 .append("\n"); 1119 } 1120 } 1121 writer.println(sb.toString()); 1122 } 1123 dumpDeviceType(int deviceType)1124 private static String dumpDeviceType(int deviceType) { 1125 switch (deviceType) { 1126 case BluetoothDevice.DEVICE_TYPE_UNKNOWN: 1127 return " ???? "; 1128 case BluetoothDevice.DEVICE_TYPE_CLASSIC: 1129 return "BR/EDR"; 1130 case BluetoothDevice.DEVICE_TYPE_LE: 1131 return " LE "; 1132 case BluetoothDevice.DEVICE_TYPE_DUAL: 1133 return " DUAL "; 1134 default: 1135 return "Invalid device type: " + deviceType; 1136 } 1137 } 1138 dumpConnectionState(int state)1139 private static String dumpConnectionState(int state) { 1140 switch (state) { 1141 case BluetoothAdapter.STATE_DISCONNECTED: 1142 return "STATE_DISCONNECTED"; 1143 case BluetoothAdapter.STATE_DISCONNECTING: 1144 return "STATE_DISCONNECTING"; 1145 case BluetoothAdapter.STATE_CONNECTING: 1146 return "STATE_CONNECTING"; 1147 case BluetoothAdapter.STATE_CONNECTED: 1148 return "STATE_CONNECTED"; 1149 default: 1150 return "Unknown Connection State " + state; 1151 } 1152 } 1153 infoLog(String msg)1154 private static void infoLog(String msg) { 1155 Log.i(TAG, msg); 1156 } 1157 debugLog(String msg)1158 private static void debugLog(String msg) { 1159 Log.d(TAG, msg); 1160 } 1161 } 1162