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