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