blob: 4b775a5a61e4044183f194b302f5c20a73b8b5d1 [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;
46import com.android.settingslib.bluetooth.CachedBluetoothDevice;
47import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
48import com.android.settingslib.bluetooth.LocalBluetoothAdapter;
49import com.android.settingslib.bluetooth.LocalBluetoothManager;
50import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
Michael Wright875d45a2015-11-26 14:10:32 +000051import com.android.settingslib.bluetooth.Utils;
Michael Wright9209c9c2015-09-03 17:57:01 +010052import com.android.systemui.R;
53import com.android.systemui.SystemUI;
54
55import java.io.FileDescriptor;
56import java.io.PrintWriter;
57import java.util.Arrays;
58import java.util.Collection;
59import java.util.List;
Michael Wright9209c9c2015-09-03 17:57:01 +010060import java.util.Set;
61
62public 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
Dmitry Torokhov365bf062015-11-05 17:40:32 -080071 // 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
Michael Wright9209c9c2015-09-03 17:57:01 +010074 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;
Dmitry Torokhov365bf062015-11-05 17:40:32 -080080 private static final int STATE_PAIRING = 5;
81 private static final int STATE_PAIRED = 6;
Michael Wright875d45a2015-11-26 14:10:32 +000082 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;
Michael Wright9209c9c2015-09-03 17:57:01 +010085
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;
Dmitry Torokhov365bf062015-11-05 17:40:32 -080096 private static final int MSG_BLE_ABORT_SCAN = 10;
Michael Wright875d45a2015-11-26 14:10:32 +000097 private static final int MSG_SHOW_ERROR = 11;
Michael Wright9209c9c2015-09-03 17:57:01 +010098
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;
Dmitry Torokhov365bf062015-11-05 17:40:32 -0800113 private int mScanAttempt = 0;
Michael Wright9209c9c2015-09-03 17:57:01 +0100114 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());
Michael Wright875d45a2015-11-26 14:10:32 +0000186 Utils.setErrorListener(new BluetoothErrorListener());
Michael Wright9209c9c2015-09-03 17:57:01 +0100187
Yohei Yukawa8ce2a532015-11-25 20:35:04 -0800188 InputManager im = context.getSystemService(InputManager.class);
Michael Wright9209c9c2015-09-03 17:57:01 +0100189 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();
Michael Wright875d45a2015-11-26 14:10:32 +0000213 } else if (mState == STATE_WAITING_FOR_BLUETOOTH) {
214 mUIHandler.sendEmptyMessage(MSG_DISMISS_BLUETOOTH_DIALOG);
Michael Wright9209c9c2015-09-03 17:57:01 +0100215 }
216 mState = STATE_WAITING_FOR_TABLET_MODE_EXIT;
217 return;
218 }
219
220 final int btState = mLocalBluetoothAdapter.getState();
Michael Wright875d45a2015-11-26 14:10:32 +0000221 if ((btState == BluetoothAdapter.STATE_TURNING_ON || btState == BluetoothAdapter.STATE_ON)
Michael Wright9209c9c2015-09-03 17:57:01 +0100222 && 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();
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -0700241 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();
Michael Wright9209c9c2015-09-03 17:57:01 +0100254 }
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);
Dmitry Torokhov365bf062015-11-05 17:40:32 -0800338
339 Message abortMsg = mHandler.obtainMessage(MSG_BLE_ABORT_SCAN, ++mScanAttempt, 0);
340 mHandler.sendMessageDelayed(abortMsg, BLUETOOTH_SCAN_TIMEOUT_MILLIS);
Michael Wright9209c9c2015-09-03 17:57:01 +0100341 }
342
343 private void stopScanning() {
344 if (mScanCallback != null) {
Michael Wright875d45a2015-11-26 14:10:32 +0000345 BluetoothLeScanner scanner = mLocalBluetoothAdapter.getBluetoothLeScanner();
346 if (scanner != null) {
347 scanner.stopScan(mScanCallback);
348 }
Michael Wright9209c9c2015-09-03 17:57:01 +0100349 mScanCallback = null;
350 }
351 }
352
353 // Should only be called on the handler thread
Dmitry Torokhov365bf062015-11-05 17:40:32 -0800354 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
Michael Wright9209c9c2015-09-03 17:57:01 +0100367 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) {
Michael Wright875d45a2015-11-26 14:10:32 +0000384 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 }
Michael Wright9209c9c2015-09-03 17:57:01 +0100392 }
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
Michael Wright875d45a2015-11-26 14:10:32 +0000403 // 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
Michael Wright9209c9c2015-09-03 17:57:01 +0100414 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: {
Michael Wright875d45a2015-11-26 14:10:32 +0000422 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();
Michael Wright9209c9c2015-09-03 17:57:01 +0100430 mDialog = new BluetoothDialog(mContext);
431 mDialog.setTitle(R.string.enable_bluetooth_title);
432 mDialog.setMessage(R.string.enable_bluetooth_message);
Michael Wright875d45a2015-11-26 14:10:32 +0000433 mDialog.setPositiveButton(
434 R.string.enable_bluetooth_confirmation_ok, clickListener);
435 mDialog.setNegativeButton(android.R.string.cancel, clickListener);
436 mDialog.setOnDismissListener(dismissListener);
Michael Wright9209c9c2015-09-03 17:57:01 +0100437 mDialog.show();
438 break;
439 }
440 case MSG_DISMISS_BLUETOOTH_DIALOG: {
441 if (mDialog != null) {
442 mDialog.dismiss();
Michael Wright9209c9c2015-09-03 17:57:01 +0100443 }
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 }
Dmitry Torokhov365bf062015-11-05 17:40:32 -0800477 break;
478 }
479 case MSG_BLE_ABORT_SCAN: {
480 int scanAttempt = msg.arg1;
481 bleAbortScanInternal(scanAttempt);
482 break;
Michael Wright9209c9c2015-09-03 17:57:01 +0100483 }
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 }
Michael Wright875d45a2015-11-26 14:10:32 +0000506 case MSG_SHOW_ERROR: {
507 Pair<Context, String> p = (Pair<Context, String>) msg.obj;
508 onShowErrorInternal(p.first, p.second, msg.arg1);
509 }
Michael Wright9209c9c2015-09-03 17:57:01 +0100510 }
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
Michael Wright875d45a2015-11-26 14:10:32 +0000523 private final class BluetoothDialogDismissListener
524 implements DialogInterface.OnDismissListener {
525 @Override
526 public void onDismiss(DialogInterface dialog) {
527 mDialog = null;
528 }
529 }
530
Michael Wright9209c9c2015-09-03 17:57:01 +0100531 private final class KeyboardScanCallback extends ScanCallback {
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -0700532
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
Michael Wright9209c9c2015-09-03 17:57:01 +0100541 @Override
542 public void onBatchScanResults(List<ScanResult> results) {
543 if (DEBUG) {
544 Slog.d(TAG, "onBatchScanResults(" + results.size() + ")");
545 }
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -0700546
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);
Michael Wright9209c9c2015-09-03 17:57:01 +0100553 }
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -0700554
555 if (isDeviceDiscoverable(result) && result.getRssi() > bestRssi) {
556 bestDevice = result.getDevice();
557 bestRssi = result.getRssi();
558 }
559 }
560
561 if (bestDevice != null) {
Michael Wright9209c9c2015-09-03 17:57:01 +0100562 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 }
Dmitry Torokhov79f00cf2015-10-22 10:07:53 -0700579
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 }
Michael Wright9209c9c2015-09-03 17:57:01 +0100587 }
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
Michael Wright875d45a2015-11-26 14:10:32 +0000613 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
Michael Wright9209c9c2015-09-03 17:57:01 +0100620 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";
Michael Wright9209c9c2015-09-03 17:57:01 +0100632 case STATE_PAIRING:
633 return "STATE_PAIRING";
634 case STATE_PAIRED:
635 return "STATE_PAIRED";
Michael Wright875d45a2015-11-26 14:10:32 +0000636 case STATE_PAIRING_FAILED:
637 return "STATE_PAIRING_FAILED";
Michael Wright9209c9c2015-09-03 17:57:01 +0100638 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:
Michael Wright875d45a2015-11-26 14:10:32 +0000644 return "STATE_UNKNOWN (" + state + ")";
Michael Wright9209c9c2015-09-03 17:57:01 +0100645 }
646 }
647}