BondStateMachine.java revision bd704c741b8c523ad747214f6f0520ac3e2caf8f
1/*
2 * Copyright (C) 2012 Google Inc.
3 */
4
5package com.android.bluetooth.btservice;
6
7import android.bluetooth.BluetoothAdapter;
8import android.bluetooth.BluetoothProfile;
9import android.bluetooth.BluetoothDevice;
10import com.android.bluetooth.a2dp.A2dpService;
11import com.android.bluetooth.hid.HidService;
12import com.android.bluetooth.hfp.HeadsetService;
13import android.content.Context;
14import android.content.Intent;
15import android.os.Message;
16import android.os.UserHandle;
17import android.util.Log;
18
19import com.android.bluetooth.Utils;
20import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
21import com.android.internal.util.State;
22import com.android.internal.util.StateMachine;
23
24import java.util.ArrayList;
25
26/**
27 * This state machine handles Bluetooth Adapter State.
28 * States:
29 *      {@link StableState} :  No device is in bonding / unbonding state.
30 *      {@link PendingCommandState} : Some device is in bonding / unbonding state.
31 * TODO(BT) This class can be removed and this logic moved to the stack.
32 */
33
34final class BondStateMachine extends StateMachine {
35    private static final boolean DBG = false;
36    private static final String TAG = "BluetoothBondStateMachine";
37
38    static final int CREATE_BOND = 1;
39    static final int CANCEL_BOND = 2;
40    static final int REMOVE_BOND = 3;
41    static final int BONDING_STATE_CHANGE = 4;
42
43    static final int BOND_STATE_NONE = 0;
44    static final int BOND_STATE_BONDING = 1;
45    static final int BOND_STATE_BONDED = 2;
46
47    private AdapterService mAdapterService;
48    private AdapterProperties mAdapterProperties;
49    private RemoteDevices mRemoteDevices;
50    private BluetoothAdapter mAdapter;
51
52    private PendingCommandState mPendingCommandState = new PendingCommandState();
53    private StableState mStableState = new StableState();
54
55    private BondStateMachine(AdapterService service,
56            AdapterProperties prop, RemoteDevices remoteDevices) {
57        super("BondStateMachine:");
58        addState(mStableState);
59        addState(mPendingCommandState);
60        mRemoteDevices = remoteDevices;
61        mAdapterService = service;
62        mAdapterProperties = prop;
63        mAdapter = BluetoothAdapter.getDefaultAdapter();
64        setInitialState(mStableState);
65    }
66
67    public static BondStateMachine make(AdapterService service,
68            AdapterProperties prop, RemoteDevices remoteDevices) {
69        Log.d(TAG, "make");
70        BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices);
71        bsm.start();
72        return bsm;
73    }
74
75    public void doQuit() {
76        quitNow();
77    }
78
79    public void cleanup() {
80        mAdapterService = null;
81        mRemoteDevices = null;
82        mAdapterProperties = null;
83    }
84
85    private class StableState extends State {
86        @Override
87        public void enter() {
88            infoLog("StableState(): Entering Off State");
89        }
90
91        @Override
92        public boolean processMessage(Message msg) {
93
94            BluetoothDevice dev = (BluetoothDevice)msg.obj;
95
96            switch(msg.what) {
97
98              case CREATE_BOND:
99                  createBond(dev, true);
100                  break;
101              case REMOVE_BOND:
102                  removeBond(dev, true);
103                  break;
104              case BONDING_STATE_CHANGE:
105                int newState = msg.arg1;
106                /* if incoming pairing, transition to pending state */
107                if (newState == BluetoothDevice.BOND_BONDING)
108                {
109                    sendIntent(dev, newState, 0);
110                    transitionTo(mPendingCommandState);
111                }
112                else
113                {
114                    Log.e(TAG, "In stable state, received invalid newState: " + newState);
115                }
116                break;
117
118              case CANCEL_BOND:
119              default:
120                   Log.e(TAG, "Received unhandled state: " + msg.what);
121                   return false;
122            }
123            return true;
124        }
125    }
126
127
128    private class PendingCommandState extends State {
129        private final ArrayList<BluetoothDevice> mDevices =
130            new ArrayList<BluetoothDevice>();
131
132        @Override
133        public void enter() {
134            infoLog("Entering PendingCommandState State");
135            BluetoothDevice dev = (BluetoothDevice)getCurrentMessage().obj;
136        }
137
138        @Override
139        public boolean processMessage(Message msg) {
140
141            BluetoothDevice dev = (BluetoothDevice)msg.obj;
142            boolean result = false;
143            if (mDevices.contains(dev) &&
144                    msg.what != CANCEL_BOND && msg.what != BONDING_STATE_CHANGE) {
145                deferMessage(msg);
146                return true;
147            }
148
149            switch (msg.what) {
150                case CREATE_BOND:
151                    result = createBond(dev, false);
152                    break;
153                case REMOVE_BOND:
154                    result = removeBond(dev, false);
155                    break;
156                case CANCEL_BOND:
157                    result = cancelBond(dev);
158                    break;
159                case BONDING_STATE_CHANGE:
160                    int newState = msg.arg1;
161                    int reason = getUnbondReasonFromHALCode(msg.arg2);
162                    sendIntent(dev, newState, reason);
163                    if(newState != BluetoothDevice.BOND_BONDING )
164                    {
165                        /* this is either none/bonded, remove and transition */
166                        result = !mDevices.remove(dev);
167                        if (mDevices.isEmpty()) {
168                            // Whenever mDevices is empty, then we need to
169                            // set result=false. Else, we will end up adding
170                            // the device to the list again. This prevents us
171                            // from pairing with a device that we just unpaired
172                            result = false;
173                            transitionTo(mStableState);
174                        }
175                        if (newState == BluetoothDevice.BOND_NONE)
176                        {
177                            // Set the profile Priorities to undefined
178                            clearProfilePriorty(dev);
179                        }
180                        else if (newState == BluetoothDevice.BOND_BONDED)
181                        {
182                           // Restore the profile priorty settings
183                           setProfilePriorty(dev);
184                        }
185                    }
186                    else if(!mDevices.contains(dev))
187                        result=true;
188                    break;
189                default:
190                    Log.e(TAG, "Received unhandled event:" + msg.what);
191                    return false;
192            }
193            if (result) mDevices.add(dev);
194
195            return true;
196        }
197    }
198
199    private boolean cancelBond(BluetoothDevice dev) {
200        if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
201            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
202            if (!mAdapterService.cancelBondNative(addr)) {
203               Log.e(TAG, "Unexpected error while cancelling bond:");
204            } else {
205                return true;
206            }
207        }
208        return false;
209    }
210
211    private boolean removeBond(BluetoothDevice dev, boolean transition) {
212        if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
213            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
214            if (!mAdapterService.removeBondNative(addr)) {
215               Log.e(TAG, "Unexpected error while removing bond:");
216            } else {
217                if (transition) transitionTo(mPendingCommandState);
218                return true;
219            }
220
221        }
222        return false;
223    }
224
225    private boolean createBond(BluetoothDevice dev, boolean transition) {
226        if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
227            infoLog("Bond address is:" + dev);
228            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
229            if (!mAdapterService.createBondNative(addr)) {
230                sendIntent(dev, BluetoothDevice.BOND_NONE,
231                           BluetoothDevice.UNBOND_REASON_REMOVED);
232                return false;
233            } else if (transition) {
234                transitionTo(mPendingCommandState);
235            }
236            return true;
237        }
238        return false;
239    }
240
241    private void sendIntent(BluetoothDevice device, int newState, int reason) {
242        DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
243        int oldState = BluetoothDevice.BOND_NONE;
244        if (devProp != null) {
245            oldState = devProp.getBondState();
246        }
247        if (oldState == newState) return;
248        mAdapterProperties.onBondStateChanged(device, newState);
249
250        Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
251        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
252        intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
253        intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
254        if (newState == BluetoothDevice.BOND_NONE)
255            intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
256        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
257                AdapterService.BLUETOOTH_PERM);
258        infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
259                + " NewState: " + newState);
260    }
261
262    void bondStateChangeCallback(int status, byte[] address, int newState) {
263        BluetoothDevice device = mRemoteDevices.getDevice(address);
264
265        if (device == null) {
266            infoLog("No record of the device:" + device);
267            // This device will be added as part of the BONDING_STATE_CHANGE intent processing
268            // in sendIntent above
269            device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
270        }
271
272        infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device
273                + " newState: " + newState);
274
275        Message msg = obtainMessage(BONDING_STATE_CHANGE);
276        msg.obj = device;
277
278        if (newState == BOND_STATE_BONDED)
279            msg.arg1 = BluetoothDevice.BOND_BONDED;
280        else if (newState == BOND_STATE_BONDING)
281            msg.arg1 = BluetoothDevice.BOND_BONDING;
282        else
283            msg.arg1 = BluetoothDevice.BOND_NONE;
284        msg.arg2 = status;
285
286        sendMessage(msg);
287    }
288
289    private void setProfilePriorty (BluetoothDevice device){
290        HidService hidService = HidService.getHidService();
291        A2dpService a2dpService = A2dpService.getA2dpService();
292        HeadsetService headsetService = HeadsetService.getHeadsetService();
293
294        if ((hidService != null) &&
295            (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
296            hidService.setPriority(device,BluetoothProfile.PRIORITY_ON);
297        }
298
299        if ((a2dpService != null) &&
300            (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
301            a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON);
302        }
303
304        if ((headsetService != null) &&
305            (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
306            headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
307        }
308    }
309
310    private void clearProfilePriorty (BluetoothDevice device){
311        HidService hidService = HidService.getHidService();
312        A2dpService a2dpService = A2dpService.getA2dpService();
313        HeadsetService headsetService = HeadsetService.getHeadsetService();
314
315        if (hidService != null)
316            hidService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
317        if(a2dpService != null)
318            a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
319        if(headsetService != null)
320            headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
321    }
322
323    private void infoLog(String msg) {
324        Log.i(TAG, msg);
325    }
326
327    private void errorLog(String msg) {
328        Log.e(TAG, msg);
329    }
330
331    private int getUnbondReasonFromHALCode (int reason) {
332        if (reason == AbstractionLayer.BT_STATUS_SUCCESS)
333            return BluetoothDevice.BOND_SUCCESS;
334        else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN)
335            return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
336        else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
337            return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
338
339        /* default */
340        return BluetoothDevice.UNBOND_REASON_REMOVED;
341    }
342}
343