AdapterState.java revision 15d36984a79d6e35c659edb0efdf929f0b526bd5
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    static final int SET_SCAN_MODE_TIMEOUT = 105;
46
47    static final int USER_TURN_OFF_DELAY_MS=500;
48
49    //TODO: tune me
50    private static final int ENABLE_TIMEOUT_DELAY = 8000;
51    private static final int DISABLE_TIMEOUT_DELAY = 8000;
52    private static final int START_TIMEOUT_DELAY = 5000;
53    private static final int STOP_TIMEOUT_DELAY = 5000;
54    private static final int PROPERTY_OP_DELAY =2000;
55    private AdapterService mAdapterService;
56    private AdapterProperties mAdapterProperties;
57    private PendingCommandState mPendingCommandState = new PendingCommandState();
58    private OnState mOnState = new OnState();
59    private OffState mOffState = new OffState();
60
61    public boolean isTurningOn() {
62        boolean isTurningOn=  mPendingCommandState.isTurningOn();
63        if (DBG) Log.d(TAG,"isTurningOn()=" + isTurningOn);
64        return isTurningOn;
65    }
66
67    public boolean isTurningOff() {
68        boolean isTurningOff= mPendingCommandState.isTurningOff();
69        if (DBG) Log.d(TAG,"isTurningOff()=" + isTurningOff);
70        return isTurningOff;
71    }
72
73    public AdapterState(AdapterService service,AdapterProperties adapterProperties) {
74        super("BluetoothAdapterState:");
75        addState(mOnState);
76        addState(mOffState);
77        addState(mPendingCommandState);
78        mAdapterService = service;
79        mAdapterProperties = adapterProperties;
80        setInitialState(mOffState);
81    }
82
83
84    public void doQuit() {
85        quitNow();
86    }
87
88    public void cleanup() {
89        if(mAdapterProperties != null)
90            mAdapterProperties = null;
91        if(mAdapterService != null)
92            mAdapterService = null;
93    }
94
95    private class OffState extends State {
96        @Override
97        public void enter() {
98            infoLog("Entering OffState");
99        }
100
101        @Override
102        public boolean processMessage(Message msg) {
103
104            switch(msg.what) {
105               case USER_TURN_ON:
106                   if (DBG) Log.d(TAG,"CURRENT_STATE=OFF, MESSAGE = USER_TURN_ON");
107                   notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON);
108                   mPendingCommandState.setTurningOn(true);
109                   transitionTo(mPendingCommandState);
110                   sendMessageDelayed(START_TIMEOUT, START_TIMEOUT_DELAY);
111                   mAdapterService.processStart();
112                   break;
113               case USER_TURN_OFF:
114                   if (DBG) Log.d(TAG,"CURRENT_STATE=OFF, MESSAGE = USER_TURN_OFF");
115                   //TODO: Handle case of service started and stopped without enable
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
134            switch(msg.what) {
135               case USER_TURN_OFF:
136                   if (DBG) Log.d(TAG,"CURRENT_STATE=ON, MESSAGE = USER_TURN_OFF");
137                   notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_OFF);
138                   mPendingCommandState.setTurningOff(true);
139                   transitionTo(mPendingCommandState);
140
141                   // Invoke onBluetoothDisable which shall trigger a
142                   // setScanMode to SCAN_MODE_NONE
143                   Message m = obtainMessage(SET_SCAN_MODE_TIMEOUT);
144                   sendMessageDelayed(m, PROPERTY_OP_DELAY);
145                   mAdapterProperties.onBluetoothDisable();
146                   break;
147
148               case USER_TURN_ON:
149                   if (DBG) Log.d(TAG,"CURRENT_STATE=ON, MESSAGE = USER_TURN_ON");
150                   Log.i(TAG,"Bluetooth already ON, ignoring USER_TURN_ON");
151                   break;
152               default:
153                   if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=ON, MESSAGE = " + msg.what );
154                   return false;
155            }
156            return true;
157        }
158    }
159
160    private class PendingCommandState extends State {
161        private boolean mIsTurningOn;
162        private boolean mIsTurningOff;
163
164        public void enter() {
165            infoLog("Entering PendingCommandState State: isTurningOn()=" + isTurningOn() + ", isTurningOff()=" + isTurningOff());
166        }
167
168        public void setTurningOn(boolean isTurningOn) {
169            mIsTurningOn = isTurningOn;
170        }
171
172        public boolean isTurningOn() {
173            return mIsTurningOn;
174        }
175
176        public void setTurningOff(boolean isTurningOff) {
177            mIsTurningOff = isTurningOff;
178        }
179
180        public boolean isTurningOff() {
181            return mIsTurningOff;
182        }
183
184        @Override
185        public boolean processMessage(Message msg) {
186
187            boolean isTurningOn= isTurningOn();
188            boolean isTurningOff = isTurningOff();
189
190            switch (msg.what) {
191                case USER_TURN_ON:
192                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = USER_TURN_ON"
193                            + ", isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
194                    if (isTurningOn) {
195                        Log.i(TAG,"CURRENT_STATE=PENDING: Alreadying turning on bluetooth... Ignoring USER_TURN_ON...");
196                    } else {
197                        Log.i(TAG,"CURRENT_STATE=PENDING: Deferring request USER_TURN_ON");
198                        deferMessage(msg);
199                    }
200                    break;
201                case USER_TURN_OFF:
202                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = USER_TURN_ON"
203                            + ", isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
204                    if (isTurningOff) {
205                        Log.i(TAG,"CURRENT_STATE=PENDING: Alreadying turning off bluetooth... Ignoring USER_TURN_OFF...");
206                    } else {
207                        Log.i(TAG,"CURRENT_STATE=PENDING: Deferring request USER_TURN_OFF");
208                        deferMessage(msg);
209                    }
210                    break;
211                case STARTED:   {
212                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STARTED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
213                    //Remove start timeout
214                    removeMessages(START_TIMEOUT);
215
216                    //Enable
217                    boolean ret = mAdapterService.enableNative();
218                    if (!ret) {
219                        Log.e(TAG, "Error while turning Bluetooth On");
220                        notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
221                        transitionTo(mOffState);
222                    } else {
223                        sendMessageDelayed(ENABLE_TIMEOUT, ENABLE_TIMEOUT_DELAY);
224                    }
225                }
226                    break;
227
228                case ENABLED_READY:
229                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = ENABLE_READY, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
230                    removeMessages(ENABLE_TIMEOUT);
231                    mAdapterProperties.onBluetoothReady();
232                    mPendingCommandState.setTurningOn(false);
233                    transitionTo(mOnState);
234                    notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
235                    break;
236
237                case SET_SCAN_MODE_TIMEOUT:
238                     Log.w(TAG,"Timeout will setting scan mode..Continuing with disable...");
239                     //Fall through
240                case BEGIN_DISABLE: {
241                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = BEGIN_DISABLE" + isTurningOn + ", isTurningOff=" + isTurningOff);
242                    removeMessages(SET_SCAN_MODE_TIMEOUT);
243                    sendMessageDelayed(DISABLE_TIMEOUT, DISABLE_TIMEOUT_DELAY);
244                    boolean ret = mAdapterService.disableNative();
245                    if (!ret) {
246                        removeMessages(DISABLE_TIMEOUT);
247                        Log.e(TAG, "Error while turning Bluetooth Off");
248                        //FIXME: what about post enable services
249                        mPendingCommandState.setTurningOff(false);
250                        notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
251                    }
252                }
253                    break;
254                case DISABLED:
255                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = DISABLED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
256                    removeMessages(DISABLE_TIMEOUT);
257                    sendMessageDelayed(STOP_TIMEOUT, STOP_TIMEOUT_DELAY);
258                    if (mAdapterService.stopProfileServices()) {
259                        Log.d(TAG,"Stopping profile services that were post enabled");
260                        break;
261                    }
262                    //Fall through if no services or services already stopped
263                case STOPPED:
264                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STOPPED, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
265                    removeMessages(STOP_TIMEOUT);
266                    setTurningOff(false);
267                    transitionTo(mOffState);
268                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
269                    break;
270                case START_TIMEOUT:
271                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = START_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
272                    errorLog("Error enabling Bluetooth");
273                    mPendingCommandState.setTurningOn(false);
274                    transitionTo(mOffState);
275                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
276                    break;
277                case ENABLE_TIMEOUT:
278                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = ENABLE_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
279                    errorLog("Error enabling Bluetooth");
280                    mPendingCommandState.setTurningOn(false);
281                    transitionTo(mOffState);
282                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
283                    break;
284                case STOP_TIMEOUT:
285                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = STOP_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
286                    errorLog("Error stopping Bluetooth profiles");
287                    mPendingCommandState.setTurningOff(false);
288                    transitionTo(mOffState);
289                    break;
290                case DISABLE_TIMEOUT:
291                    if (DBG) Log.d(TAG,"CURRENT_STATE=PENDING, MESSAGE = DISABLE_TIMEOUT, isTurningOn=" + isTurningOn + ", isTurningOff=" + isTurningOff);
292                    errorLog("Error disabling Bluetooth");
293                    mPendingCommandState.setTurningOff(false);
294                    transitionTo(mOnState);
295                    break;
296                default:
297                    if (DBG) Log.d(TAG,"ERROR: UNEXPECTED MESSAGE: CURRENT_STATE=PENDING, MESSAGE = " + msg.what );
298                    return false;
299            }
300            return true;
301        }
302    }
303
304
305    private void notifyAdapterStateChange(int newState) {
306        int oldState = mAdapterProperties.getState();
307        mAdapterProperties.setState(newState);
308        infoLog("Bluetooth adapter state changed: " + oldState + "-> " + newState);
309        mAdapterService.updateAdapterState(oldState, newState);
310    }
311
312    void stateChangeCallback(int status) {
313        if (status == AbstractionLayer.BT_STATE_OFF) {
314            sendMessage(DISABLED);
315        } else if (status == AbstractionLayer.BT_STATE_ON) {
316            // We should have got the property change for adapter and remote devices.
317            sendMessage(ENABLED_READY);
318        } else {
319            errorLog("Incorrect status in stateChangeCallback");
320        }
321    }
322
323    private void infoLog(String msg) {
324        if (DBG) Log.i(TAG, msg);
325    }
326
327    private void errorLog(String msg) {
328        Log.e(TAG, msg);
329    }
330
331}
332