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