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