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