AdapterState.java revision c4fbd756e2645147470c486ae96f2253f5e13a52
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    private void cleanup() {
123        if (mAdapterProperties != null) {
124            mAdapterProperties = null;
125        }
126        if (mAdapterService != null) {
127            mAdapterService = null;
128        }
129    }
130
131    @Override
132    protected void onQuitting() {
133        cleanup();
134    }
135
136    private class OffState extends State {
137        @Override
138        public void enter() {
139            infoLog("Entering OffState");
140        }
141
142        @Override
143        public boolean processMessage(Message msg) {
144            AdapterService adapterService = mAdapterService;
145            if (adapterService == null) {
146                errorLog("Received message in OffState after cleanup: " + msg.what);
147                return false;
148            }
149
150            debugLog("Current state: OFF, message: " + msg.what);
151
152            switch (msg.what) {
153                case BLE_TURN_ON:
154                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON);
155                    mPendingCommandState.setBleTurningOn(true);
156                    transitionTo(mPendingCommandState);
157                    sendMessageDelayed(BLE_START_TIMEOUT, BLE_START_TIMEOUT_DELAY);
158                    adapterService.bleOnProcessStart();
159                    break;
160
161                case USER_TURN_OFF:
162                    //TODO: Handle case of service started and stopped without enable
163                    break;
164
165                default:
166                    return false;
167            }
168            return true;
169        }
170    }
171
172    private class BleOnState extends State {
173        @Override
174        public void enter() {
175            infoLog("Entering BleOnState");
176        }
177
178        @Override
179        public boolean processMessage(Message msg) {
180
181            AdapterService adapterService = mAdapterService;
182            AdapterProperties adapterProperties = mAdapterProperties;
183            if ((adapterService == null) || (adapterProperties == null)) {
184                errorLog("Received message in BleOnState after cleanup: " + msg.what);
185                return false;
186            }
187
188            debugLog("Current state: BLE ON, message: " + msg.what);
189
190            switch (msg.what) {
191                case USER_TURN_ON:
192                    notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON);
193                    mPendingCommandState.setTurningOn(true);
194                    transitionTo(mPendingCommandState);
195                    sendMessageDelayed(BREDR_START_TIMEOUT, BREDR_START_TIMEOUT_DELAY);
196                    adapterService.startCoreServices();
197                    break;
198
199                case USER_TURN_OFF:
200                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_TURNING_OFF);
201                    mPendingCommandState.setBleTurningOff(true);
202                    adapterProperties.onBleDisable();
203                    transitionTo(mPendingCommandState);
204                    sendMessageDelayed(DISABLE_TIMEOUT, DISABLE_TIMEOUT_DELAY);
205                    boolean ret = adapterService.disableNative();
206                    if (!ret) {
207                        removeMessages(DISABLE_TIMEOUT);
208                        errorLog("Error while calling disableNative");
209                        //FIXME: what about post enable services
210                        mPendingCommandState.setBleTurningOff(false);
211                        notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
212                    }
213                    break;
214
215                default:
216                    return false;
217            }
218            return true;
219        }
220    }
221
222    private class OnState extends State {
223        @Override
224        public void enter() {
225            infoLog("Entering OnState");
226
227            AdapterService adapterService = mAdapterService;
228            if (adapterService == null) {
229                errorLog("Entered OnState after cleanup");
230                return;
231            }
232            adapterService.updateUuids();
233        }
234
235        @Override
236        public boolean processMessage(Message msg) {
237            AdapterProperties adapterProperties = mAdapterProperties;
238            if (adapterProperties == null) {
239                errorLog("Received message in OnState after cleanup: " + msg.what);
240                return false;
241            }
242
243            debugLog("Current state: ON, message: " + msg.what);
244
245            switch (msg.what) {
246                case BLE_TURN_OFF:
247                    notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_OFF);
248                    mPendingCommandState.setTurningOff(true);
249                    transitionTo(mPendingCommandState);
250
251                    // Invoke onBluetoothDisable which shall trigger a
252                    // setScanMode to SCAN_MODE_NONE
253                    Message m = obtainMessage(SET_SCAN_MODE_TIMEOUT);
254                    sendMessageDelayed(m, PROPERTY_OP_DELAY);
255                    adapterProperties.onBluetoothDisable();
256                    break;
257
258                case USER_TURN_ON:
259                    break;
260
261                default:
262                    return false;
263            }
264            return true;
265        }
266    }
267
268    private class PendingCommandState extends State {
269        private boolean mIsTurningOn;
270        private boolean mIsTurningOff;
271        private boolean mIsBleTurningOn;
272        private boolean mIsBleTurningOff;
273
274        @Override
275        public void enter() {
276            infoLog("Entering PendingCommandState");
277        }
278
279        public void setTurningOn(boolean isTurningOn) {
280            mIsTurningOn = isTurningOn;
281        }
282
283        public boolean isTurningOn() {
284            return mIsTurningOn;
285        }
286
287        public void setTurningOff(boolean isTurningOff) {
288            mIsTurningOff = isTurningOff;
289        }
290
291        public boolean isTurningOff() {
292            return mIsTurningOff;
293        }
294
295        public void setBleTurningOn(boolean isBleTurningOn) {
296            mIsBleTurningOn = isBleTurningOn;
297        }
298
299        public boolean isBleTurningOn() {
300            return mIsBleTurningOn;
301        }
302
303        public void setBleTurningOff(boolean isBleTurningOff) {
304            mIsBleTurningOff = isBleTurningOff;
305        }
306
307        public boolean isBleTurningOff() {
308            return mIsBleTurningOff;
309        }
310
311        @Override
312        public boolean processMessage(Message msg) {
313
314            /* Cache current states */
315            /* TODO(eisenbach): Not sure why this is done at all.
316             * Seems like the mIs* variables should be protected,
317             * or really, removed. Which reminds me: This file needs
318             * a serious refactor...*/
319            boolean isTurningOn = isTurningOn();
320            boolean isTurningOff = isTurningOff();
321            boolean isBleTurningOn = isBleTurningOn();
322            boolean isBleTurningOff = isBleTurningOff();
323
324            logTransientStates();
325
326            AdapterService adapterService = mAdapterService;
327            AdapterProperties adapterProperties = mAdapterProperties;
328            if ((adapterService == null) || (adapterProperties == null)) {
329                errorLog("Received message in PendingCommandState after cleanup: " + msg.what);
330                return false;
331            }
332
333            debugLog("Current state: PENDING_COMMAND, message: " + msg.what);
334
335            switch (msg.what) {
336                case USER_TURN_ON:
337                    if (isBleTurningOff
338                            || isTurningOff) { //TODO:do we need to send it after ble turn off
339                        // also??
340                        infoLog("Deferring USER_TURN_ON request...");
341                        deferMessage(msg);
342                    }
343                    break;
344
345                case USER_TURN_OFF:
346                    if (isTurningOn || isBleTurningOn) {
347                        infoLog("Deferring USER_TURN_OFF request...");
348                        deferMessage(msg);
349                    }
350                    break;
351
352                case BLE_TURN_ON:
353                    if (isTurningOff || isBleTurningOff) {
354                        infoLog("Deferring BLE_TURN_ON request...");
355                        deferMessage(msg);
356                    }
357                    break;
358
359                case BLE_TURN_OFF:
360                    if (isTurningOn || isBleTurningOn) {
361                        infoLog("Deferring BLE_TURN_OFF request...");
362                        deferMessage(msg);
363                    }
364                    break;
365
366                case BLE_STARTED:
367                    //Remove start timeout
368                    removeMessages(BLE_START_TIMEOUT);
369
370                    //Enable
371                    boolean isGuest = UserManager.get(mAdapterService).isGuestUser();
372                    if (!adapterService.enableNative(isGuest)) {
373                        errorLog("Error while turning Bluetooth on");
374                        notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
375                        transitionTo(mOffState);
376                    } else {
377                        sendMessageDelayed(ENABLE_TIMEOUT, ENABLE_TIMEOUT_DELAY);
378                    }
379                    break;
380
381                case BREDR_STARTED:
382                    //Remove start timeout
383                    removeMessages(BREDR_START_TIMEOUT);
384                    adapterProperties.onBluetoothReady();
385                    mPendingCommandState.setTurningOn(false);
386                    transitionTo(mOnState);
387                    notifyAdapterStateChange(BluetoothAdapter.STATE_ON);
388                    break;
389
390                case ENABLED_READY:
391                    removeMessages(ENABLE_TIMEOUT);
392                    mPendingCommandState.setBleTurningOn(false);
393                    transitionTo(mBleOnState);
394                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
395                    break;
396
397                case SET_SCAN_MODE_TIMEOUT:
398                    warningLog("Timeout while setting scan mode. Continuing with disable...");
399                    //Fall through
400                case BEGIN_DISABLE:
401                    removeMessages(SET_SCAN_MODE_TIMEOUT);
402                    sendMessageDelayed(BREDR_STOP_TIMEOUT, BREDR_STOP_TIMEOUT_DELAY);
403                    adapterService.stopProfileServices();
404                    break;
405
406                case DISABLED:
407                    if (isTurningOn) {
408                        removeMessages(ENABLE_TIMEOUT);
409                        errorLog("Error enabling Bluetooth - hardware init failed?");
410                        mPendingCommandState.setTurningOn(false);
411                        transitionTo(mOffState);
412                        adapterService.stopProfileServices();
413                        notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
414                        break;
415                    }
416                    removeMessages(DISABLE_TIMEOUT);
417                    sendMessageDelayed(BLE_STOP_TIMEOUT, BLE_STOP_TIMEOUT_DELAY);
418                    if (adapterService.stopGattProfileService()) {
419                        debugLog("Stopping Gatt profile services that were post enabled");
420                        break;
421                    }
422                    //Fall through if no services or services already stopped
423                case BLE_STOPPED:
424                    removeMessages(BLE_STOP_TIMEOUT);
425                    setBleTurningOff(false);
426                    transitionTo(mOffState);
427                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
428                    break;
429
430                case BREDR_STOPPED:
431                    removeMessages(BREDR_STOP_TIMEOUT);
432                    setTurningOff(false);
433                    transitionTo(mBleOnState);
434                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
435                    break;
436
437                case BLE_START_TIMEOUT:
438                    errorLog("Error enabling Bluetooth (BLE start timeout)");
439                    mPendingCommandState.setBleTurningOn(false);
440                    transitionTo(mOffState);
441                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
442                    break;
443
444                case BREDR_START_TIMEOUT:
445                    errorLog("Error enabling Bluetooth (start timeout)");
446                    mPendingCommandState.setTurningOn(false);
447                    adapterService.stopProfileServices();
448                    transitionTo(mBleOnState);
449                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
450                    break;
451
452                case ENABLE_TIMEOUT:
453                    errorLog("Error enabling Bluetooth (enable timeout)");
454                    mPendingCommandState.setBleTurningOn(false);
455                    transitionTo(mOffState);
456                    adapterService.stopProfileServices();
457                    adapterService.stopGattProfileService();
458                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
459                    break;
460
461                case BREDR_STOP_TIMEOUT:
462                    errorLog("Error stopping Bluetooth profiles (stop timeout)");
463                    mPendingCommandState.setTurningOff(false);
464                    transitionTo(mBleOnState);
465                    notifyAdapterStateChange(BluetoothAdapter.STATE_BLE_ON);
466                    break;
467
468                case BLE_STOP_TIMEOUT:
469                    errorLog("Error stopping Bluetooth profiles (BLE stop timeout)");
470                    mPendingCommandState.setTurningOff(false);
471                    transitionTo(mOffState);
472                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
473                    break;
474
475                case DISABLE_TIMEOUT:
476                    errorLog("Error disabling Bluetooth (disable timeout)");
477                    if (isTurningOn) {
478                        mPendingCommandState.setTurningOn(false);
479                    }
480                    adapterService.stopProfileServices();
481                    adapterService.stopGattProfileService();
482                    mPendingCommandState.setTurningOff(false);
483                    setBleTurningOff(false);
484                    transitionTo(mOffState);
485                    notifyAdapterStateChange(BluetoothAdapter.STATE_OFF);
486                    break;
487
488                default:
489                    return false;
490            }
491            return true;
492        }
493
494        private void logTransientStates() {
495            StringBuilder sb = new StringBuilder();
496            sb.append("PendingCommand - transient state(s):");
497
498            if (isTurningOn()) {
499                sb.append(" isTurningOn");
500            }
501            if (isTurningOff()) {
502                sb.append(" isTurningOff");
503            }
504            if (isBleTurningOn()) {
505                sb.append(" isBleTurningOn");
506            }
507            if (isBleTurningOff()) {
508                sb.append(" isBleTurningOff");
509            }
510
511            verboseLog(sb.toString());
512        }
513    }
514
515    private void notifyAdapterStateChange(int newState) {
516        AdapterService adapterService = mAdapterService;
517        AdapterProperties adapterProperties = mAdapterProperties;
518        if ((adapterService == null) || (adapterProperties == null)) {
519            errorLog("notifyAdapterStateChange after cleanup:" + newState);
520            return;
521        }
522
523        int oldState = adapterProperties.getState();
524        adapterProperties.setState(newState);
525        infoLog("Bluetooth adapter state changed: " + oldState + "-> " + newState);
526        adapterService.updateAdapterState(oldState, newState);
527    }
528
529    void stateChangeCallback(int status) {
530        if (status == AbstractionLayer.BT_STATE_OFF) {
531            sendMessage(DISABLED);
532
533        } else if (status == AbstractionLayer.BT_STATE_ON) {
534            // We should have got the property change for adapter and remote devices.
535            sendMessage(ENABLED_READY);
536
537        } else {
538            errorLog("Incorrect status in stateChangeCallback");
539        }
540    }
541
542    private void infoLog(String msg) {
543        if (DBG) {
544            Log.i(TAG, msg);
545        }
546    }
547
548    private void debugLog(String msg) {
549        if (DBG) {
550            Log.d(TAG, msg);
551        }
552    }
553
554    private void warningLog(String msg) {
555        if (DBG) {
556            Log.w(TAG, msg);
557        }
558    }
559
560    private void verboseLog(String msg) {
561        if (VDBG) {
562            Log.v(TAG, msg);
563        }
564    }
565
566    private void errorLog(String msg) {
567        Log.e(TAG, msg);
568    }
569
570}
571