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