AdapterState.java revision 4852c5686229f1014e9851f4e9a3a19547581b45
1/*
2 * Copyright (C) 2012 Google Inc.
3 */
4
5package com.android.bluetooth.btservice;
6
7import android.bluetooth.BluetoothAdapter;
8import android.content.Context;
9import android.content.Intent;
10import android.os.Message;
11import android.util.Log;
12
13import com.android.internal.util.State;
14import com.android.internal.util.StateMachine;
15
16/**
17 * This state machine handles Bluetooth Adapter State.
18 * States:
19 *      {@link OnState} : Bluetooth is on at this state
20 *      {@link OffState}: Bluetooth is off at this state. This is the initial
21 *      state.
22 *      {@link PendingCommandState} : An enable / disable operation is pending.
23 * TODO(BT): Add per process on state.
24 */
25
26final class AdapterState extends StateMachine {
27    private static final boolean DBG = true;
28    private static final String TAG = "BluetoothAdapterState";
29
30    static final int USER_TURN_ON = 1;
31    static final int STARTED=2;
32    static final int ENABLED_READY = 3;
33
34    static final int USER_TURN_OFF = 20;
35    static final int BEGIN_DISABLE = 21;
36    static final int ALL_DEVICES_DISCONNECTED = 22;
37
38    static final int DISABLED = 24;
39    static final int STOPPED=25;
40
41    static final int START_TIMEOUT = 100;
42    static final int ENABLE_TIMEOUT = 101;
43    static final int DISABLE_TIMEOUT = 103;
44    static final int STOP_TIMEOUT = 104;
45
46    static final int USER_TURN_OFF_DELAY_MS=500;
47
48    //TODO: tune me
49    private static final int ENABLE_TIMEOUT_DELAY = 8000;
50    private static final int DISABLE_TIMEOUT_DELAY = 8000;
51    private static final int START_TIMEOUT_DELAY = 5000;
52    private static final int STOP_TIMEOUT_DELAY = 5000;
53    private static final int PROPERTY_OP_DELAY =2000;
54    private AdapterService mAdapterService;
55    private AdapterProperties mAdapterProperties;
56    private PendingCommandState mPendingCommandState = new PendingCommandState();
57    private OnState mOnState = new OnState();
58    private OffState mOffState = new OffState();
59
60    public boolean isTurningOn() {
61        boolean isTurningOn=  mPendingCommandState.isTurningOn();
62        if (DBG) Log.d(TAG,"isTurningOn()=" + isTurningOn);
63        return isTurningOn;
64    }
65
66    public boolean isTurningOff() {
67        boolean isTurningOff= mPendingCommandState.isTurningOff();
68        if (DBG) Log.d(TAG,"isTurningOff()=" + isTurningOff);
69        return isTurningOff;
70    }
71
72    public AdapterState(AdapterService service,AdapterProperties adapterProperties) {
73        super("BluetoothAdapterState:");
74        addState(mOnState);
75        addState(mOffState);
76        addState(mPendingCommandState);
77        mAdapterService = service;
78        mAdapterProperties = adapterProperties;
79        setInitialState(mOffState);
80    }
81
82    public void cleanup() {
83        if(mAdapterProperties != null)
84            mAdapterProperties = null;
85        if(mAdapterService != null)
86            mAdapterService = null;
87    }
88
89    private class OffState extends State {
90        @Override
91        public void enter() {
92            infoLog("Entering OffState");
93        }
94
95        @Override
96        public boolean processMessage(Message msg) {
97            if (msg.what == SM_QUIT_CMD) {
98                Log.d(TAG, "Received quit request...");
99                return false;
100            }
101            int requestId = msg.arg1;
102
103            switch(msg.what) {
104               case USER_TURN_ON:
105                   if (DBG) Log.d(TAG,"CURRENT_STATE=OFF, MESSAGE = USER_TURN_ON, requestId= " + msg.arg1);
106                   notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON);
107                   mPendingCommandState.setTurningOn(true);
108                   transitionTo(mPendingCommandState);
109                   sendMessageDelayed(START_TIMEOUT, START_TIMEOUT_DELAY);
110                   mAdapterService.processStart();
111                   break;
112               case USER_TURN_OFF:
113                   if (DBG) Log.d(TAG,"CURRENT_STATE=OFF, MESSAGE = USER_TURN_OFF, requestId= " + msg.arg1);
114                   //Handle case of service started and stopped without enable
115                   mAdapterService.startShutdown(requestId);
116                   break;
117               default:
118                   if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=OFF, MESSAGE = " + msg.what );
119                   return false;
120            }
121            return true;
122        }
123    }
124
125    private class OnState extends State {
126        @Override
127        public void enter() {
128            infoLog("Entering On State");
129        }
130
131        @Override
132        public boolean processMessage(Message msg) {
133            switch(msg.what) {
134               case USER_TURN_OFF:
135                   if (DBG) Log.d(TAG,"CURRENT_STATE=ON, MESSAGE = USER_TURN_OFF, requestId= " + msg.arg1);
136                   notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_OFF);
137                   mPendingCommandState.setTurningOff(true);
138                   mPendingCommandState.setOffRequestId(msg.arg1);
139                   transitionTo(mPendingCommandState);
140
141                   // Invoke onBluetoothDisable which shall trigger a
142                   // setScanMode to SCAN_MODE_NONE
143                   Message m = obtainMessage(BEGIN_DISABLE);
144                   m.arg1 = msg.arg1;
145                   sendMessageDelayed(m, PROPERTY_OP_DELAY);
146                   mAdapterProperties.onBluetoothDisable();
147                   break;
148
149               case USER_TURN_ON:
150                   if (DBG) Log.d(TAG,"CURRENT_STATE=ON, MESSAGE = USER_TURN_ON, requestId= " + msg.arg1);
151                   Log.i(TAG,"Bluetooth already ON, ignoring USER_TURN_ON");
152                   break;
153               default:
154                   if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=ON, MESSAGE = " + msg.what );
155                   return false;
156            }
157            return true;
158        }
159    }
160
161    private class PendingCommandState extends State {
162        private boolean mIsTurningOn;
163        private boolean mIsTurningOff;
164
165        private int mRequestId;
166
167        public void enter() {
168            infoLog("Entering PendingCommandState State: isTurningOn()=" + isTurningOn() + ", isTurningOff()=" + isTurningOff());
169        }
170
171        public void setTurningOn(boolean isTurningOn) {
172            mIsTurningOn = isTurningOn;
173        }
174
175        public boolean isTurningOn() {
176            return mIsTurningOn;
177        }
178
179        public void setTurningOff(boolean isTurningOff) {
180            mIsTurningOff = isTurningOff;
181        }
182
183        public boolean isTurningOff() {
184            return mIsTurningOff;
185        }
186
187        public void setOffRequestId(int requestId) {
188            mRequestId = requestId;
189        }
190
191        public int getOffRequestId() {
192            return mRequestId;
193        }
194
195        @Override
196        public boolean processMessage(Message msg) {
197            boolean isTurningOn= isTurningOn();
198            boolean isTurningOff = isTurningOff();
199            switch (msg.what) {
200                case USER_TURN_ON:
201                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = USER_TURN_ON, requestId= " + msg.arg1
202                            + ", isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
203                    if (isTurningOn) {
204                        Log.i(TAG,"CURRENT_STATE=PENDING: Alreadying turning on bluetooth... Ignoring USER_TURN_ON...");
205                    } else {
206                        Log.i(TAG,"CURRENT_STATE=PENDING: Deferring request USER_TURN_ON");
207                        deferMessage(msg);
208                    }
209                    break;
210                case USER_TURN_OFF:
211                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = USER_TURN_ON, requestId= " + msg.arg1
212                            + ", isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
213                    if (isTurningOff) {
214                        Log.i(TAG,"CURRENT_STATE=PENDING: Alreadying turning off bluetooth... Ignoring USER_TURN_OFF...");
215                    } else {
216                        Log.i(TAG,"CURRENT_STATE=PENDING: Deferring request USER_TURN_OFF");
217                        deferMessage(msg);
218                    }
219                    break;
220                case STARTED:   {
221                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STARTED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
222                    //Remove start timeout
223                    removeMessages(START_TIMEOUT);
224
225                    //Enable
226                    boolean ret = mAdapterService.enableNative();
227                    if (!ret) {
228                        Log.e(TAG, "Error while turning Bluetooth On");
229                        notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
230                        transitionTo(mOffState);
231                    } else {
232                        sendMessageDelayed(ENABLE_TIMEOUT, ENABLE_TIMEOUT_DELAY);
233                    }
234                }
235                    break;
236
237                case ENABLED_READY:
238                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = ENABLE_READY, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
239                    removeMessages(ENABLE_TIMEOUT);
240                    mAdapterProperties.onBluetoothReady();
241                    mPendingCommandState.setTurningOn(false);
242                    transitionTo(mOnState);
243                    notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
244                    break;
245
246                case BEGIN_DISABLE: {
247                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = BEGIN_DISABLE" + isTurningOn + ", isTurningOff=" + isTurningOff);
248                    removeMessages(BEGIN_DISABLE); //Remove extra message we setup in USER_TURN_OFF
249                    //Log.d(TAG,"CURRENT_STATE=ON, MESSAGE = BEGIN_DISABLE_ON, requestId= " + msg.arg1);
250                    sendMessageDelayed(DISABLE_TIMEOUT, DISABLE_TIMEOUT_DELAY);
251                    boolean ret = mAdapterService.disableNative();
252                    if (!ret) {
253                        removeMessages(DISABLE_TIMEOUT);
254                        Log.e(TAG, "Error while turning Bluetooth Off");
255                        //FIXME: what about post enable services
256                        mPendingCommandState.setTurningOff(false);
257                        mPendingCommandState.setOffRequestId(-1);
258                        notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
259                    }
260                }
261                    break;
262                case DISABLED:
263                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = DISABLED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
264                    removeMessages(DISABLE_TIMEOUT);
265                    sendMessageDelayed(STOP_TIMEOUT, STOP_TIMEOUT_DELAY);
266                    if (mAdapterService.stopProfileServices()) {
267                        Log.d(TAG,"Stopping profile services that were post enabled");
268                        break;
269                    }
270                    //Fall through if no services or services already stopped
271                case STOPPED:
272                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STOPPED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
273                    removeMessages(STOP_TIMEOUT);
274                    setTurningOff(false);
275                    int requestId= getOffRequestId();
276                    setOffRequestId(-1);
277                    transitionTo(mOffState);
278                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
279                    mAdapterService.startShutdown(requestId);
280                    break;
281                case START_TIMEOUT:
282                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = START_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
283                    errorLog("Error enabling Bluetooth");
284                    mPendingCommandState.setTurningOn(false);
285                    transitionTo(mOffState);
286                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
287                    break;
288                case ENABLE_TIMEOUT:
289                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = ENABLE_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
290                    errorLog("Error enabling Bluetooth");
291                    mPendingCommandState.setTurningOn(false);
292                    transitionTo(mOffState);
293                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
294                    break;
295                case STOP_TIMEOUT:
296                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STOP_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
297                    errorLog("Error stopping Bluetooth profiles");
298                    mPendingCommandState.setTurningOff(false);
299                    transitionTo(mOffState);
300                    break;
301                case DISABLE_TIMEOUT:
302                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = DISABLE_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
303                    errorLog("Error disabling Bluetooth");
304                    mPendingCommandState.setTurningOff(false);
305                    transitionTo(mOnState);
306                    break;
307                default:
308                    if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=PENDING, MESSAGE = " + msg.what );
309                    return false;
310            }
311            return true;
312        }
313    }
314
315
316    private void notifyAdapterStateChange(int newState) {
317        int oldState = mAdapterProperties.getState();
318        mAdapterProperties.setState(newState);
319        infoLog("Bluetooth adapter state changed: " + oldState + "-> " + newState);
320        mAdapterService.updateAdapterState(oldState, newState);
321    }
322
323    void stateChangeCallback(int status) {
324        if (status == AbstractionLayer.BT_STATE_OFF) {
325            sendMessage(DISABLED);
326        } else if (status == AbstractionLayer.BT_STATE_ON) {
327            // We should have got the property change for adapter and remote devices.
328            sendMessage(ENABLED_READY);
329        } else {
330            errorLog("Incorrect status in stateChangeCallback");
331        }
332    }
333
334    private void infoLog(String msg) {
335        if (DBG) Log.i(TAG, msg);
336    }
337
338    private void errorLog(String msg) {
339        Log.e(TAG, msg);
340    }
341}
342