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                            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
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
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
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
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
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
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
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
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
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
462    private void infoLog(String msg) {
463        Log.i(TAG, msg);
464    }
465
466    private void errorLog(String msg) {
467        Log.e(TAG, msg);
468    }
469
470    private void warnLog(String msg) {
471        Log.w(TAG, msg);
472    }
473
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