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