• 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
127                 {
128                     Log.e(TAG, "In stable state, received invalid newState: " + newState);
129                 }
130                 break;
131 
132               case CANCEL_BOND:
133               default:
134                    Log.e(TAG, "Received unhandled state: " + msg.what);
135                    return false;
136             }
137             return true;
138         }
139     }
140 
141 
142     private class PendingCommandState extends State {
143         private final ArrayList<BluetoothDevice> mDevices =
144             new ArrayList<BluetoothDevice>();
145 
146         @Override
enter()147         public void enter() {
148             infoLog("Entering PendingCommandState State");
149             BluetoothDevice dev = (BluetoothDevice)getCurrentMessage().obj;
150         }
151 
152         @Override
processMessage(Message msg)153         public boolean processMessage(Message msg) {
154 
155             BluetoothDevice dev = (BluetoothDevice)msg.obj;
156             DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
157             boolean result = false;
158              if (mDevices.contains(dev) && msg.what != CANCEL_BOND &&
159                    msg.what != BONDING_STATE_CHANGE && msg.what != SSP_REQUEST &&
160                    msg.what != PIN_REQUEST) {
161                  deferMessage(msg);
162                  return true;
163              }
164 
165             Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
166 
167             switch (msg.what) {
168                 case CREATE_BOND:
169                     result = createBond(dev, msg.arg1, false);
170                     break;
171                 case REMOVE_BOND:
172                     result = removeBond(dev, false);
173                     break;
174                 case CANCEL_BOND:
175                     result = cancelBond(dev);
176                     break;
177                 case BONDING_STATE_CHANGE:
178                     int newState = msg.arg1;
179                     int reason = getUnbondReasonFromHALCode(msg.arg2);
180                     sendIntent(dev, newState, reason);
181                     if(newState != BluetoothDevice.BOND_BONDING )
182                     {
183                         /* this is either none/bonded, remove and transition */
184                         result = !mDevices.remove(dev);
185                         if (mDevices.isEmpty()) {
186                             // Whenever mDevices is empty, then we need to
187                             // set result=false. Else, we will end up adding
188                             // the device to the list again. This prevents us
189                             // from pairing with a device that we just unpaired
190                             result = false;
191                             transitionTo(mStableState);
192                         }
193                         if (newState == BluetoothDevice.BOND_NONE)
194                         {
195                             mAdapterService.setPhonebookAccessPermission(dev,
196                                     BluetoothDevice.ACCESS_UNKNOWN);
197                             mAdapterService.setMessageAccessPermission(dev,
198                                     BluetoothDevice.ACCESS_UNKNOWN);
199                             // Set the profile Priorities to undefined
200                             clearProfilePriorty(dev);
201                         }
202                         else if (newState == BluetoothDevice.BOND_BONDED)
203                         {
204                            // Do not set profile priority
205                            // Profile priority should be set after SDP completion
206 
207                            // Restore the profile priorty settings
208                            //setProfilePriorty(dev);
209                         }
210                     }
211                     else if(!mDevices.contains(dev))
212                         result=true;
213                     break;
214                 case SSP_REQUEST:
215                     int passkey = msg.arg1;
216                     int variant = msg.arg2;
217                     sendDisplayPinIntent(devProp.getAddress(), passkey, variant);
218                     break;
219                 case PIN_REQUEST:
220                     BluetoothClass btClass = dev.getBluetoothClass();
221                     int btDeviceClass = btClass.getDeviceClass();
222                     if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD ||
223                          btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
224                         // Its a keyboard. Follow the HID spec recommendation of creating the
225                         // passkey and displaying it to the user. If the keyboard doesn't follow
226                         // the spec recommendation, check if the keyboard has a fixed PIN zero
227                         // and pair.
228                         //TODO: Maintain list of devices that have fixed pin
229                         // Generate a variable 6-digit PIN in range of 100000-999999
230                         // This is not truly random but good enough.
231                         int pin = 100000 + (int)Math.floor((Math.random() * (999999 - 100000)));
232                         sendDisplayPinIntent(devProp.getAddress(), pin,
233                                  BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
234                         break;
235                     }
236                     //In PIN_REQUEST, there is no passkey to display.So do not send the
237                     //EXTRA_PAIRING_KEY type in the intent( 0 in SendDisplayPinIntent() )
238                     sendDisplayPinIntent(devProp.getAddress(), 0,
239                                           BluetoothDevice.PAIRING_VARIANT_PIN);
240 
241                     break;
242                 default:
243                     Log.e(TAG, "Received unhandled event:" + msg.what);
244                     return false;
245             }
246             if (result) mDevices.add(dev);
247 
248             return true;
249         }
250     }
251 
cancelBond(BluetoothDevice dev)252     private boolean cancelBond(BluetoothDevice dev) {
253         if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
254             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
255             if (!mAdapterService.cancelBondNative(addr)) {
256                Log.e(TAG, "Unexpected error while cancelling bond:");
257             } else {
258                 return true;
259             }
260         }
261         return false;
262     }
263 
removeBond(BluetoothDevice dev, boolean transition)264     private boolean removeBond(BluetoothDevice dev, boolean transition) {
265         if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
266             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
267             if (!mAdapterService.removeBondNative(addr)) {
268                Log.e(TAG, "Unexpected error while removing bond:");
269             } else {
270                 if (transition) transitionTo(mPendingCommandState);
271                 return true;
272             }
273 
274         }
275         return false;
276     }
277 
createBond(BluetoothDevice dev, int transport, boolean transition)278     private boolean createBond(BluetoothDevice dev, int transport, boolean transition) {
279         if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
280             infoLog("Bond address is:" + dev);
281             byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
282             if (!mAdapterService.createBondNative(addr, transport)) {
283                 sendIntent(dev, BluetoothDevice.BOND_NONE,
284                            BluetoothDevice.UNBOND_REASON_REMOVED);
285                 return false;
286             } else if (transition) {
287                 transitionTo(mPendingCommandState);
288             }
289             return true;
290         }
291         return false;
292     }
293 
sendDisplayPinIntent(byte[] address, int pin, int variant)294     private void sendDisplayPinIntent(byte[] address, int pin, int variant) {
295         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
296         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address));
297         if (pin != 0) {
298             intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
299         }
300         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant);
301         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
302         mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
303     }
304 
sendIntent(BluetoothDevice device, int newState, int reason)305     private void sendIntent(BluetoothDevice device, int newState, int reason) {
306         DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
307         int oldState = BluetoothDevice.BOND_NONE;
308         if (devProp != null) {
309             oldState = devProp.getBondState();
310         }
311         if (oldState == newState) return;
312         mAdapterProperties.onBondStateChanged(device, newState);
313 
314         Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
315         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
316         intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
317         intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
318         if (newState == BluetoothDevice.BOND_NONE)
319             intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
320         mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
321                 AdapterService.BLUETOOTH_PERM);
322         infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
323                 + " NewState: " + newState);
324     }
325 
bondStateChangeCallback(int status, byte[] address, int newState)326     void bondStateChangeCallback(int status, byte[] address, int newState) {
327         BluetoothDevice device = mRemoteDevices.getDevice(address);
328 
329         if (device == null) {
330             infoLog("No record of the device:" + device);
331             // This device will be added as part of the BONDING_STATE_CHANGE intent processing
332             // in sendIntent above
333             device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
334         }
335 
336         infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device
337                 + " newState: " + newState);
338 
339         Message msg = obtainMessage(BONDING_STATE_CHANGE);
340         msg.obj = device;
341 
342         if (newState == BOND_STATE_BONDED)
343             msg.arg1 = BluetoothDevice.BOND_BONDED;
344         else if (newState == BOND_STATE_BONDING)
345             msg.arg1 = BluetoothDevice.BOND_BONDING;
346         else
347             msg.arg1 = BluetoothDevice.BOND_NONE;
348         msg.arg2 = status;
349 
350         sendMessage(msg);
351     }
352 
sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant, int passkey)353     void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
354             int passkey) {
355         //TODO(BT): Get wakelock and update name and cod
356         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
357         if (bdDevice == null) {
358             mRemoteDevices.addDeviceProperties(address);
359         }
360         infoLog("sspRequestCallback: " + address + " name: " + name + " cod: " +
361                 cod + " pairingVariant " + pairingVariant + " passkey: " + passkey);
362         int variant;
363         boolean displayPasskey = false;
364         switch(pairingVariant) {
365 
366             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION :
367                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION;
368                 displayPasskey = true;
369             break;
370 
371             case AbstractionLayer.BT_SSP_VARIANT_CONSENT :
372                 variant = BluetoothDevice.PAIRING_VARIANT_CONSENT;
373             break;
374 
375             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY :
376                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY;
377             break;
378 
379             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION :
380                 variant = BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY;
381                 displayPasskey = true;
382             break;
383 
384             default:
385                 errorLog("SSP Pairing variant not present");
386                 return;
387         }
388         BluetoothDevice device = mRemoteDevices.getDevice(address);
389         if (device == null) {
390            warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address));
391            mRemoteDevices.addDeviceProperties(address);
392            device = mRemoteDevices.getDevice(address);
393         }
394 
395         Message msg = obtainMessage(SSP_REQUEST);
396         msg.obj = device;
397         if(displayPasskey)
398             msg.arg1 = passkey;
399         msg.arg2 = variant;
400         sendMessage(msg);
401     }
402 
pinRequestCallback(byte[] address, byte[] name, int cod)403     void pinRequestCallback(byte[] address, byte[] name, int cod) {
404         //TODO(BT): Get wakelock and update name and cod
405         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
406         if (bdDevice == null) {
407             mRemoteDevices.addDeviceProperties(address);
408         }
409         infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" +
410                 cod);
411 
412         Message msg = obtainMessage(PIN_REQUEST);
413         msg.obj = bdDevice;
414 
415         sendMessage(msg);
416     }
417 
setProfilePriorty(BluetoothDevice device)418     private void setProfilePriorty (BluetoothDevice device){
419         HidService hidService = HidService.getHidService();
420         A2dpService a2dpService = A2dpService.getA2dpService();
421         HeadsetService headsetService = HeadsetService.getHeadsetService();
422 
423         if ((hidService != null) &&
424             (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
425             hidService.setPriority(device,BluetoothProfile.PRIORITY_ON);
426         }
427 
428         if ((a2dpService != null) &&
429             (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
430             a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON);
431         }
432 
433         if ((headsetService != null) &&
434             (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
435             headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
436         }
437     }
438 
clearProfilePriorty(BluetoothDevice device)439     private void clearProfilePriorty (BluetoothDevice device){
440         HidService hidService = HidService.getHidService();
441         A2dpService a2dpService = A2dpService.getA2dpService();
442         HeadsetService headsetService = HeadsetService.getHeadsetService();
443 
444         if (hidService != null)
445             hidService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
446         if(a2dpService != null)
447             a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
448         if(headsetService != null)
449             headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
450     }
451 
infoLog(String msg)452     private void infoLog(String msg) {
453         Log.i(TAG, msg);
454     }
455 
errorLog(String msg)456     private void errorLog(String msg) {
457         Log.e(TAG, msg);
458     }
459 
warnLog(String msg)460     private void warnLog(String msg) {
461         Log.w(TAG, msg);
462     }
463 
getUnbondReasonFromHALCode(int reason)464     private int getUnbondReasonFromHALCode (int reason) {
465         if (reason == AbstractionLayer.BT_STATUS_SUCCESS)
466             return BluetoothDevice.BOND_SUCCESS;
467         else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN)
468             return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
469         else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
470             return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
471         else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED)
472             return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
473         else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT)
474             return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
475 
476         /* default */
477         return BluetoothDevice.UNBOND_REASON_REMOVED;
478     }
479 }
480