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