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