1 /* 2 * Copyright (C) 2015 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.systemui.keyboard; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.le.BluetoothLeScanner; 22 import android.bluetooth.le.ScanCallback; 23 import android.bluetooth.le.ScanFilter; 24 import android.bluetooth.le.ScanRecord; 25 import android.bluetooth.le.ScanResult; 26 import android.bluetooth.le.ScanSettings; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.hardware.input.InputManager; 30 import android.os.Handler; 31 import android.os.HandlerThread; 32 import android.os.Looper; 33 import android.os.Message; 34 import android.os.Process; 35 import android.os.SystemClock; 36 import android.os.UserHandle; 37 import android.provider.Settings.Secure; 38 import android.text.TextUtils; 39 import android.util.Pair; 40 import android.util.Slog; 41 import android.widget.Toast; 42 43 import androidx.annotation.NonNull; 44 45 import com.android.settingslib.bluetooth.BluetoothCallback; 46 import com.android.settingslib.bluetooth.BluetoothUtils; 47 import com.android.settingslib.bluetooth.CachedBluetoothDevice; 48 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; 49 import com.android.settingslib.bluetooth.LocalBluetoothAdapter; 50 import com.android.settingslib.bluetooth.LocalBluetoothManager; 51 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; 52 import com.android.systemui.CoreStartable; 53 import com.android.systemui.R; 54 import com.android.systemui.dagger.SysUISingleton; 55 import com.android.systemui.util.settings.SecureSettings; 56 57 import java.io.PrintWriter; 58 import java.util.Arrays; 59 import java.util.Collection; 60 import java.util.List; 61 import java.util.Set; 62 63 import javax.inject.Inject; 64 import javax.inject.Provider; 65 66 /** */ 67 @SysUISingleton 68 public class KeyboardUI implements CoreStartable, InputManager.OnTabletModeChangedListener { 69 private static final String TAG = "KeyboardUI"; 70 private static final boolean DEBUG = false; 71 72 // Give BT some time to start after SyUI comes up. This avoids flashing a dialog in the user's 73 // face because BT starts a little bit later in the boot process than SysUI and it takes some 74 // time for us to receive the signal that it's starting. 75 private static final long BLUETOOTH_START_DELAY_MILLIS = 10 * 1000; 76 77 // We will be scanning up to 30 seconds, after which we'll stop. 78 private static final long BLUETOOTH_SCAN_TIMEOUT_MILLIS = 30 * 1000; 79 80 private static final int STATE_NOT_ENABLED = -1; 81 private static final int STATE_UNKNOWN = 0; 82 private static final int STATE_WAITING_FOR_BOOT_COMPLETED = 1; 83 private static final int STATE_WAITING_FOR_TABLET_MODE_EXIT = 2; 84 private static final int STATE_WAITING_FOR_DEVICE_DISCOVERY = 3; 85 private static final int STATE_WAITING_FOR_BLUETOOTH = 4; 86 private static final int STATE_PAIRING = 5; 87 private static final int STATE_PAIRED = 6; 88 private static final int STATE_PAIRING_FAILED = 7; 89 private static final int STATE_USER_CANCELLED = 8; 90 private static final int STATE_DEVICE_NOT_FOUND = 9; 91 92 private static final int MSG_INIT = 0; 93 private static final int MSG_ON_BOOT_COMPLETED = 1; 94 private static final int MSG_PROCESS_KEYBOARD_STATE = 2; 95 private static final int MSG_ENABLE_BLUETOOTH = 3; 96 private static final int MSG_ON_BLUETOOTH_STATE_CHANGED = 4; 97 private static final int MSG_ON_DEVICE_BOND_STATE_CHANGED = 5; 98 private static final int MSG_ON_BLUETOOTH_DEVICE_ADDED = 6; 99 private static final int MSG_ON_BLE_SCAN_FAILED = 7; 100 private static final int MSG_SHOW_BLUETOOTH_DIALOG = 8; 101 private static final int MSG_DISMISS_BLUETOOTH_DIALOG = 9; 102 private static final int MSG_BLE_ABORT_SCAN = 10; 103 private static final int MSG_SHOW_ERROR = 11; 104 105 private volatile KeyboardHandler mHandler; 106 private volatile KeyboardUIHandler mUIHandler; 107 108 protected volatile Context mContext; 109 110 private final Provider<LocalBluetoothManager> mBluetoothManagerProvider; 111 private final SecureSettings mSecureSettings; 112 113 private boolean mEnabled; 114 private String mKeyboardName; 115 private CachedBluetoothDeviceManager mCachedDeviceManager; 116 private LocalBluetoothAdapter mLocalBluetoothAdapter; 117 private LocalBluetoothProfileManager mProfileManager; 118 private boolean mBootCompleted; 119 private long mBootCompletedTime; 120 121 private int mInTabletMode = InputManager.SWITCH_STATE_UNKNOWN; 122 private int mScanAttempt = 0; 123 private ScanCallback mScanCallback; 124 private BluetoothDialog mDialog; 125 126 private int mState; 127 128 @Inject KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider, SecureSettings secureSettings)129 public KeyboardUI(Context context, Provider<LocalBluetoothManager> bluetoothManagerProvider, 130 SecureSettings secureSettings) { 131 mContext = context; 132 this.mBluetoothManagerProvider = bluetoothManagerProvider; 133 mSecureSettings = secureSettings; 134 } 135 136 @Override start()137 public void start() { 138 HandlerThread thread = new HandlerThread("Keyboard", Process.THREAD_PRIORITY_BACKGROUND); 139 thread.start(); 140 mHandler = new KeyboardHandler(thread.getLooper()); 141 mHandler.sendEmptyMessage(MSG_INIT); 142 } 143 144 @Override dump(PrintWriter pw, String[] args)145 public void dump(PrintWriter pw, String[] args) { 146 pw.println("KeyboardUI:"); 147 pw.println(" mEnabled=" + mEnabled); 148 pw.println(" mBootCompleted=" + mEnabled); 149 pw.println(" mBootCompletedTime=" + mBootCompletedTime); 150 pw.println(" mKeyboardName=" + mKeyboardName); 151 pw.println(" mInTabletMode=" + mInTabletMode); 152 pw.println(" mState=" + stateToString(mState)); 153 } 154 155 @Override onBootCompleted()156 public void onBootCompleted() { 157 mHandler.sendEmptyMessage(MSG_ON_BOOT_COMPLETED); 158 } 159 160 @Override onTabletModeChanged(long whenNanos, boolean inTabletMode)161 public void onTabletModeChanged(long whenNanos, boolean inTabletMode) { 162 if (DEBUG) { 163 Slog.d(TAG, "onTabletModeChanged(" + whenNanos + ", " + inTabletMode + ")"); 164 } 165 166 if (inTabletMode && mInTabletMode != InputManager.SWITCH_STATE_ON 167 || !inTabletMode && mInTabletMode != InputManager.SWITCH_STATE_OFF) { 168 mInTabletMode = inTabletMode ? 169 InputManager.SWITCH_STATE_ON : InputManager.SWITCH_STATE_OFF; 170 processKeyboardState(); 171 } 172 } 173 174 // Shoud only be called on the handler thread init()175 private void init() { 176 Context context = mContext; 177 mKeyboardName = 178 context.getString(com.android.internal.R.string.config_packagedKeyboardName); 179 if (TextUtils.isEmpty(mKeyboardName)) { 180 if (DEBUG) { 181 Slog.d(TAG, "No packaged keyboard name given."); 182 } 183 return; 184 } 185 186 LocalBluetoothManager bluetoothManager = mBluetoothManagerProvider.get(); 187 if (bluetoothManager == null) { 188 if (DEBUG) { 189 Slog.e(TAG, "Failed to retrieve LocalBluetoothManager instance"); 190 } 191 return; 192 } 193 mEnabled = true; 194 mCachedDeviceManager = bluetoothManager.getCachedDeviceManager(); 195 mLocalBluetoothAdapter = bluetoothManager.getBluetoothAdapter(); 196 mProfileManager = bluetoothManager.getProfileManager(); 197 bluetoothManager.getEventManager().registerCallback(new BluetoothCallbackHandler()); 198 BluetoothUtils.setErrorListener(new BluetoothErrorListener()); 199 200 InputManager im = context.getSystemService(InputManager.class); 201 im.registerOnTabletModeChangedListener(this, mHandler); 202 mInTabletMode = im.isInTabletMode(); 203 204 processKeyboardState(); 205 mUIHandler = new KeyboardUIHandler(); 206 } 207 208 // Should only be called on the handler thread processKeyboardState()209 private void processKeyboardState() { 210 mHandler.removeMessages(MSG_PROCESS_KEYBOARD_STATE); 211 212 if (!mEnabled) { 213 mState = STATE_NOT_ENABLED; 214 return; 215 } 216 217 if (!mBootCompleted) { 218 mState = STATE_WAITING_FOR_BOOT_COMPLETED; 219 return; 220 } 221 222 if (mInTabletMode != InputManager.SWITCH_STATE_OFF) { 223 if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) { 224 stopScanning(); 225 } else if (mState == STATE_WAITING_FOR_BLUETOOTH) { 226 mUIHandler.sendEmptyMessage(MSG_DISMISS_BLUETOOTH_DIALOG); 227 } 228 mState = STATE_WAITING_FOR_TABLET_MODE_EXIT; 229 return; 230 } 231 232 final int btState = mLocalBluetoothAdapter.getState(); 233 if ((btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON) 234 && mState == STATE_WAITING_FOR_BLUETOOTH) { 235 // If we're waiting for bluetooth but it has come on in the meantime, or is coming 236 // on, just dismiss the dialog. This frequently happens during device startup. 237 mUIHandler.sendEmptyMessage(MSG_DISMISS_BLUETOOTH_DIALOG); 238 } 239 240 if (btState == BluetoothAdapter.STATE_TURNING_ON) { 241 mState = STATE_WAITING_FOR_BLUETOOTH; 242 // Wait for bluetooth to fully come on. 243 return; 244 } 245 246 if (btState != BluetoothAdapter.STATE_ON) { 247 mState = STATE_WAITING_FOR_BLUETOOTH; 248 showBluetoothDialog(); 249 return; 250 } 251 252 CachedBluetoothDevice device = getPairedKeyboard(); 253 if (mState == STATE_WAITING_FOR_TABLET_MODE_EXIT || mState == STATE_WAITING_FOR_BLUETOOTH) { 254 if (device != null) { 255 // If we're just coming out of tablet mode or BT just turned on, 256 // then we want to go ahead and automatically connect to the 257 // keyboard. We want to avoid this in other cases because we might 258 // be spuriously called after the user has manually disconnected 259 // the keyboard, meaning we shouldn't try to automtically connect 260 // it again. 261 mState = STATE_PAIRED; 262 device.connect(false); 263 return; 264 } 265 mCachedDeviceManager.clearNonBondedDevices(); 266 } 267 268 device = getDiscoveredKeyboard(); 269 if (device != null) { 270 mState = STATE_PAIRING; 271 device.startPairing(); 272 } else { 273 mState = STATE_WAITING_FOR_DEVICE_DISCOVERY; 274 startScanning(); 275 } 276 } 277 278 // Should only be called on the handler thread onBootCompletedInternal()279 public void onBootCompletedInternal() { 280 mBootCompleted = true; 281 mBootCompletedTime = SystemClock.uptimeMillis(); 282 if (mState == STATE_WAITING_FOR_BOOT_COMPLETED) { 283 processKeyboardState(); 284 } 285 } 286 287 // Should only be called on the handler thread showBluetoothDialog()288 private void showBluetoothDialog() { 289 if (isUserSetupComplete()) { 290 long now = SystemClock.uptimeMillis(); 291 long earliestDialogTime = mBootCompletedTime + BLUETOOTH_START_DELAY_MILLIS; 292 if (earliestDialogTime < now) { 293 mUIHandler.sendEmptyMessage(MSG_SHOW_BLUETOOTH_DIALOG); 294 } else { 295 mHandler.sendEmptyMessageAtTime(MSG_PROCESS_KEYBOARD_STATE, earliestDialogTime); 296 } 297 } else { 298 // If we're in setup wizard and the keyboard is docked, just automatically enable BT. 299 mLocalBluetoothAdapter.enable(); 300 } 301 } 302 isUserSetupComplete()303 private boolean isUserSetupComplete() { 304 return mSecureSettings.getIntForUser( 305 Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0; 306 } 307 getPairedKeyboard()308 private CachedBluetoothDevice getPairedKeyboard() { 309 Set<BluetoothDevice> devices = mLocalBluetoothAdapter.getBondedDevices(); 310 for (BluetoothDevice d : devices) { 311 if (mKeyboardName.equals(d.getName())) { 312 return getCachedBluetoothDevice(d); 313 } 314 } 315 return null; 316 } 317 getDiscoveredKeyboard()318 private CachedBluetoothDevice getDiscoveredKeyboard() { 319 Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy(); 320 for (CachedBluetoothDevice d : devices) { 321 if (d.getName().equals(mKeyboardName)) { 322 return d; 323 } 324 } 325 return null; 326 } 327 328 getCachedBluetoothDevice(BluetoothDevice d)329 private CachedBluetoothDevice getCachedBluetoothDevice(BluetoothDevice d) { 330 CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(d); 331 if (cachedDevice == null) { 332 cachedDevice = mCachedDeviceManager.addDevice(d); 333 } 334 return cachedDevice; 335 } 336 startScanning()337 private void startScanning() { 338 BluetoothLeScanner scanner = mLocalBluetoothAdapter.getBluetoothLeScanner(); 339 ScanFilter filter = (new ScanFilter.Builder()).setDeviceName(mKeyboardName).build(); 340 ScanSettings settings = (new ScanSettings.Builder()) 341 .setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES) 342 .setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT) 343 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) 344 .setReportDelay(0) 345 .build(); 346 mScanCallback = new KeyboardScanCallback(); 347 scanner.startScan(Arrays.asList(filter), settings, mScanCallback); 348 349 Message abortMsg = mHandler.obtainMessage(MSG_BLE_ABORT_SCAN, ++mScanAttempt, 0); 350 mHandler.sendMessageDelayed(abortMsg, BLUETOOTH_SCAN_TIMEOUT_MILLIS); 351 } 352 stopScanning()353 private void stopScanning() { 354 if (mScanCallback != null) { 355 BluetoothLeScanner scanner = mLocalBluetoothAdapter.getBluetoothLeScanner(); 356 if (scanner != null) { 357 scanner.stopScan(mScanCallback); 358 } 359 mScanCallback = null; 360 } 361 } 362 363 // Should only be called on the handler thread bleAbortScanInternal(int scanAttempt)364 private void bleAbortScanInternal(int scanAttempt) { 365 if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY && scanAttempt == mScanAttempt) { 366 if (DEBUG) { 367 Slog.d(TAG, "Bluetooth scan timed out"); 368 } 369 stopScanning(); 370 // FIXME: should we also try shutting off bluetooth if we enabled 371 // it in the first place? 372 mState = STATE_DEVICE_NOT_FOUND; 373 } 374 } 375 376 // Should only be called on the handler thread onDeviceAddedInternal(CachedBluetoothDevice d)377 private void onDeviceAddedInternal(CachedBluetoothDevice d) { 378 if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY && d.getName().equals(mKeyboardName)) { 379 stopScanning(); 380 d.startPairing(); 381 mState = STATE_PAIRING; 382 } 383 } 384 385 // Should only be called on the handler thread onBluetoothStateChangedInternal(int bluetoothState)386 private void onBluetoothStateChangedInternal(int bluetoothState) { 387 if (bluetoothState == BluetoothAdapter.STATE_ON && mState == STATE_WAITING_FOR_BLUETOOTH) { 388 processKeyboardState(); 389 } 390 } 391 392 // Should only be called on the handler thread onDeviceBondStateChangedInternal(CachedBluetoothDevice d, int bondState)393 private void onDeviceBondStateChangedInternal(CachedBluetoothDevice d, int bondState) { 394 if (mState == STATE_PAIRING && d.getName().equals(mKeyboardName)) { 395 if (bondState == BluetoothDevice.BOND_BONDED) { 396 // We don't need to manually connect to the device here because it will 397 // automatically try to connect after it has been paired. 398 mState = STATE_PAIRED; 399 } else if (bondState == BluetoothDevice.BOND_NONE) { 400 mState = STATE_PAIRING_FAILED; 401 } 402 } 403 } 404 405 // Should only be called on the handler thread onBleScanFailedInternal()406 private void onBleScanFailedInternal() { 407 mScanCallback = null; 408 if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) { 409 mState = STATE_DEVICE_NOT_FOUND; 410 } 411 } 412 413 // Should only be called on the handler thread. We want to be careful not to show errors for 414 // pairings not initiated by this UI, so we only pop up the toast when we're at an appropriate 415 // point in our pairing flow and it's the expected device. onShowErrorInternal(Context context, String name, int messageResId)416 private void onShowErrorInternal(Context context, String name, int messageResId) { 417 if ((mState == STATE_PAIRING || mState == STATE_PAIRING_FAILED) 418 && mKeyboardName.equals(name)) { 419 String message = context.getString(messageResId, name); 420 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 421 } 422 } 423 424 private final class KeyboardUIHandler extends Handler { KeyboardUIHandler()425 public KeyboardUIHandler() { 426 super(Looper.getMainLooper(), null, true /*async*/); 427 } 428 @Override handleMessage(Message msg)429 public void handleMessage(Message msg) { 430 switch(msg.what) { 431 case MSG_SHOW_BLUETOOTH_DIALOG: { 432 if (mDialog != null) { 433 // Don't show another dialog if one is already present 434 break; 435 } 436 DialogInterface.OnClickListener clickListener = 437 new BluetoothDialogClickListener(); 438 DialogInterface.OnDismissListener dismissListener = 439 new BluetoothDialogDismissListener(); 440 mDialog = new BluetoothDialog(mContext); 441 mDialog.setTitle(R.string.enable_bluetooth_title); 442 mDialog.setMessage(R.string.enable_bluetooth_message); 443 mDialog.setPositiveButton( 444 R.string.enable_bluetooth_confirmation_ok, clickListener); 445 mDialog.setNegativeButton(android.R.string.cancel, clickListener); 446 mDialog.setOnDismissListener(dismissListener); 447 mDialog.show(); 448 break; 449 } 450 case MSG_DISMISS_BLUETOOTH_DIALOG: { 451 if (mDialog != null) { 452 mDialog.dismiss(); 453 } 454 break; 455 } 456 } 457 } 458 } 459 460 private final class KeyboardHandler extends Handler { KeyboardHandler(Looper looper)461 public KeyboardHandler(Looper looper) { 462 super(looper, null, true /*async*/); 463 } 464 465 @Override handleMessage(Message msg)466 public void handleMessage(Message msg) { 467 switch(msg.what) { 468 case MSG_INIT: { 469 init(); 470 break; 471 } 472 case MSG_ON_BOOT_COMPLETED: { 473 onBootCompletedInternal(); 474 break; 475 } 476 case MSG_PROCESS_KEYBOARD_STATE: { 477 processKeyboardState(); 478 break; 479 } 480 case MSG_ENABLE_BLUETOOTH: { 481 boolean enable = msg.arg1 == 1; 482 if (enable) { 483 mLocalBluetoothAdapter.enable(); 484 } else { 485 mState = STATE_USER_CANCELLED; 486 } 487 break; 488 } 489 case MSG_BLE_ABORT_SCAN: { 490 int scanAttempt = msg.arg1; 491 bleAbortScanInternal(scanAttempt); 492 break; 493 } 494 case MSG_ON_BLUETOOTH_STATE_CHANGED: { 495 int bluetoothState = msg.arg1; 496 onBluetoothStateChangedInternal(bluetoothState); 497 break; 498 } 499 case MSG_ON_DEVICE_BOND_STATE_CHANGED: { 500 CachedBluetoothDevice d = (CachedBluetoothDevice)msg.obj; 501 int bondState = msg.arg1; 502 onDeviceBondStateChangedInternal(d, bondState); 503 break; 504 } 505 case MSG_ON_BLUETOOTH_DEVICE_ADDED: { 506 BluetoothDevice d = (BluetoothDevice)msg.obj; 507 CachedBluetoothDevice cachedDevice = getCachedBluetoothDevice(d); 508 onDeviceAddedInternal(cachedDevice); 509 break; 510 511 } 512 case MSG_ON_BLE_SCAN_FAILED: { 513 onBleScanFailedInternal(); 514 break; 515 } 516 case MSG_SHOW_ERROR: { 517 Pair<Context, String> p = (Pair<Context, String>) msg.obj; 518 onShowErrorInternal(p.first, p.second, msg.arg1); 519 } 520 } 521 } 522 } 523 524 private final class BluetoothDialogClickListener implements DialogInterface.OnClickListener { 525 @Override onClick(DialogInterface dialog, int which)526 public void onClick(DialogInterface dialog, int which) { 527 int enable = DialogInterface.BUTTON_POSITIVE == which ? 1 : 0; 528 mHandler.obtainMessage(MSG_ENABLE_BLUETOOTH, enable, 0).sendToTarget(); 529 mDialog = null; 530 } 531 } 532 533 private final class BluetoothDialogDismissListener 534 implements DialogInterface.OnDismissListener { 535 @Override onDismiss(DialogInterface dialog)536 public void onDismiss(DialogInterface dialog) { 537 mDialog = null; 538 } 539 } 540 541 private final class KeyboardScanCallback extends ScanCallback { 542 isDeviceDiscoverable(ScanResult result)543 private boolean isDeviceDiscoverable(ScanResult result) { 544 final ScanRecord scanRecord = result.getScanRecord(); 545 final int flags = scanRecord.getAdvertiseFlags(); 546 final int BT_DISCOVERABLE_MASK = 0x03; 547 548 return (flags & BT_DISCOVERABLE_MASK) != 0; 549 } 550 551 @Override onBatchScanResults(List<ScanResult> results)552 public void onBatchScanResults(List<ScanResult> results) { 553 if (DEBUG) { 554 Slog.d(TAG, "onBatchScanResults(" + results.size() + ")"); 555 } 556 557 BluetoothDevice bestDevice = null; 558 int bestRssi = Integer.MIN_VALUE; 559 560 for (ScanResult result : results) { 561 if (DEBUG) { 562 Slog.d(TAG, "onBatchScanResults: considering " + result); 563 } 564 565 if (isDeviceDiscoverable(result) && result.getRssi() > bestRssi) { 566 bestDevice = result.getDevice(); 567 bestRssi = result.getRssi(); 568 } 569 } 570 571 if (bestDevice != null) { 572 mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED, bestDevice).sendToTarget(); 573 } 574 } 575 576 @Override onScanFailed(int errorCode)577 public void onScanFailed(int errorCode) { 578 if (DEBUG) { 579 Slog.d(TAG, "onScanFailed(" + errorCode + ")"); 580 } 581 mHandler.obtainMessage(MSG_ON_BLE_SCAN_FAILED).sendToTarget(); 582 } 583 584 @Override onScanResult(int callbackType, ScanResult result)585 public void onScanResult(int callbackType, ScanResult result) { 586 if (DEBUG) { 587 Slog.d(TAG, "onScanResult(" + callbackType + ", " + result + ")"); 588 } 589 590 if (isDeviceDiscoverable(result)) { 591 mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED, 592 result.getDevice()).sendToTarget(); 593 } else if (DEBUG) { 594 Slog.d(TAG, "onScanResult: device " + result.getDevice() + 595 " is not discoverable, ignoring"); 596 } 597 } 598 } 599 600 private final class BluetoothCallbackHandler implements BluetoothCallback { 601 @Override onBluetoothStateChanged(@luetoothCallback.AdapterState int bluetoothState)602 public void onBluetoothStateChanged(@BluetoothCallback.AdapterState int bluetoothState) { 603 mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED, 604 bluetoothState, 0).sendToTarget(); 605 } 606 607 @Override onDeviceBondStateChanged( @onNull CachedBluetoothDevice cachedDevice, int bondState)608 public void onDeviceBondStateChanged( 609 @NonNull CachedBluetoothDevice cachedDevice, int bondState) { 610 mHandler.obtainMessage(MSG_ON_DEVICE_BOND_STATE_CHANGED, 611 bondState, 0, cachedDevice).sendToTarget(); 612 } 613 } 614 615 private final class BluetoothErrorListener implements BluetoothUtils.ErrorListener { onShowError(Context context, String name, int messageResId)616 public void onShowError(Context context, String name, int messageResId) { 617 mHandler.obtainMessage(MSG_SHOW_ERROR, messageResId, 0 /*unused*/, 618 new Pair<>(context, name)).sendToTarget(); 619 } 620 } 621 stateToString(int state)622 private static String stateToString(int state) { 623 switch (state) { 624 case STATE_NOT_ENABLED: 625 return "STATE_NOT_ENABLED"; 626 case STATE_WAITING_FOR_BOOT_COMPLETED: 627 return "STATE_WAITING_FOR_BOOT_COMPLETED"; 628 case STATE_WAITING_FOR_TABLET_MODE_EXIT: 629 return "STATE_WAITING_FOR_TABLET_MODE_EXIT"; 630 case STATE_WAITING_FOR_DEVICE_DISCOVERY: 631 return "STATE_WAITING_FOR_DEVICE_DISCOVERY"; 632 case STATE_WAITING_FOR_BLUETOOTH: 633 return "STATE_WAITING_FOR_BLUETOOTH"; 634 case STATE_PAIRING: 635 return "STATE_PAIRING"; 636 case STATE_PAIRED: 637 return "STATE_PAIRED"; 638 case STATE_PAIRING_FAILED: 639 return "STATE_PAIRING_FAILED"; 640 case STATE_USER_CANCELLED: 641 return "STATE_USER_CANCELLED"; 642 case STATE_DEVICE_NOT_FOUND: 643 return "STATE_DEVICE_NOT_FOUND"; 644 case STATE_UNKNOWN: 645 default: 646 return "STATE_UNKNOWN (" + state + ")"; 647 } 648 } 649 } 650