• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.bluetooth.btservice;
18 
19 import android.bluetooth.BluetoothAdapter;
20 import android.bluetooth.BluetoothClass;
21 import android.bluetooth.BluetoothDevice;
22 import android.bluetooth.BluetoothProfile;
23 import android.bluetooth.OobData;
24 import android.content.Intent;
25 import android.os.Message;
26 import android.os.UserHandle;
27 import android.util.Log;
28 
29 import com.android.bluetooth.Utils;
30 import com.android.bluetooth.a2dp.A2dpService;
31 import com.android.bluetooth.a2dpsink.A2dpSinkService;
32 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
33 import com.android.bluetooth.hfp.HeadsetService;
34 import com.android.bluetooth.hfpclient.HeadsetClientService;
35 import com.android.bluetooth.hid.HidHostService;
36 import com.android.bluetooth.pbapclient.PbapClientService;
37 import com.android.internal.annotations.VisibleForTesting;
38 import com.android.internal.util.State;
39 import com.android.internal.util.StateMachine;
40 
41 import java.util.ArrayList;
42 import java.util.HashSet;
43 import java.util.Set;
44 
45 /**
46  * This state machine handles Bluetooth Adapter State.
47  * States:
48  *      {@link StableState} :  No device is in bonding / unbonding state.
49  *      {@link PendingCommandState} : Some device is in bonding / unbonding state.
50  * TODO(BT) This class can be removed and this logic moved to the stack.
51  */
52 
53 final class BondStateMachine extends StateMachine {
54     private static final boolean DBG = false;
55     private static final String TAG = "BluetoothBondStateMachine";
56 
57     static final int CREATE_BOND = 1;
58     static final int CANCEL_BOND = 2;
59     static final int REMOVE_BOND = 3;
60     static final int BONDING_STATE_CHANGE = 4;
61     static final int SSP_REQUEST = 5;
62     static final int PIN_REQUEST = 6;
63     static final int UUID_UPDATE = 10;
64     static final int BOND_STATE_NONE = 0;
65     static final int BOND_STATE_BONDING = 1;
66     static final int BOND_STATE_BONDED = 2;
67 
68     private AdapterService mAdapterService;
69     private AdapterProperties mAdapterProperties;
70     private RemoteDevices mRemoteDevices;
71     private BluetoothAdapter mAdapter;
72 
73     private PendingCommandState mPendingCommandState = new PendingCommandState();
74     private StableState mStableState = new StableState();
75 
76     public static final String OOBDATA = "oobdata";
77 
78     @VisibleForTesting Set<BluetoothDevice> mPendingBondedDevices = new HashSet<>();
79 
BondStateMachine(AdapterService service, AdapterProperties prop, RemoteDevices remoteDevices)80     private BondStateMachine(AdapterService service, AdapterProperties prop,
81             RemoteDevices remoteDevices) {
82         super("BondStateMachine:");
83         addState(mStableState);
84         addState(mPendingCommandState);
85         mRemoteDevices = remoteDevices;
86         mAdapterService = service;
87         mAdapterProperties = prop;
88         mAdapter = BluetoothAdapter.getDefaultAdapter();
89         setInitialState(mStableState);
90     }
91 
make(AdapterService service, AdapterProperties prop, RemoteDevices remoteDevices)92     public static BondStateMachine make(AdapterService service, AdapterProperties prop,
93             RemoteDevices remoteDevices) {
94         Log.d(TAG, "make");
95         BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices);
96         bsm.start();
97         return bsm;
98     }
99 
doQuit()100     public void doQuit() {
101         quitNow();
102     }
103 
cleanup()104     private void cleanup() {
105         mAdapterService = null;
106         mRemoteDevices = null;
107         mAdapterProperties = null;
108     }
109 
110     @Override
onQuitting()111     protected void onQuitting() {
112         cleanup();
113     }
114 
115     private class StableState extends State {
116         @Override
enter()117         public void enter() {
118             infoLog("StableState(): Entering Off State");
119         }
120 
121         @Override
processMessage(Message msg)122         public boolean processMessage(Message msg) {
123 
124             BluetoothDevice dev = (BluetoothDevice) msg.obj;
125 
126             switch (msg.what) {
127 
128                 case CREATE_BOND:
129                     OobData oobData = null;
130                     if (msg.getData() != null) {
131                         oobData = msg.getData().getParcelable(OOBDATA);
132                     }
133 
134                     createBond(dev, msg.arg1, oobData, true);
135                     break;
136                 case REMOVE_BOND:
137                     removeBond(dev, true);
138                     break;
139                 case BONDING_STATE_CHANGE:
140                     int newState = msg.arg1;
141                 /* if incoming pairing, transition to pending state */
142                     if (newState == BluetoothDevice.BOND_BONDING) {
143                         sendIntent(dev, newState, 0);
144                         transitionTo(mPendingCommandState);
145                     } else if (newState == BluetoothDevice.BOND_NONE) {
146                     /* if the link key was deleted by the stack */
147                         sendIntent(dev, newState, 0);
148                     } else {
149                         Log.e(TAG, "In stable state, received invalid newState: "
150                                 + state2str(newState));
151                     }
152                     break;
153                 case UUID_UPDATE:
154                     if (mPendingBondedDevices.contains(dev)) {
155                         sendIntent(dev, BluetoothDevice.BOND_BONDED, 0);
156                     }
157                     break;
158                 case CANCEL_BOND:
159                 default:
160                     Log.e(TAG, "Received unhandled state: " + msg.what);
161                     return false;
162             }
163             return true;
164         }
165     }
166 
167 
168     private class PendingCommandState extends State {
169         private final ArrayList<BluetoothDevice> mDevices = new ArrayList<BluetoothDevice>();
170 
171         @Override
enter()172         public void enter() {
173             infoLog("Entering PendingCommandState State");
174             BluetoothDevice dev = (BluetoothDevice) getCurrentMessage().obj;
175         }
176 
177         @Override
processMessage(Message msg)178         public boolean processMessage(Message msg) {
179             BluetoothDevice dev = (BluetoothDevice) msg.obj;
180             DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
181             boolean result = false;
182             if (mDevices.contains(dev) && msg.what != CANCEL_BOND
183                     && msg.what != BONDING_STATE_CHANGE && msg.what != SSP_REQUEST
184                     && msg.what != PIN_REQUEST) {
185                 deferMessage(msg);
186                 return true;
187             }
188 
189             switch (msg.what) {
190                 case CREATE_BOND:
191                     OobData oobData = null;
192                     if (msg.getData() != null) {
193                         oobData = msg.getData().getParcelable(OOBDATA);
194                     }
195 
196                     result = createBond(dev, msg.arg1, oobData, false);
197                     break;
198                 case REMOVE_BOND:
199                     result = removeBond(dev, false);
200                     break;
201                 case CANCEL_BOND:
202                     result = cancelBond(dev);
203                     break;
204                 case BONDING_STATE_CHANGE:
205                     int newState = msg.arg1;
206                     int reason = getUnbondReasonFromHALCode(msg.arg2);
207                     sendIntent(dev, newState, reason);
208                     if (newState != BluetoothDevice.BOND_BONDING) {
209                         /* this is either none/bonded, remove and transition */
210                         result = !mDevices.remove(dev);
211                         if (mDevices.isEmpty()) {
212                             // Whenever mDevices is empty, then we need to
213                             // set result=false. Else, we will end up adding
214                             // the device to the list again. This prevents us
215                             // from pairing with a device that we just unpaired
216                             result = false;
217                             transitionTo(mStableState);
218                         }
219                         if (newState == BluetoothDevice.BOND_NONE) {
220                             mAdapterService.setPhonebookAccessPermission(dev,
221                                     BluetoothDevice.ACCESS_UNKNOWN);
222                             mAdapterService.setMessageAccessPermission(dev,
223                                     BluetoothDevice.ACCESS_UNKNOWN);
224                             mAdapterService.setSimAccessPermission(dev,
225                                     BluetoothDevice.ACCESS_UNKNOWN);
226                             // Set the profile Priorities to undefined
227                             clearProfilePriority(dev);
228                         }
229                     } else if (!mDevices.contains(dev)) {
230                         result = true;
231                     }
232                     break;
233                 case SSP_REQUEST:
234                     int passkey = msg.arg1;
235                     int variant = msg.arg2;
236                     sendDisplayPinIntent(devProp.getAddress(), passkey, variant);
237                     break;
238                 case PIN_REQUEST:
239                     BluetoothClass btClass = dev.getBluetoothClass();
240                     int btDeviceClass = btClass.getDeviceClass();
241                     if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || btDeviceClass
242                             == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
243                         // Its a keyboard. Follow the HID spec recommendation of creating the
244                         // passkey and displaying it to the user. If the keyboard doesn't follow
245                         // the spec recommendation, check if the keyboard has a fixed PIN zero
246                         // and pair.
247                         //TODO: Maintain list of devices that have fixed pin
248                         // Generate a variable 6-digit PIN in range of 100000-999999
249                         // This is not truly random but good enough.
250                         int pin = 100000 + (int) Math.floor((Math.random() * (999999 - 100000)));
251                         sendDisplayPinIntent(devProp.getAddress(), pin,
252                                 BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
253                         break;
254                     }
255 
256                     if (msg.arg2 == 1) { // Minimum 16 digit pin required here
257                         sendDisplayPinIntent(devProp.getAddress(), 0,
258                                 BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS);
259                     } else {
260                         // In PIN_REQUEST, there is no passkey to display.So do not send the
261                         // EXTRA_PAIRING_KEY type in the intent( 0 in SendDisplayPinIntent() )
262                         sendDisplayPinIntent(devProp.getAddress(), 0,
263                                 BluetoothDevice.PAIRING_VARIANT_PIN);
264                     }
265 
266                     break;
267                 default:
268                     Log.e(TAG, "Received unhandled event:" + msg.what);
269                     return false;
270             }
271             if (result) {
272                 mDevices.add(dev);
273             }
274 
275             return true;
276         }
277     }
278 
cancelBond(BluetoothDevice dev)279     private boolean cancelBond(BluetoothDevice dev) {
280         if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
281             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
282             if (!mAdapterService.cancelBondNative(addr)) {
283                 Log.e(TAG, "Unexpected error while cancelling bond:");
284             } else {
285                 return true;
286             }
287         }
288         return false;
289     }
290 
removeBond(BluetoothDevice dev, boolean transition)291     private boolean removeBond(BluetoothDevice dev, boolean transition) {
292         if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
293             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
294             if (!mAdapterService.removeBondNative(addr)) {
295                 Log.e(TAG, "Unexpected error while removing bond:");
296             } else {
297                 if (transition) {
298                     transitionTo(mPendingCommandState);
299                 }
300                 return true;
301             }
302 
303         }
304         return false;
305     }
306 
createBond(BluetoothDevice dev, int transport, OobData oobData, boolean transition)307     private boolean createBond(BluetoothDevice dev, int transport, OobData oobData,
308             boolean transition) {
309         if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
310             infoLog("Bond address is:" + dev);
311             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
312             boolean result;
313             if (oobData != null) {
314                 result = mAdapterService.createBondOutOfBandNative(addr, transport, oobData);
315             } else {
316                 result = mAdapterService.createBondNative(addr, transport);
317             }
318 
319             if (!result) {
320                 sendIntent(dev, BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED);
321                 return false;
322             } else if (transition) {
323                 transitionTo(mPendingCommandState);
324             }
325             return true;
326         }
327         return false;
328     }
329 
sendDisplayPinIntent(byte[] address, int pin, int variant)330     private void sendDisplayPinIntent(byte[] address, int pin, int variant) {
331         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
332         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address));
333         if (pin != 0) {
334             intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
335         }
336         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant);
337         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
338         // Workaround for Android Auto until pre-accepting pairing requests is added.
339         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
340         mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
341     }
342 
343     @VisibleForTesting
sendIntent(BluetoothDevice device, int newState, int reason)344     void sendIntent(BluetoothDevice device, int newState, int reason) {
345         DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
346         int oldState = BluetoothDevice.BOND_NONE;
347         if (newState != BluetoothDevice.BOND_NONE
348                 && newState != BluetoothDevice.BOND_BONDING
349                 && newState != BluetoothDevice.BOND_BONDED) {
350             infoLog("Invalid bond state " + newState);
351             return;
352         }
353         if (devProp != null) {
354             oldState = devProp.getBondState();
355         }
356         if (mPendingBondedDevices.contains(device)) {
357             mPendingBondedDevices.remove(device);
358             if (oldState == BluetoothDevice.BOND_BONDED) {
359                 if (newState == BluetoothDevice.BOND_BONDING) {
360                     mAdapterProperties.onBondStateChanged(device, newState);
361                 }
362                 oldState = BluetoothDevice.BOND_BONDING;
363             } else {
364                 // Should not enter here.
365                 throw new IllegalArgumentException("Invalid old state " + oldState);
366             }
367         }
368         if (oldState == newState) {
369             return;
370         }
371 
372         mAdapterProperties.onBondStateChanged(device, newState);
373 
374         if (devProp != null && ((devProp.getDeviceType() == BluetoothDevice.DEVICE_TYPE_CLASSIC
375                 || devProp.getDeviceType() == BluetoothDevice.DEVICE_TYPE_DUAL)
376                 && newState == BluetoothDevice.BOND_BONDED && devProp.getUuids() == null)) {
377             infoLog(device + " is bonded, wait for SDP complete to broadcast bonded intent");
378             if (!mPendingBondedDevices.contains(device)) {
379                 mPendingBondedDevices.add(device);
380             }
381             if (oldState == BluetoothDevice.BOND_NONE) {
382                 // Broadcast NONE->BONDING for NONE->BONDED case.
383                 newState = BluetoothDevice.BOND_BONDING;
384             } else {
385                 return;
386             }
387         }
388 
389         Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
390         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
391         intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
392         intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
393         if (newState == BluetoothDevice.BOND_NONE) {
394             intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
395         }
396         mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, AdapterService.BLUETOOTH_PERM);
397         infoLog("Bond State Change Intent:" + device + " " + state2str(oldState) + " => "
398                 + state2str(newState));
399     }
400 
bondStateChangeCallback(int status, byte[] address, int newState)401     void bondStateChangeCallback(int status, byte[] address, int newState) {
402         BluetoothDevice device = mRemoteDevices.getDevice(address);
403 
404         if (device == null) {
405             infoLog("No record of the device:" + device);
406             // This device will be added as part of the BONDING_STATE_CHANGE intent processing
407             // in sendIntent above
408             device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
409         }
410 
411         infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device + " newState: "
412                 + newState);
413 
414         Message msg = obtainMessage(BONDING_STATE_CHANGE);
415         msg.obj = device;
416 
417         if (newState == BOND_STATE_BONDED) {
418             msg.arg1 = BluetoothDevice.BOND_BONDED;
419         } else if (newState == BOND_STATE_BONDING) {
420             msg.arg1 = BluetoothDevice.BOND_BONDING;
421         } else {
422             msg.arg1 = BluetoothDevice.BOND_NONE;
423         }
424         msg.arg2 = status;
425 
426         sendMessage(msg);
427     }
428 
sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant, int passkey)429     void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant, int passkey) {
430         //TODO(BT): Get wakelock and update name and cod
431         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
432         if (bdDevice == null) {
433             mRemoteDevices.addDeviceProperties(address);
434         }
435         infoLog("sspRequestCallback: " + address + " name: " + name + " cod: " + cod
436                 + " pairingVariant " + pairingVariant + " passkey: " + passkey);
437         int variant;
438         boolean displayPasskey = false;
439         switch (pairingVariant) {
440 
441             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION:
442                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION;
443                 displayPasskey = true;
444                 break;
445 
446             case AbstractionLayer.BT_SSP_VARIANT_CONSENT:
447                 variant = BluetoothDevice.PAIRING_VARIANT_CONSENT;
448                 break;
449 
450             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY:
451                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY;
452                 break;
453 
454             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION:
455                 variant = BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY;
456                 displayPasskey = true;
457                 break;
458 
459             default:
460                 errorLog("SSP Pairing variant not present");
461                 return;
462         }
463         BluetoothDevice device = mRemoteDevices.getDevice(address);
464         if (device == null) {
465             warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address));
466             mRemoteDevices.addDeviceProperties(address);
467             device = mRemoteDevices.getDevice(address);
468         }
469 
470         Message msg = obtainMessage(SSP_REQUEST);
471         msg.obj = device;
472         if (displayPasskey) {
473             msg.arg1 = passkey;
474         }
475         msg.arg2 = variant;
476         sendMessage(msg);
477     }
478 
pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits)479     void pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits) {
480         //TODO(BT): Get wakelock and update name and cod
481 
482         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
483         if (bdDevice == null) {
484             mRemoteDevices.addDeviceProperties(address);
485         }
486         infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" + cod);
487 
488         Message msg = obtainMessage(PIN_REQUEST);
489         msg.obj = bdDevice;
490         msg.arg2 = min16Digits ? 1 : 0; // Use arg2 to pass the min16Digit boolean
491 
492         sendMessage(msg);
493     }
494 
clearProfilePriority(BluetoothDevice device)495     private void clearProfilePriority(BluetoothDevice device) {
496         HidHostService hidService = HidHostService.getHidHostService();
497         A2dpService a2dpService = A2dpService.getA2dpService();
498         HeadsetService headsetService = HeadsetService.getHeadsetService();
499         HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
500         A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
501         PbapClientService pbapClientService = PbapClientService.getPbapClientService();
502 
503         if (hidService != null) {
504             hidService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
505         }
506         if (a2dpService != null) {
507             a2dpService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
508         }
509         if (headsetService != null) {
510             headsetService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
511         }
512         if (headsetClientService != null) {
513             headsetClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
514         }
515         if (a2dpSinkService != null) {
516             a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
517         }
518         if (pbapClientService != null) {
519             pbapClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
520         }
521 
522         // Clear Absolute Volume black list
523         if (a2dpService != null) {
524             a2dpService.resetAvrcpBlacklist(device);
525         }
526     }
527 
state2str(int state)528     private String state2str(int state) {
529         if (state == BluetoothDevice.BOND_NONE) {
530             return "BOND_NONE";
531         } else if (state == BluetoothDevice.BOND_BONDING) {
532             return "BOND_BONDING";
533         } else if (state == BluetoothDevice.BOND_BONDED) {
534             return "BOND_BONDED";
535         } else return "UNKNOWN(" + state + ")";
536     }
537 
infoLog(String msg)538     private void infoLog(String msg) {
539         Log.i(TAG, msg);
540     }
541 
errorLog(String msg)542     private void errorLog(String msg) {
543         Log.e(TAG, msg);
544     }
545 
warnLog(String msg)546     private void warnLog(String msg) {
547         Log.w(TAG, msg);
548     }
549 
getUnbondReasonFromHALCode(int reason)550     private int getUnbondReasonFromHALCode(int reason) {
551         if (reason == AbstractionLayer.BT_STATUS_SUCCESS) {
552             return BluetoothDevice.BOND_SUCCESS;
553         } else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN) {
554             return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
555         } else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE) {
556             return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
557         } else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED) {
558             return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
559         } else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT) {
560             return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
561         }
562 
563         /* default */
564         return BluetoothDevice.UNBOND_REASON_REMOVED;
565     }
566 }
567