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