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 com.android.bluetooth.a2dpsink.A2dpSinkService;
27 import com.android.bluetooth.hfpclient.HeadsetClientService;
28 import com.android.bluetooth.pbapclient.PbapClientService;
29 
30 import android.bluetooth.OobData;
31 import android.content.Intent;
32 import android.os.Message;
33 import android.os.UserHandle;
34 import android.util.Log;
35 
36 import com.android.bluetooth.R;
37 import com.android.bluetooth.Utils;
38 import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
39 import com.android.internal.util.State;
40 import com.android.internal.util.StateMachine;
41 
42 import java.util.ArrayList;
43 
44 /**
45  * This state machine handles Bluetooth Adapter State.
46  * States:
47  *      {@link StableState} :  No device is in bonding / unbonding state.
48  *      {@link PendingCommandState} : Some device is in bonding / unbonding state.
49  * TODO(BT) This class can be removed and this logic moved to the stack.
50  */
51 
52 final class BondStateMachine extends StateMachine {
53     private static final boolean DBG = false;
54     private static final String TAG = "BluetoothBondStateMachine";
55 
56     static final int CREATE_BOND = 1;
57     static final int CANCEL_BOND = 2;
58     static final int REMOVE_BOND = 3;
59     static final int BONDING_STATE_CHANGE = 4;
60     static final int SSP_REQUEST = 5;
61     static final int PIN_REQUEST = 6;
62     static final int BOND_STATE_NONE = 0;
63     static final int BOND_STATE_BONDING = 1;
64     static final int BOND_STATE_BONDED = 2;
65 
66     private AdapterService mAdapterService;
67     private AdapterProperties mAdapterProperties;
68     private RemoteDevices mRemoteDevices;
69     private BluetoothAdapter mAdapter;
70 
71     private PendingCommandState mPendingCommandState = new PendingCommandState();
72     private StableState mStableState = new StableState();
73 
74     public static final String OOBDATA = "oobdata";
75 
BondStateMachine(AdapterService service, AdapterProperties prop, RemoteDevices remoteDevices)76     private BondStateMachine(AdapterService service,
77             AdapterProperties prop, RemoteDevices remoteDevices) {
78         super("BondStateMachine:");
79         addState(mStableState);
80         addState(mPendingCommandState);
81         mRemoteDevices = remoteDevices;
82         mAdapterService = service;
83         mAdapterProperties = prop;
84         mAdapter = BluetoothAdapter.getDefaultAdapter();
85         setInitialState(mStableState);
86     }
87 
make(AdapterService service, AdapterProperties prop, RemoteDevices remoteDevices)88     public static BondStateMachine make(AdapterService service,
89             AdapterProperties prop, RemoteDevices remoteDevices) {
90         Log.d(TAG, "make");
91         BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices);
92         bsm.start();
93         return bsm;
94     }
95 
doQuit()96     public void doQuit() {
97         quitNow();
98     }
99 
cleanup()100     public void cleanup() {
101         mAdapterService = null;
102         mRemoteDevices = null;
103         mAdapterProperties = null;
104     }
105 
106     private class StableState extends State {
107         @Override
enter()108         public void enter() {
109             infoLog("StableState(): Entering Off State");
110         }
111 
112         @Override
processMessage(Message msg)113         public boolean processMessage(Message msg) {
114 
115             BluetoothDevice dev = (BluetoothDevice)msg.obj;
116 
117             switch(msg.what) {
118 
119               case CREATE_BOND:
120                   OobData oobData = null;
121                   if (msg.getData() != null)
122                       oobData = msg.getData().getParcelable(OOBDATA);
123 
124                   createBond(dev, msg.arg1, oobData, true);
125                   break;
126               case REMOVE_BOND:
127                   removeBond(dev, true);
128                   break;
129               case BONDING_STATE_CHANGE:
130                 int newState = msg.arg1;
131                 /* if incoming pairing, transition to pending state */
132                 if (newState == BluetoothDevice.BOND_BONDING)
133                 {
134                     sendIntent(dev, newState, 0);
135                     transitionTo(mPendingCommandState);
136                 }
137                 else if (newState == BluetoothDevice.BOND_NONE)
138                 {
139                     /* if the link key was deleted by the stack */
140                     sendIntent(dev, newState, 0);
141                 }
142                 else
143                 {
144                     Log.e(TAG, "In stable state, received invalid newState: " + newState);
145                 }
146                 break;
147 
148               case CANCEL_BOND:
149               default:
150                    Log.e(TAG, "Received unhandled state: " + msg.what);
151                    return false;
152             }
153             return true;
154         }
155     }
156 
157 
158     private class PendingCommandState extends State {
159         private final ArrayList<BluetoothDevice> mDevices =
160             new ArrayList<BluetoothDevice>();
161 
162         @Override
enter()163         public void enter() {
164             infoLog("Entering PendingCommandState State");
165             BluetoothDevice dev = (BluetoothDevice)getCurrentMessage().obj;
166         }
167 
168         @Override
processMessage(Message msg)169         public boolean processMessage(Message msg) {
170             BluetoothDevice dev = (BluetoothDevice)msg.obj;
171             DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev);
172             boolean result = false;
173              if (mDevices.contains(dev) && msg.what != CANCEL_BOND &&
174                    msg.what != BONDING_STATE_CHANGE && msg.what != SSP_REQUEST &&
175                    msg.what != PIN_REQUEST) {
176                  deferMessage(msg);
177                  return true;
178              }
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         // Workaround for Android Auto until pre-accepting pairing requests is added.
328         intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
329         mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
330     }
331 
sendIntent(BluetoothDevice device, int newState, int reason)332     private void sendIntent(BluetoothDevice device, int newState, int reason) {
333         DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
334         int oldState = BluetoothDevice.BOND_NONE;
335         if (devProp != null) {
336             oldState = devProp.getBondState();
337         }
338         if (oldState == newState) return;
339         mAdapterProperties.onBondStateChanged(device, newState);
340 
341         Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
342         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
343         intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
344         intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
345         if (newState == BluetoothDevice.BOND_NONE)
346             intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
347         mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
348                 AdapterService.BLUETOOTH_PERM);
349         infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
350                 + " NewState: " + newState);
351     }
352 
bondStateChangeCallback(int status, byte[] address, int newState)353     void bondStateChangeCallback(int status, byte[] address, int newState) {
354         BluetoothDevice device = mRemoteDevices.getDevice(address);
355 
356         if (device == null) {
357             infoLog("No record of the device:" + device);
358             // This device will be added as part of the BONDING_STATE_CHANGE intent processing
359             // in sendIntent above
360             device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
361         }
362 
363         infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device
364                 + " newState: " + newState);
365 
366         Message msg = obtainMessage(BONDING_STATE_CHANGE);
367         msg.obj = device;
368 
369         if (newState == BOND_STATE_BONDED)
370             msg.arg1 = BluetoothDevice.BOND_BONDED;
371         else if (newState == BOND_STATE_BONDING)
372             msg.arg1 = BluetoothDevice.BOND_BONDING;
373         else
374             msg.arg1 = BluetoothDevice.BOND_NONE;
375         msg.arg2 = status;
376 
377         sendMessage(msg);
378     }
379 
sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant, int passkey)380     void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
381             int passkey) {
382         //TODO(BT): Get wakelock and update name and cod
383         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
384         if (bdDevice == null) {
385             mRemoteDevices.addDeviceProperties(address);
386         }
387         infoLog("sspRequestCallback: " + address + " name: " + name + " cod: " +
388                 cod + " pairingVariant " + pairingVariant + " passkey: " + passkey);
389         int variant;
390         boolean displayPasskey = false;
391         switch(pairingVariant) {
392 
393             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION :
394                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION;
395                 displayPasskey = true;
396             break;
397 
398             case AbstractionLayer.BT_SSP_VARIANT_CONSENT :
399                 variant = BluetoothDevice.PAIRING_VARIANT_CONSENT;
400             break;
401 
402             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY :
403                 variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY;
404             break;
405 
406             case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION :
407                 variant = BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY;
408                 displayPasskey = true;
409             break;
410 
411             default:
412                 errorLog("SSP Pairing variant not present");
413                 return;
414         }
415         BluetoothDevice device = mRemoteDevices.getDevice(address);
416         if (device == null) {
417            warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address));
418            mRemoteDevices.addDeviceProperties(address);
419            device = mRemoteDevices.getDevice(address);
420         }
421 
422         Message msg = obtainMessage(SSP_REQUEST);
423         msg.obj = device;
424         if(displayPasskey)
425             msg.arg1 = passkey;
426         msg.arg2 = variant;
427         sendMessage(msg);
428     }
429 
pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits)430     void pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits) {
431         //TODO(BT): Get wakelock and update name and cod
432 
433         BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
434         if (bdDevice == null) {
435             mRemoteDevices.addDeviceProperties(address);
436         }
437         infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" +
438                 cod);
439 
440         Message msg = obtainMessage(PIN_REQUEST);
441         msg.obj = bdDevice;
442         msg.arg2 = min16Digits ? 1 : 0; // Use arg2 to pass the min16Digit boolean
443 
444         sendMessage(msg);
445     }
446 
clearProfilePriority(BluetoothDevice device)447     private void clearProfilePriority(BluetoothDevice device) {
448         HidService hidService = HidService.getHidService();
449         A2dpService a2dpService = A2dpService.getA2dpService();
450         HeadsetService headsetService = HeadsetService.getHeadsetService();
451         HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
452         A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService();
453         PbapClientService pbapClientService = PbapClientService.getPbapClientService();
454 
455         if (hidService != null)
456             hidService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
457         if (a2dpService != null)
458             a2dpService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
459         if (headsetService != null)
460             headsetService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
461         if (headsetClientService != null)
462             headsetClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
463         if (a2dpSinkService != null)
464             a2dpSinkService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
465         if (pbapClientService != null)
466             pbapClientService.setPriority(device, BluetoothProfile.PRIORITY_UNDEFINED);
467 
468         // Clear Absolute Volume black list
469         if(a2dpService != null)
470             a2dpService.resetAvrcpBlacklist(device);
471     }
472 
infoLog(String msg)473     private void infoLog(String msg) {
474         Log.i(TAG, msg);
475     }
476 
errorLog(String msg)477     private void errorLog(String msg) {
478         Log.e(TAG, msg);
479     }
480 
warnLog(String msg)481     private void warnLog(String msg) {
482         Log.w(TAG, msg);
483     }
484 
getUnbondReasonFromHALCode(int reason)485     private int getUnbondReasonFromHALCode (int reason) {
486         if (reason == AbstractionLayer.BT_STATUS_SUCCESS)
487             return BluetoothDevice.BOND_SUCCESS;
488         else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN)
489             return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
490         else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
491             return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
492         else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED)
493             return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
494         else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT)
495             return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
496 
497         /* default */
498         return BluetoothDevice.UNBOND_REASON_REMOVED;
499     }
500 }
501