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