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