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.os.Message;
21import android.os.UserManager;
22import android.util.Log;
23
24import com.android.internal.util.State;
25import com.android.internal.util.StateMachine;
26
27/**
28 * This state machine handles Bluetooth Adapter State.
29 * States:
30 *      {@link OnState} : Bluetooth is on at this state
31 *      {@link OffState}: Bluetooth is off at this state. This is the initial
32 *      state.
33 *      {@link PendingCommandState} : An enable / disable operation is pending.
34 * TODO(BT): Add per process on state.
35 */
36
37final class AdapterState extends StateMachine {
38    private static final boolean DBG = true;
39    private static final boolean VDBG = true;
40    private static final String TAG = "BluetoothAdapterState";
41
42    static final int BLE_TURN_ON = 0;
43    static final int USER_TURN_ON = 1;
44    static final int BREDR_STARTED=2;
45    static final int ENABLED_READY = 3;
46    static final int BLE_STARTED=4;
47
48    static final int USER_TURN_OFF = 20;
49    static final int BEGIN_DISABLE = 21;
50    static final int ALL_DEVICES_DISCONNECTED = 22;
51    static final int BLE_TURN_OFF = 23;
52
53    static final int DISABLED = 24;
54    static final int BLE_STOPPED=25;
55    static final int BREDR_STOPPED = 26;
56
57    static final int BREDR_START_TIMEOUT = 100;
58    static final int ENABLE_TIMEOUT = 101;
59    static final int DISABLE_TIMEOUT = 103;
60    static final int BLE_STOP_TIMEOUT = 104;
61    static final int SET_SCAN_MODE_TIMEOUT = 105;
62    static final int BLE_START_TIMEOUT = 106;
63    static final int BREDR_STOP_TIMEOUT = 107;
64
65    static final int USER_TURN_OFF_DELAY_MS=500;
66
67    //TODO: tune me
68    private static final int ENABLE_TIMEOUT_DELAY = 12000;
69    private static final int DISABLE_TIMEOUT_DELAY = 8000;
70    private static final int BREDR_START_TIMEOUT_DELAY = 4000;
71    //BLE_START_TIMEOUT can happen quickly as it just a start gattservice
72    private static final int BLE_START_TIMEOUT_DELAY = 2000; //To start GattService
73    private static final int BLE_STOP_TIMEOUT_DELAY = 2000;
74    //BREDR_STOP_TIMEOUT can < STOP_TIMEOUT
75    private static final int BREDR_STOP_TIMEOUT_DELAY = 4000;
76    private static final int PROPERTY_OP_DELAY =2000;
77    private AdapterService mAdapterService;
78    private AdapterProperties mAdapterProperties;
79    private PendingCommandState mPendingCommandState = new PendingCommandState();
80    private OnState mOnState = new OnState();
81    private OffState mOffState = new OffState();
82    private BleOnState mBleOnState = new BleOnState();
83
84    public boolean isTurningOn() {
85        return mPendingCommandState.isTurningOn();
86    }
87
88    public boolean isBleTurningOn() {
89        return mPendingCommandState.isBleTurningOn();
90    }
91
92    public boolean isBleTurningOff() {
93        return mPendingCommandState.isBleTurningOff();
94    }
95
96    public boolean isTurningOff() {
97        return mPendingCommandState.isTurningOff();
98    }
99
100    private AdapterState(AdapterService service, AdapterProperties adapterProperties) {
101        super("BluetoothAdapterState:");
102        addState(mOnState);
103        addState(mBleOnState);
104        addState(mOffState);
105        addState(mPendingCommandState);
106        mAdapterService = service;
107        mAdapterProperties = adapterProperties;
108        setInitialState(mOffState);
109    }
110
111    public static AdapterState make(AdapterService service, AdapterProperties adapterProperties) {
112        Log.d(TAG, "make() - Creating AdapterState");
113        AdapterState as = new AdapterState(service, adapterProperties);
114        as.start();
115        return as;
116    }
117
118    public void doQuit() {
119        quitNow();
120    }
121
122    public void cleanup() {
123        if(mAdapterProperties != null)
124            mAdapterProperties = null;
125        if(mAdapterService != null)
126            mAdapterService = null;
127    }
128
129    private class OffState extends State {
130        @Override
131        public void enter() {
132            infoLog("Entering OffState");
133        }
134
135        @Override
136        public boolean processMessage(Message msg) {
137            AdapterService adapterService = mAdapterService;
138            if (adapterService == null) {
139                errorLog("Received message in OffState after cleanup: " + msg.what);
140                return false;
141            }
142
143            debugLog("Current state: OFF, message: " + msg.what);
144
145            switch(msg.what) {
146               case BLE_TURN_ON:
147                   notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON);
148                   mPendingCommandState.setBleTurningOn(true);
149                   transitionTo(mPendingCommandState);
150                   sendMessageDelayed(BLE_START_TIMEOUT, BLE_START_TIMEOUT_DELAY);
151                   adapterService.BleOnProcessStart();
152                   break;
153
154               case USER_TURN_OFF:
155                   //TODO: Handle case of service started and stopped without enable
156                   break;
157
158               default:
159                   return false;
160            }
161            return true;
162        }
163    }
164
165    private class BleOnState extends State {
166        @Override
167        public void enter() {
168            infoLog("Entering BleOnState");
169        }
170
171        @Override
172        public boolean processMessage(Message msg) {
173
174            AdapterService adapterService = mAdapterService;
175            AdapterProperties adapterProperties = mAdapterProperties;
176            if ((adapterService == null) || (adapterProperties == null)) {
177                errorLog("Received message in BleOnState after cleanup: " + msg.what);
178                return false;
179            }
180
181            debugLog("Current state: BLE ON, message: " + msg.what);
182
183            switch(msg.what) {
184               case USER_TURN_ON:
185                   notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON);
186                   mPendingCommandState.setTurningOn(true);
187                   transitionTo(mPendingCommandState);
188                   sendMessageDelayed(BREDR_START_TIMEOUT, BREDR_START_TIMEOUT_DELAY);
189                   adapterService.startCoreServices();
190                   break;
191
192               case USER_TURN_OFF:
193                   notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF);
194                   mPendingCommandState.setBleTurningOff(true);
195                   adapterProperties.onBleDisable();
196                   transitionTo(mPendingCommandState);
197                   sendMessageDelayed(DISABLE_TIMEOUT, DISABLE_TIMEOUT_DELAY);
198                   boolean ret = adapterService.disableNative();
199                   if (!ret) {
200                        removeMessages(DISABLE_TIMEOUT);
201                        errorLog("Error while calling disableNative");
202                        //FIXME: what about post enable services
203                        mPendingCommandState.setBleTurningOff(false);
204                        notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
205                   }
206                   break;
207
208               default:
209                   return false;
210            }
211            return true;
212        }
213    }
214
215    private class OnState extends State {
216        @Override
217        public void enter() {
218            infoLog("Entering OnState");
219
220            AdapterService adapterService = mAdapterService;
221            if (adapterService == null) {
222                errorLog("Entered OnState after cleanup");
223                return;
224            }
225            adapterService.updateUuids();
226        }
227
228        @Override
229        public boolean processMessage(Message msg) {
230            AdapterProperties adapterProperties = mAdapterProperties;
231            if (adapterProperties == null) {
232                errorLog("Received message in OnState after cleanup: " + msg.what);
233                return false;
234            }
235
236            debugLog("Current state: ON, message: " + msg.what);
237
238            switch(msg.what) {
239               case BLE_TURN_OFF:
240                   notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_OFF);
241                   mPendingCommandState.setTurningOff(true);
242                   transitionTo(mPendingCommandState);
243
244                   // Invoke onBluetoothDisable which shall trigger a
245                   // setScanMode to SCAN_MODE_NONE
246                   Message m = obtainMessage(SET_SCAN_MODE_TIMEOUT);
247                   sendMessageDelayed(m, PROPERTY_OP_DELAY);
248                   adapterProperties.onBluetoothDisable();
249                   break;
250
251               case USER_TURN_ON:
252                   break;
253
254               default:
255                   return false;
256            }
257            return true;
258        }
259    }
260
261    private class PendingCommandState extends State {
262        private boolean mIsTurningOn;
263        private boolean mIsTurningOff;
264        private boolean mIsBleTurningOn;
265        private boolean mIsBleTurningOff;
266
267        public void enter() {
268            infoLog("Entering PendingCommandState");
269        }
270
271        public void setTurningOn(boolean isTurningOn) {
272            mIsTurningOn = isTurningOn;
273        }
274
275        public boolean isTurningOn() {
276            return mIsTurningOn;
277        }
278
279        public void setTurningOff(boolean isTurningOff) {
280            mIsTurningOff = isTurningOff;
281        }
282
283        public boolean isTurningOff() {
284            return mIsTurningOff;
285        }
286
287        public void setBleTurningOn(boolean isBleTurningOn) {
288            mIsBleTurningOn = isBleTurningOn;
289        }
290
291        public boolean isBleTurningOn() {
292            return mIsBleTurningOn;
293        }
294
295        public void setBleTurningOff(boolean isBleTurningOff) {
296            mIsBleTurningOff = isBleTurningOff;
297        }
298
299        public boolean isBleTurningOff() {
300            return mIsBleTurningOff;
301        }
302
303        @Override
304        public boolean processMessage(Message msg) {
305
306            /* Cache current states */
307            /* TODO(eisenbach): Not sure why this is done at all.
308             * Seems like the mIs* variables should be protected,
309             * or really, removed. Which reminds me: This file needs
310             * a serious refactor...*/
311            boolean isTurningOn = isTurningOn();
312            boolean isTurningOff = isTurningOff();
313            boolean isBleTurningOn = isBleTurningOn();
314            boolean isBleTurningOff = isBleTurningOff();
315
316            logTransientStates();
317
318            AdapterService adapterService = mAdapterService;
319            AdapterProperties adapterProperties = mAdapterProperties;
320            if ((adapterService == null) || (adapterProperties == null)) {
321                errorLog("Received message in PendingCommandState after cleanup: " + msg.what);
322                return false;
323            }
324
325            debugLog("Current state: PENDING_COMMAND, message: " + msg.what);
326
327            switch (msg.what) {
328                case USER_TURN_ON:
329                    if (isBleTurningOff || isTurningOff) { //TODO:do we need to send it after ble turn off also??
330                        infoLog("Deferring USER_TURN_ON request...");
331                        deferMessage(msg);
332                    }
333                    break;
334
335                case USER_TURN_OFF:
336                    if (isTurningOn || isBleTurningOn) {
337                        infoLog("Deferring USER_TURN_OFF request...");
338                        deferMessage(msg);
339                    }
340                    break;
341
342                case BLE_TURN_ON:
343                    if (isTurningOff || isBleTurningOff) {
344                        infoLog("Deferring BLE_TURN_ON request...");
345                        deferMessage(msg);
346                    }
347                    break;
348
349                case BLE_TURN_OFF:
350                    if (isTurningOn || isBleTurningOn) {
351                        infoLog("Deferring BLE_TURN_OFF request...");
352                        deferMessage(msg);
353                    }
354                    break;
355
356                case BLE_STARTED:
357                    //Remove start timeout
358                    removeMessages(BLE_START_TIMEOUT);
359
360                    //Enable
361                    boolean isGuest = UserManager.get(mAdapterService).isGuestUser();
362                    if (!adapterService.enableNative(isGuest)) {
363                        errorLog("Error while turning Bluetooth on");
364                        notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
365                        transitionTo(mOffState);
366                    } else {
367                        sendMessageDelayed(ENABLE_TIMEOUT, ENABLE_TIMEOUT_DELAY);
368                    }
369                    break;
370
371                case BREDR_STARTED:
372                    //Remove start timeout
373                    removeMessages(BREDR_START_TIMEOUT);
374                    adapterProperties.onBluetoothReady();
375                    mPendingCommandState.setTurningOn(false);
376                    transitionTo(mOnState);
377                    notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
378                    break;
379
380                case ENABLED_READY:
381                    removeMessages(ENABLE_TIMEOUT);
382                    mPendingCommandState.setBleTurningOn(false);
383                    transitionTo(mBleOnState);
384                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
385                    break;
386
387                case SET_SCAN_MODE_TIMEOUT:
388                     warningLog("Timeout while setting scan mode. Continuing with disable...");
389                     //Fall through
390                case BEGIN_DISABLE:
391                    removeMessages(SET_SCAN_MODE_TIMEOUT);
392                    sendMessageDelayed(BREDR_STOP_TIMEOUT, BREDR_STOP_TIMEOUT_DELAY);
393                    adapterService.stopProfileServices();
394                    break;
395
396                case DISABLED:
397                    if (isTurningOn) {
398                        removeMessages(ENABLE_TIMEOUT);
399                        errorLog("Error enabling Bluetooth - hardware init failed?");
400                        mPendingCommandState.setTurningOn(false);
401                        transitionTo(mOffState);
402                        adapterService.stopProfileServices();
403                        notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
404                        break;
405                    }
406                    removeMessages(DISABLE_TIMEOUT);
407                    sendMessageDelayed(BLE_STOP_TIMEOUT, BLE_STOP_TIMEOUT_DELAY);
408                    if (adapterService.stopGattProfileService()) {
409                        debugLog("Stopping Gatt profile services that were post enabled");
410                        break;
411                    }
412                    //Fall through if no services or services already stopped
413                case BLE_STOPPED:
414                    removeMessages(BLE_STOP_TIMEOUT);
415                    setBleTurningOff(false);
416                    transitionTo(mOffState);
417                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
418                    break;
419
420                case BREDR_STOPPED:
421                    removeMessages(BREDR_STOP_TIMEOUT);
422                    setTurningOff(false);
423                    transitionTo(mBleOnState);
424                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
425                    break;
426
427                case BLE_START_TIMEOUT:
428                    errorLog("Error enabling Bluetooth (BLE start timeout)");
429                    mPendingCommandState.setBleTurningOn(false);
430                    transitionTo(mOffState);
431                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
432                    break;
433
434                case BREDR_START_TIMEOUT:
435                    errorLog("Error enabling Bluetooth (start timeout)");
436                    mPendingCommandState.setTurningOn(false);
437                    adapterService.stopProfileServices();
438                    transitionTo(mBleOnState);
439                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
440                    break;
441
442                case ENABLE_TIMEOUT:
443                    errorLog("Error enabling Bluetooth (enable timeout)");
444                    mPendingCommandState.setBleTurningOn(false);
445                    transitionTo(mOffState);
446                    adapterService.stopProfileServices();
447                    adapterService.stopGattProfileService();
448                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
449                    break;
450
451                case BREDR_STOP_TIMEOUT:
452                    errorLog("Error stopping Bluetooth profiles (stop timeout)");
453                    mPendingCommandState.setTurningOff(false);
454                    transitionTo(mBleOnState);
455                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
456                    break;
457
458                case BLE_STOP_TIMEOUT:
459                    errorLog("Error stopping Bluetooth profiles (BLE stop timeout)");
460                    mPendingCommandState.setTurningOff(false);
461                    transitionTo(mOffState);
462                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
463                    break;
464
465                case DISABLE_TIMEOUT:
466                    errorLog("Error disabling Bluetooth (disable timeout)");
467                    if (isTurningOn)
468                        mPendingCommandState.setTurningOn(false);
469                    adapterService.stopProfileServices();
470                    adapterService.stopGattProfileService();
471                    mPendingCommandState.setTurningOff(false);
472                    setBleTurningOff(false);
473                    transitionTo(mOffState);
474                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
475                    break;
476
477                default:
478                    return false;
479            }
480            return true;
481        }
482
483        private void logTransientStates() {
484            StringBuilder sb = new StringBuilder();
485            sb.append("PendingCommand - transient state(s):");
486
487            if (isTurningOn()) sb.append(" isTurningOn");
488            if (isTurningOff()) sb.append(" isTurningOff");
489            if (isBleTurningOn()) sb.append(" isBleTurningOn");
490            if (isBleTurningOff()) sb.append(" isBleTurningOff");
491
492            verboseLog(sb.toString());
493        }
494    }
495
496    private void notifyAdapterStateChange(int newState) {
497        AdapterService adapterService = mAdapterService;
498        AdapterProperties adapterProperties = mAdapterProperties;
499        if ((adapterService == null) || (adapterProperties == null)) {
500            errorLog("notifyAdapterStateChange after cleanup:" + newState);
501            return;
502        }
503
504        int oldState = adapterProperties.getState();
505        adapterProperties.setState(newState);
506        infoLog("Bluetooth adapter state changed: " + oldState + "-> " + newState);
507        adapterService.updateAdapterState(oldState, newState);
508    }
509
510    void stateChangeCallback(int status) {
511        if (status == AbstractionLayer.BT_STATE_OFF) {
512            sendMessage(DISABLED);
513
514        } else if (status == AbstractionLayer.BT_STATE_ON) {
515            // We should have got the property change for adapter and remote devices.
516            sendMessage(ENABLED_READY);
517
518        } else {
519            errorLog("Incorrect status in stateChangeCallback");
520        }
521    }
522
523    private void infoLog(String msg) {
524        if (DBG) Log.i(TAG, msg);
525    }
526
527    private void debugLog(String msg) {
528        if (DBG) Log.d(TAG, msg);
529    }
530
531    private void warningLog(String msg) {
532        if (DBG) Log.w(TAG, msg);
533    }
534
535    private void verboseLog(String msg) {
536        if (VDBG) Log.v(TAG, msg);
537    }
538
539    private void errorLog(String msg) {
540        Log.e(TAG, msg);
541    }
542
543}
544