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