blob: 42f455af7df4371ce035398f745ae5f8e77f61bb [file] [log] [blame]
Michael Wright9209c9c2015-09-03 17:57:01 +01001/*
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
17package com.android.systemui.keyboard;
18
Michael Wright9209c9c2015-09-03 17:57:01 +010019import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
Michael Wright9209c9c2015-09-03 17:57:01 +010021import android.bluetooth.le.BluetoothLeScanner;
22import android.bluetooth.le.ScanCallback;
23import android.bluetooth.le.ScanFilter;
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -070024import android.bluetooth.le.ScanRecord;
Michael Wright9209c9c2015-09-03 17:57:01 +010025import android.bluetooth.le.ScanResult;
26import android.bluetooth.le.ScanSettings;
27import android.content.ContentResolver;
28import android.content.Context;
29import android.content.DialogInterface;
Michael Wright9209c9c2015-09-03 17:57:01 +010030import android.content.res.Configuration;
31import android.hardware.input.InputManager;
32import android.os.Handler;
33import android.os.HandlerThread;
34import android.os.Looper;
35import android.os.Message;
36import android.os.Process;
37import android.os.SystemClock;
38import android.os.UserHandle;
39import android.provider.Settings.Secure;
40import android.text.TextUtils;
Michael Wright875d45a2015-11-26 14:10:32 +000041import android.util.Pair;
Michael Wright9209c9c2015-09-03 17:57:01 +010042import android.util.Slog;
Michael Wright875d45a2015-11-26 14:10:32 +000043import android.widget.Toast;
Michael Wright9209c9c2015-09-03 17:57:01 +010044
45import com.android.settingslib.bluetooth.BluetoothCallback;
Gus Prevasab336792018-11-14 13:52:20 -050046import com.android.settingslib.bluetooth.BluetoothUtils;
Michael Wright9209c9c2015-09-03 17:57:01 +010047import com.android.settingslib.bluetooth.CachedBluetoothDevice;
48import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
49import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
50import com.android.settingslib.bluetooth.LocalBluetoothManager;
51import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
Amin Shaikh947f76d2018-08-27 15:55:57 -040052import com.android.systemui.Dependency;
Michael Wright9209c9c2015-09-03 17:57:01 +010053import com.android.systemui.R;
54import com.android.systemui.SystemUI;
55
56import java.io.FileDescriptor;
57import java.io.PrintWriter;
58import java.util.Arrays;
59import java.util.Collection;
60import java.util.List;
Michael Wright9209c9c2015-09-03 17:57:01 +010061import java.util.Set;
62
63public 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
Dmitry Torokhov365bf062015-11-05 17:40:32 -080072 // 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
Michael Wright9209c9c2015-09-03 17:57:01 +010075 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;
Dmitry Torokhov365bf062015-11-05 17:40:32 -080081 private static final int STATE_PAIRING = 5;
82 private static final int STATE_PAIRED = 6;
Michael Wright875d45a2015-11-26 14:10:32 +000083 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;
Michael Wright9209c9c2015-09-03 17:57:01 +010086
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;
Dmitry Torokhov365bf062015-11-05 17:40:32 -080097 private static final int MSG_BLE_ABORT_SCAN = 10;
Michael Wright875d45a2015-11-26 14:10:32 +000098 private static final int MSG_SHOW_ERROR = 11;
Michael Wright9209c9c2015-09-03 17:57:01 +010099
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;
Dmitry Torokhov365bf062015-11-05 17:40:32 -0800114 private int mScanAttempt = 0;
Michael Wright9209c9c2015-09-03 17:57:01 +0100115 private ScanCallback mScanCallback;
116 private BluetoothDialog mDialog;
117
118 private int mState;
119
Dave Mankoffa5d8a392019-10-10 12:21:09 -0400120 public KeyboardUI(Context context) {
121 super(context);
122 }
123
Michael Wright9209c9c2015-09-03 17:57:01 +0100124 @Override
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
134 protected void onConfigurationChanged(Configuration newConfig) {
135 }
136
137 @Override
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
149 protected void onBootCompleted() {
150 mHandler.sendEmptyMessage(MSG_ON_BOOT_COMPLETED);
151 }
152
153 @Override
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
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
Amin Shaikh947f76d2018-08-27 15:55:57 -0400179 LocalBluetoothManager bluetoothManager = Dependency.get(LocalBluetoothManager.class);
Michael Wright9209c9c2015-09-03 17:57:01 +0100180 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());
Kunhung Lie7b7cb82018-05-17 11:04:54 +0800191 BluetoothUtils.setErrorListener(new BluetoothErrorListener());
Michael Wright9209c9c2015-09-03 17:57:01 +0100192
Yohei Yukawa8ce2a532015-11-25 20:35:04 -0800193 InputManager im = context.getSystemService(InputManager.class);
Michael Wright9209c9c2015-09-03 17:57:01 +0100194 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
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();
Michael Wright875d45a2015-11-26 14:10:32 +0000218 } else if (mState == STATE_WAITING_FOR_BLUETOOTH) {
219 mUIHandler.sendEmptyMessage(MSG_DISMISS_BLUETOOTH_DIALOG);
Michael Wright9209c9c2015-09-03 17:57:01 +0100220 }
221 mState = STATE_WAITING_FOR_TABLET_MODE_EXIT;
222 return;
223 }
224
225 final int btState = mLocalBluetoothAdapter.getState();
Michael Wright875d45a2015-11-26 14:10:32 +0000226 if ((btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON)
Michael Wright9209c9c2015-09-03 17:57:01 +0100227 && 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();
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -0700246 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();
Michael Wright9209c9c2015-09-03 17:57:01 +0100259 }
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
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
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
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
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
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
323 private CachedBluetoothDevice getCachedBluetoothDevice(BluetoothDevice d) {
324 CachedBluetoothDevice cachedDevice = mCachedDeviceManager.findDevice(d);
325 if (cachedDevice == null) {
timhypeng8a6ef892018-07-31 15:44:31 +0800326 cachedDevice = mCachedDeviceManager.addDevice(d);
Michael Wright9209c9c2015-09-03 17:57:01 +0100327 }
328 return cachedDevice;
329 }
330
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);
Dmitry Torokhov365bf062015-11-05 17:40:32 -0800342
343 Message abortMsg = mHandler.obtainMessage(MSG_BLE_ABORT_SCAN, ++mScanAttempt, 0);
344 mHandler.sendMessageDelayed(abortMsg, BLUETOOTH_SCAN_TIMEOUT_MILLIS);
Michael Wright9209c9c2015-09-03 17:57:01 +0100345 }
346
347 private void stopScanning() {
348 if (mScanCallback != null) {
Michael Wright875d45a2015-11-26 14:10:32 +0000349 BluetoothLeScanner scanner = mLocalBluetoothAdapter.getBluetoothLeScanner();
350 if (scanner != null) {
351 scanner.stopScan(mScanCallback);
352 }
Michael Wright9209c9c2015-09-03 17:57:01 +0100353 mScanCallback = null;
354 }
355 }
356
357 // Should only be called on the handler thread
Dmitry Torokhov365bf062015-11-05 17:40:32 -0800358 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
Michael Wright9209c9c2015-09-03 17:57:01 +0100371 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
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
387 private void onDeviceBondStateChangedInternal(CachedBluetoothDevice d, int bondState) {
Michael Wright875d45a2015-11-26 14:10:32 +0000388 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 }
Michael Wright9209c9c2015-09-03 17:57:01 +0100396 }
397 }
398
399 // Should only be called on the handler thread
400 private void onBleScanFailedInternal() {
401 mScanCallback = null;
402 if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY) {
403 mState = STATE_DEVICE_NOT_FOUND;
404 }
405 }
406
Michael Wright875d45a2015-11-26 14:10:32 +0000407 // 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.
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
Michael Wright9209c9c2015-09-03 17:57:01 +0100418 private final class KeyboardUIHandler extends Handler {
419 public KeyboardUIHandler() {
420 super(Looper.getMainLooper(), null, true /*async*/);
421 }
422 @Override
423 public void handleMessage(Message msg) {
424 switch(msg.what) {
425 case MSG_SHOW_BLUETOOTH_DIALOG: {
Michael Wright875d45a2015-11-26 14:10:32 +0000426 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();
Michael Wright9209c9c2015-09-03 17:57:01 +0100434 mDialog = new BluetoothDialog(mContext);
435 mDialog.setTitle(R.string.enable_bluetooth_title);
436 mDialog.setMessage(R.string.enable_bluetooth_message);
Michael Wright875d45a2015-11-26 14:10:32 +0000437 mDialog.setPositiveButton(
438 R.string.enable_bluetooth_confirmation_ok, clickListener);
439 mDialog.setNegativeButton(android.R.string.cancel, clickListener);
440 mDialog.setOnDismissListener(dismissListener);
Michael Wright9209c9c2015-09-03 17:57:01 +0100441 mDialog.show();
442 break;
443 }
444 case MSG_DISMISS_BLUETOOTH_DIALOG: {
445 if (mDialog != null) {
446 mDialog.dismiss();
Michael Wright9209c9c2015-09-03 17:57:01 +0100447 }
448 break;
449 }
450 }
451 }
452 }
453
454 private final class KeyboardHandler extends Handler {
455 public KeyboardHandler(Looper looper) {
456 super(looper, null, true /*async*/);
457 }
458
459 @Override
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 }
Dmitry Torokhov365bf062015-11-05 17:40:32 -0800481 break;
482 }
483 case MSG_BLE_ABORT_SCAN: {
484 int scanAttempt = msg.arg1;
485 bleAbortScanInternal(scanAttempt);
486 break;
Michael Wright9209c9c2015-09-03 17:57:01 +0100487 }
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 }
Michael Wright875d45a2015-11-26 14:10:32 +0000510 case MSG_SHOW_ERROR: {
511 Pair<Context, String> p = (Pair<Context, String>) msg.obj;
512 onShowErrorInternal(p.first, p.second, msg.arg1);
513 }
Michael Wright9209c9c2015-09-03 17:57:01 +0100514 }
515 }
516 }
517
518 private final class BluetoothDialogClickListener implements DialogInterface.OnClickListener {
519 @Override
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
Michael Wright875d45a2015-11-26 14:10:32 +0000527 private final class BluetoothDialogDismissListener
528 implements DialogInterface.OnDismissListener {
529 @Override
530 public void onDismiss(DialogInterface dialog) {
531 mDialog = null;
532 }
533 }
534
Michael Wright9209c9c2015-09-03 17:57:01 +0100535 private final class KeyboardScanCallback extends ScanCallback {
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -0700536
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
Michael Wright9209c9c2015-09-03 17:57:01 +0100545 @Override
546 public void onBatchScanResults(List<ScanResult> results) {
547 if (DEBUG) {
548 Slog.d(TAG, "onBatchScanResults(" + results.size() + ")");
549 }
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -0700550
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);
Michael Wright9209c9c2015-09-03 17:57:01 +0100557 }
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -0700558
559 if (isDeviceDiscoverable(result) && result.getRssi() > bestRssi) {
560 bestDevice = result.getDevice();
561 bestRssi = result.getRssi();
562 }
563 }
564
565 if (bestDevice != null) {
Michael Wright9209c9c2015-09-03 17:57:01 +0100566 mHandler.obtainMessage(MSG_ON_BLUETOOTH_DEVICE_ADDED, bestDevice).sendToTarget();
567 }
568 }
569
570 @Override
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
579 public void onScanResult(int callbackType, ScanResult result) {
580 if (DEBUG) {
581 Slog.d(TAG, "onScanResult(" + callbackType + ", " + result + ")");
582 }
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -0700583
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 }
Michael Wright9209c9c2015-09-03 17:57:01 +0100591 }
592 }
593
594 private final class BluetoothCallbackHandler implements BluetoothCallback {
595 @Override
596 public void onBluetoothStateChanged(int bluetoothState) {
597 mHandler.obtainMessage(MSG_ON_BLUETOOTH_STATE_CHANGED,
598 bluetoothState, 0).sendToTarget();
599 }
600
601 @Override
602 public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) {
603 mHandler.obtainMessage(MSG_ON_DEVICE_BOND_STATE_CHANGED,
604 bondState, 0, cachedDevice).sendToTarget();
605 }
Michael Wright9209c9c2015-09-03 17:57:01 +0100606 }
607
Kunhung Lie7b7cb82018-05-17 11:04:54 +0800608 private final class BluetoothErrorListener implements BluetoothUtils.ErrorListener {
Michael Wright875d45a2015-11-26 14:10:32 +0000609 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
Michael Wright9209c9c2015-09-03 17:57:01 +0100615 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";
Michael Wright9209c9c2015-09-03 17:57:01 +0100627 case STATE_PAIRING:
628 return "STATE_PAIRING";
629 case STATE_PAIRED:
630 return "STATE_PAIRED";
Michael Wright875d45a2015-11-26 14:10:32 +0000631 case STATE_PAIRING_FAILED:
632 return "STATE_PAIRING_FAILED";
Michael Wright9209c9c2015-09-03 17:57:01 +0100633 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:
Michael Wright875d45a2015-11-26 14:10:32 +0000639 return "STATE_UNKNOWN (" + state + ")";
Michael Wright9209c9c2015-09-03 17:57:01 +0100640 }
641 }
642}