BondStateMachine.java revision 914484bd8562ae75b062a669c89020773c8529b5
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
17package com.android.bluetooth.btservice;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothClass;
21import android.bluetooth.BluetoothProfile;
22import android.bluetooth.BluetoothDevice;
23import com.android.bluetooth.a2dp.A2dpService;
24import com.android.bluetooth.hid.HidService;
25import com.android.bluetooth.hfp.HeadsetService;
26
27import android.bluetooth.OobData;
28import android.content.Context;
29import android.content.Intent;
30import android.os.Message;
31import android.os.UserHandle;
32import android.util.Log;
33
34import com.android.bluetooth.Utils;
35import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
36import com.android.internal.util.State;
37import com.android.internal.util.StateMachine;
38
39import 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
49final 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
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
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
93    public void doQuit() {
94        quitNow();
95    }
96
97    public void cleanup() {
98        mAdapterService = null;
99        mRemoteDevices = null;
100        mAdapterProperties = null;
101    }
102
103    private class StableState extends State {
104        @Override
105        public void enter() {
106            infoLog("StableState(): Entering Off State");
107        }
108
109        @Override
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
160        public void enter() {
161            infoLog("Entering PendingCommandState State");
162            BluetoothDevice dev = (BluetoothDevice)getCurrentMessage().obj;
163        }
164
165        @Override
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                            clearProfilePriorty(dev);
220                        }
221                        else if (newState == BluetoothDevice.BOND_BONDED)
222                        {
223                           // Do not set profile priority
224                           // Profile priority should be set after SDP completion
225
226                           // Restore the profile priorty settings
227                           //setProfilePriorty(dev);
228                        }
229                    }
230                    else if(!mDevices.contains(dev))
231                        result=true;
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 ||
242                         btDeviceClass == 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) mDevices.add(dev);
272
273            return true;
274        }
275    }
276
277    private boolean cancelBond(BluetoothDevice dev) {
278        if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
279            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
280            if (!mAdapterService.cancelBondNative(addr)) {
281               Log.e(TAG, "Unexpected error while cancelling bond:");
282            } else {
283                return true;
284            }
285        }
286        return false;
287    }
288
289    private boolean removeBond(BluetoothDevice dev, boolean transition) {
290        if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
291            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
292            if (!mAdapterService.removeBondNative(addr)) {
293               Log.e(TAG, "Unexpected error while removing bond:");
294            } else {
295                if (transition) transitionTo(mPendingCommandState);
296                return true;
297            }
298
299        }
300        return false;
301    }
302
303    private boolean createBond(BluetoothDevice dev, int transport, OobData oobData,
304                               boolean transition) {
305        if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
306            infoLog("Bond address is:" + dev);
307            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
308            boolean result;
309            if (oobData != null) {
310                result = mAdapterService.createBondOutOfBandNative(addr, transport, oobData);
311            } else {
312                result = mAdapterService.createBondNative(addr, transport);
313            }
314
315            if (!result) {
316                sendIntent(dev, BluetoothDevice.BOND_NONE,
317                           BluetoothDevice.UNBOND_REASON_REMOVED);
318                return false;
319            } else if (transition) {
320                transitionTo(mPendingCommandState);
321            }
322            return true;
323        }
324        return false;
325    }
326
327    private void sendDisplayPinIntent(byte[] address, int pin, int variant) {
328        Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
329        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address));
330        if (pin != 0) {
331            intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
332        }
333        intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant);
334        intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
335        mAdapterService.sendOrderedBroadcast(intent, mAdapterService.BLUETOOTH_ADMIN_PERM);
336    }
337
338    private void sendIntent(BluetoothDevice device, int newState, int reason) {
339        DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
340        int oldState = BluetoothDevice.BOND_NONE;
341        if (devProp != null) {
342            oldState = devProp.getBondState();
343        }
344        if (oldState == newState) return;
345        mAdapterProperties.onBondStateChanged(device, newState);
346
347        Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
348        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
349        intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
350        intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
351        if (newState == BluetoothDevice.BOND_NONE)
352            intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
353        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
354                AdapterService.BLUETOOTH_PERM);
355        infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
356                + " NewState: " + newState);
357    }
358
359    void bondStateChangeCallback(int status, byte[] address, int newState) {
360        BluetoothDevice device = mRemoteDevices.getDevice(address);
361
362        if (device == null) {
363            infoLog("No record of the device:" + device);
364            // This device will be added as part of the BONDING_STATE_CHANGE intent processing
365            // in sendIntent above
366            device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
367        }
368
369        infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device
370                + " newState: " + newState);
371
372        Message msg = obtainMessage(BONDING_STATE_CHANGE);
373        msg.obj = device;
374
375        if (newState == BOND_STATE_BONDED)
376            msg.arg1 = BluetoothDevice.BOND_BONDED;
377        else if (newState == BOND_STATE_BONDING)
378            msg.arg1 = BluetoothDevice.BOND_BONDING;
379        else
380            msg.arg1 = BluetoothDevice.BOND_NONE;
381        msg.arg2 = status;
382
383        sendMessage(msg);
384    }
385
386    void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant,
387            int passkey) {
388        //TODO(BT): Get wakelock and update name and cod
389        BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
390        if (bdDevice == null) {
391            mRemoteDevices.addDeviceProperties(address);
392        }
393        infoLog("sspRequestCallback: " + address + " name: " + name + " cod: " +
394                cod + " pairingVariant " + pairingVariant + " passkey: " + passkey);
395        int variant;
396        boolean displayPasskey = false;
397        switch(pairingVariant) {
398
399            case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION :
400                variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION;
401                displayPasskey = true;
402            break;
403
404            case AbstractionLayer.BT_SSP_VARIANT_CONSENT :
405                variant = BluetoothDevice.PAIRING_VARIANT_CONSENT;
406            break;
407
408            case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY :
409                variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY;
410            break;
411
412            case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION :
413                variant = BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY;
414                displayPasskey = true;
415            break;
416
417            default:
418                errorLog("SSP Pairing variant not present");
419                return;
420        }
421        BluetoothDevice device = mRemoteDevices.getDevice(address);
422        if (device == null) {
423           warnLog("Device is not known for:" + Utils.getAddressStringFromByte(address));
424           mRemoteDevices.addDeviceProperties(address);
425           device = mRemoteDevices.getDevice(address);
426        }
427
428        Message msg = obtainMessage(SSP_REQUEST);
429        msg.obj = device;
430        if(displayPasskey)
431            msg.arg1 = passkey;
432        msg.arg2 = variant;
433        sendMessage(msg);
434    }
435
436    void pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits) {
437        //TODO(BT): Get wakelock and update name and cod
438
439        BluetoothDevice bdDevice = mRemoteDevices.getDevice(address);
440        if (bdDevice == null) {
441            mRemoteDevices.addDeviceProperties(address);
442        }
443        infoLog("pinRequestCallback: " + address + " name:" + name + " cod:" +
444                cod);
445
446        Message msg = obtainMessage(PIN_REQUEST);
447        msg.obj = bdDevice;
448        msg.arg2 = min16Digits ? 1 : 0; // Use arg2 to pass the min16Digit boolean
449
450        sendMessage(msg);
451    }
452
453    private void setProfilePriorty (BluetoothDevice device){
454        HidService hidService = HidService.getHidService();
455        A2dpService a2dpService = A2dpService.getA2dpService();
456        HeadsetService headsetService = HeadsetService.getHeadsetService();
457
458        if ((hidService != null) &&
459            (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
460            hidService.setPriority(device,BluetoothProfile.PRIORITY_ON);
461        }
462
463        if ((a2dpService != null) &&
464            (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
465            a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON);
466        }
467
468        if ((headsetService != null) &&
469            (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
470            headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
471        }
472    }
473
474    private void clearProfilePriorty (BluetoothDevice device){
475        HidService hidService = HidService.getHidService();
476        A2dpService a2dpService = A2dpService.getA2dpService();
477        HeadsetService headsetService = HeadsetService.getHeadsetService();
478
479        if (hidService != null)
480            hidService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
481        if(a2dpService != null)
482            a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
483        if(headsetService != null)
484            headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
485    }
486
487    private void infoLog(String msg) {
488        Log.i(TAG, msg);
489    }
490
491    private void errorLog(String msg) {
492        Log.e(TAG, msg);
493    }
494
495    private void warnLog(String msg) {
496        Log.w(TAG, msg);
497    }
498
499    private int getUnbondReasonFromHALCode (int reason) {
500        if (reason == AbstractionLayer.BT_STATUS_SUCCESS)
501            return BluetoothDevice.BOND_SUCCESS;
502        else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN)
503            return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
504        else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
505            return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
506        else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED)
507            return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
508        else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT)
509            return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
510
511        /* default */
512        return BluetoothDevice.UNBOND_REASON_REMOVED;
513    }
514}
515