BondStateMachine.java revision db681f3973bd8a088a878fd6f41d4330cdbc5522
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, msg.arg1, 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, msg.arg1, 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                           // Do not set profile priority
195                           // Profile priority should be set after SDP completion
196
197                           // Restore the profile priorty settings
198                           //setProfilePriorty(dev);
199                        }
200                    }
201                    else if(!mDevices.contains(dev))
202                        result=true;
203                    break;
204                default:
205                    Log.e(TAG, "Received unhandled event:" + msg.what);
206                    return false;
207            }
208            if (result) mDevices.add(dev);
209
210            return true;
211        }
212    }
213
214    private boolean cancelBond(BluetoothDevice dev) {
215        if (dev.getBondState() == BluetoothDevice.BOND_BONDING) {
216            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
217            if (!mAdapterService.cancelBondNative(addr)) {
218               Log.e(TAG, "Unexpected error while cancelling bond:");
219            } else {
220                return true;
221            }
222        }
223        return false;
224    }
225
226    private boolean removeBond(BluetoothDevice dev, boolean transition) {
227        if (dev.getBondState() == BluetoothDevice.BOND_BONDED) {
228            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
229            if (!mAdapterService.removeBondNative(addr)) {
230               Log.e(TAG, "Unexpected error while removing bond:");
231            } else {
232                if (transition) transitionTo(mPendingCommandState);
233                return true;
234            }
235
236        }
237        return false;
238    }
239
240    private boolean createBond(BluetoothDevice dev, int transport, boolean transition) {
241        if (dev.getBondState() == BluetoothDevice.BOND_NONE) {
242            infoLog("Bond address is:" + dev);
243            byte[] addr = Utils.getBytesFromAddress(dev.getAddress());
244            if (!mAdapterService.createBondNative(addr, transport)) {
245                sendIntent(dev, BluetoothDevice.BOND_NONE,
246                           BluetoothDevice.UNBOND_REASON_REMOVED);
247                return false;
248            } else if (transition) {
249                transitionTo(mPendingCommandState);
250            }
251            return true;
252        }
253        return false;
254    }
255
256    private void sendIntent(BluetoothDevice device, int newState, int reason) {
257        DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device);
258        int oldState = BluetoothDevice.BOND_NONE;
259        if (devProp != null) {
260            oldState = devProp.getBondState();
261        }
262        if (oldState == newState) return;
263        mAdapterProperties.onBondStateChanged(device, newState);
264
265        Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
266        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
267        intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState);
268        intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
269        if (newState == BluetoothDevice.BOND_NONE)
270            intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
271        mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL,
272                AdapterService.BLUETOOTH_PERM);
273        infoLog("Bond State Change Intent:" + device + " OldState: " + oldState
274                + " NewState: " + newState);
275    }
276
277    void bondStateChangeCallback(int status, byte[] address, int newState) {
278        BluetoothDevice device = mRemoteDevices.getDevice(address);
279
280        if (device == null) {
281            infoLog("No record of the device:" + device);
282            // This device will be added as part of the BONDING_STATE_CHANGE intent processing
283            // in sendIntent above
284            device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address));
285        }
286
287        infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device
288                + " newState: " + newState);
289
290        Message msg = obtainMessage(BONDING_STATE_CHANGE);
291        msg.obj = device;
292
293        if (newState == BOND_STATE_BONDED)
294            msg.arg1 = BluetoothDevice.BOND_BONDED;
295        else if (newState == BOND_STATE_BONDING)
296            msg.arg1 = BluetoothDevice.BOND_BONDING;
297        else
298            msg.arg1 = BluetoothDevice.BOND_NONE;
299        msg.arg2 = status;
300
301        sendMessage(msg);
302    }
303
304    private void setProfilePriorty (BluetoothDevice device){
305        HidService hidService = HidService.getHidService();
306        A2dpService a2dpService = A2dpService.getA2dpService();
307        HeadsetService headsetService = HeadsetService.getHeadsetService();
308
309        if ((hidService != null) &&
310            (hidService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
311            hidService.setPriority(device,BluetoothProfile.PRIORITY_ON);
312        }
313
314        if ((a2dpService != null) &&
315            (a2dpService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
316            a2dpService.setPriority(device,BluetoothProfile.PRIORITY_ON);
317        }
318
319        if ((headsetService != null) &&
320            (headsetService.getPriority(device) == BluetoothProfile.PRIORITY_UNDEFINED)){
321            headsetService.setPriority(device,BluetoothProfile.PRIORITY_ON);
322        }
323    }
324
325    private void clearProfilePriorty (BluetoothDevice device){
326        HidService hidService = HidService.getHidService();
327        A2dpService a2dpService = A2dpService.getA2dpService();
328        HeadsetService headsetService = HeadsetService.getHeadsetService();
329
330        if (hidService != null)
331            hidService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
332        if(a2dpService != null)
333            a2dpService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
334        if(headsetService != null)
335            headsetService.setPriority(device,BluetoothProfile.PRIORITY_UNDEFINED);
336    }
337
338    private void infoLog(String msg) {
339        Log.i(TAG, msg);
340    }
341
342    private void errorLog(String msg) {
343        Log.e(TAG, msg);
344    }
345
346    private int getUnbondReasonFromHALCode (int reason) {
347        if (reason == AbstractionLayer.BT_STATUS_SUCCESS)
348            return BluetoothDevice.BOND_SUCCESS;
349        else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN)
350            return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN;
351        else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE)
352            return BluetoothDevice.UNBOND_REASON_AUTH_FAILED;
353        else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED)
354            return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED;
355        else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT)
356            return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT;
357
358        /* default */
359        return BluetoothDevice.UNBOND_REASON_REMOVED;
360    }
361}
362