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 = 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
74    public boolean isTurningOn() {
75        boolean isTurningOn=  mPendingCommandState.isTurningOn();
76        if (VDBG) Log.d(TAG,"isTurningOn()=" + isTurningOn);
77        return isTurningOn;
78    }
79
80    public boolean isTurningOff() {
81        boolean isTurningOff= mPendingCommandState.isTurningOff();
82        if (VDBG) Log.d(TAG,"isTurningOff()=" + isTurningOff);
83        return isTurningOff;
84    }
85
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
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
103    public void doQuit() {
104        quitNow();
105    }
106
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
116        public void enter() {
117            infoLog("Entering OffState");
118        }
119
120        @Override
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
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
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
200        public void enter() {
201            infoLog("Entering PendingCommandState State: isTurningOn()=" + isTurningOn() + ", isTurningOff()=" + isTurningOff());
202        }
203
204        public void setTurningOn(boolean isTurningOn) {
205            mIsTurningOn = isTurningOn;
206        }
207
208        public boolean isTurningOn() {
209            return mIsTurningOn;
210        }
211
212        public void setTurningOff(boolean isTurningOff) {
213            mIsTurningOff = isTurningOff;
214        }
215
216        public boolean isTurningOff() {
217            return mIsTurningOff;
218        }
219
220        @Override
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
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
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
384    private void infoLog(String msg) {
385        if (DBG) Log.i(TAG, msg);
386    }
387
388    private void errorLog(String msg) {
389        Log.e(TAG, msg);
390    }
391
392}
393