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