1/*
2 * Copyright (C) 2011 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 android.server;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.IBluetoothStateChangeCallback;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.os.Binder;
25import android.os.Message;
26import android.os.RemoteException;
27import android.provider.Settings;
28import android.util.Log;
29
30import com.android.internal.util.IState;
31import com.android.internal.util.State;
32import com.android.internal.util.StateMachine;
33
34import java.io.PrintWriter;
35
36/**
37 * Bluetooth Adapter StateMachine
38 * All the states are at the same level, ie, no hierarchy.
39 *                         (BluetootOn)<----------------------<-
40 *                           |    ^    -------------------->-  |
41 *                           |    |                         |  |
42 *                 TURN_OFF  |    | SCAN_MODE_CHANGED    m1 |  | USER_TURN_ON
43 *         AIRPLANE_MODE_ON  |    |                         |  |
44 *                           V    |                         |  |
45 *                         (Switching)                   (PerProcessState)
46 *                           |    ^                         |  |
47 *     POWER_STATE_CHANGED & |    | TURN_ON(_CONTINUE)      |  |
48 * ALL_DEVICES_DISCONNECTED  |    |                     m2  |  |
49 *                           V    |------------------------<   | SCAN_MODE_CHANGED
50 *                          (HotOff)-------------------------->- PER_PROCESS_TURN_ON
51 *                           /    ^
52 *                          /     |  SERVICE_RECORD_LOADED
53 *                         |      |
54 *              TURN_COLD  |   (Warmup)
55 *                         \      ^
56 *                          \     |  TURN_HOT/TURN_ON
57 *                           |    |  AIRPLANE_MODE_OFF(when Bluetooth was on before)
58 *                           V    |
59 *                           (PowerOff)   <----- initial state
60 *
61 * Legend:
62 * m1 = TURN_HOT
63 * m2 = Transition to HotOff when number of process wanting BT on is 0.
64 *      POWER_STATE_CHANGED will make the transition.
65 */
66final class BluetoothAdapterStateMachine extends StateMachine {
67    private static final String TAG = "BluetoothAdapterStateMachine";
68    private static final boolean DBG = false;
69
70    // Message(what) to take an action
71    //
72    // We get this message when user tries to turn on BT
73    static final int USER_TURN_ON = 1;
74    // We get this message when user tries to turn off BT
75    static final int USER_TURN_OFF = 2;
76    // Per process enable / disable messages
77    static final int PER_PROCESS_TURN_ON = 3;
78    static final int PER_PROCESS_TURN_OFF = 4;
79
80    // Turn on Bluetooth Module, Load firmware, and do all the preparation
81    // needed to get the Bluetooth Module ready but keep it not discoverable
82    // and not connectable. This way the Bluetooth Module can be quickly
83    // switched on if needed
84    static final int TURN_HOT = 5;
85
86    // Message(what) to report a event that the state machine need to respond to
87    //
88    // Event indicates sevice records have been loaded
89    static final int SERVICE_RECORD_LOADED = 51;
90    // Event indicates all the remote Bluetooth devices has been disconnected
91    static final int ALL_DEVICES_DISCONNECTED = 52;
92    // Event indicates the Bluetooth scan mode has changed
93    static final int SCAN_MODE_CHANGED = 53;
94    // Event indicates the powered state has changed
95    static final int POWER_STATE_CHANGED = 54;
96    // Event indicates airplane mode is turned on
97    static final int AIRPLANE_MODE_ON = 55;
98    // Event indicates airplane mode is turned off
99    static final int AIRPLANE_MODE_OFF = 56;
100
101    // private internal messages
102    //
103    // USER_TURN_ON is changed to TURN_ON_CONTINUE after we broadcast the
104    // state change intent so that we will not broadcast the intent again in
105    // other state
106    private static final int TURN_ON_CONTINUE = 101;
107    // Unload firmware, turning off Bluetooth module power
108    private static final int TURN_COLD = 102;
109    // Device disconnecting timeout happens
110    private static final int DEVICES_DISCONNECT_TIMEOUT = 103;
111    // Prepare Bluetooth timeout happens
112    private static final int PREPARE_BLUETOOTH_TIMEOUT = 104;
113    // Bluetooth Powerdown timeout happens
114    private static final int POWER_DOWN_TIMEOUT = 105;
115
116    private Context mContext;
117    private BluetoothService mBluetoothService;
118    private BluetoothEventLoop mEventLoop;
119
120    private BluetoothOn mBluetoothOn;
121    private Switching mSwitching;
122    private HotOff mHotOff;
123    private WarmUp mWarmUp;
124    private PowerOff mPowerOff;
125    private PerProcessState mPerProcessState;
126
127    // this is the BluetoothAdapter state that reported externally
128    private int mPublicState;
129
130    // timeout value waiting for all the devices to be disconnected
131    private static final int DEVICES_DISCONNECT_TIMEOUT_TIME = 3000;
132
133    private static final int PREPARE_BLUETOOTH_TIMEOUT_TIME = 10000;
134
135    private static final int POWER_DOWN_TIMEOUT_TIME = 5000;
136
137    BluetoothAdapterStateMachine(Context context, BluetoothService bluetoothService,
138                                 BluetoothAdapter bluetoothAdapter) {
139        super(TAG);
140        mContext = context;
141        mBluetoothService = bluetoothService;
142        mEventLoop = new BluetoothEventLoop(context, bluetoothAdapter, bluetoothService, this);
143
144        mBluetoothOn = new BluetoothOn();
145        mSwitching = new Switching();
146        mHotOff = new HotOff();
147        mWarmUp = new WarmUp();
148        mPowerOff = new PowerOff();
149        mPerProcessState = new PerProcessState();
150
151        addState(mBluetoothOn);
152        addState(mSwitching);
153        addState(mHotOff);
154        addState(mWarmUp);
155        addState(mPowerOff);
156        addState(mPerProcessState);
157
158        setInitialState(mPowerOff);
159        mPublicState = BluetoothAdapter.STATE_OFF;
160    }
161
162    /**
163     * Bluetooth module's power is off, firmware is not loaded.
164     */
165    private class PowerOff extends State {
166        @Override
167        public void enter() {
168            if (DBG) log("Enter PowerOff: " + getCurrentMessage().what);
169        }
170        @Override
171        public boolean processMessage(Message message) {
172            log("PowerOff process message: " + message.what);
173
174            boolean retValue = HANDLED;
175            switch(message.what) {
176                case USER_TURN_ON:
177                    // starts turning on BT module, broadcast this out
178                    broadcastState(BluetoothAdapter.STATE_TURNING_ON);
179                    transitionTo(mWarmUp);
180                    if (prepareBluetooth()) {
181                        // this is user request, save the setting
182                        if ((Boolean) message.obj) {
183                            persistSwitchSetting(true);
184                        }
185                        // We will continue turn the BT on all the way to the BluetoothOn state
186                        deferMessage(obtainMessage(TURN_ON_CONTINUE));
187                    } else {
188                        Log.e(TAG, "failed to prepare bluetooth, abort turning on");
189                        transitionTo(mPowerOff);
190                        broadcastState(BluetoothAdapter.STATE_OFF);
191                    }
192                    break;
193                case TURN_HOT:
194                    if (prepareBluetooth()) {
195                        transitionTo(mWarmUp);
196                    }
197                    break;
198                case AIRPLANE_MODE_OFF:
199                    if (getBluetoothPersistedSetting()) {
200                        // starts turning on BT module, broadcast this out
201                        broadcastState(BluetoothAdapter.STATE_TURNING_ON);
202                        transitionTo(mWarmUp);
203                        if (prepareBluetooth()) {
204                            // We will continue turn the BT on all the way to the BluetoothOn state
205                            deferMessage(obtainMessage(TURN_ON_CONTINUE));
206                            transitionTo(mWarmUp);
207                        } else {
208                            Log.e(TAG, "failed to prepare bluetooth, abort turning on");
209                            transitionTo(mPowerOff);
210                            broadcastState(BluetoothAdapter.STATE_OFF);
211                        }
212                    } else if (mContext.getResources().getBoolean
213                            (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
214                        sendMessage(TURN_HOT);
215                    }
216                    break;
217                case PER_PROCESS_TURN_ON:
218                    if (prepareBluetooth()) {
219                        transitionTo(mWarmUp);
220                    }
221                    deferMessage(obtainMessage(PER_PROCESS_TURN_ON));
222                    break;
223                case PER_PROCESS_TURN_OFF:
224                    perProcessCallback(false, (IBluetoothStateChangeCallback) message.obj);
225                    break;
226                case USER_TURN_OFF:
227                    Log.w(TAG, "PowerOff received: " + message.what);
228                case AIRPLANE_MODE_ON: // ignore
229                    break;
230                default:
231                    return NOT_HANDLED;
232            }
233            return retValue;
234        }
235
236        /**
237         * Turn on Bluetooth Module, Load firmware, and do all the preparation
238         * needed to get the Bluetooth Module ready but keep it not discoverable
239         * and not connectable.
240         * The last step of this method sets up the local service record DB.
241         * There will be a event reporting the status of the SDP setup.
242         */
243        private boolean prepareBluetooth() {
244            if (mBluetoothService.enableNative() != 0) {
245                return false;
246            }
247
248            // try to start event loop, give 2 attempts
249            int retryCount = 2;
250            boolean eventLoopStarted = false;
251            while ((retryCount-- > 0) && !eventLoopStarted) {
252                mEventLoop.start();
253                // it may take a moment for the other thread to do its
254                // thing.  Check periodically for a while.
255                int pollCount = 5;
256                while ((pollCount-- > 0) && !eventLoopStarted) {
257                    if (mEventLoop.isEventLoopRunning()) {
258                        eventLoopStarted = true;
259                        break;
260                    }
261                    try {
262                        Thread.sleep(100);
263                    } catch (InterruptedException e) {
264                        log("prepareBluetooth sleep interrupted: " + pollCount);
265                        break;
266                    }
267                }
268            }
269
270            if (!eventLoopStarted) {
271                mBluetoothService.disableNative();
272                return false;
273            }
274
275            // get BluetoothService ready
276            if (!mBluetoothService.prepareBluetooth()) {
277                mEventLoop.stop();
278                mBluetoothService.disableNative();
279                return false;
280            }
281
282            sendMessageDelayed(PREPARE_BLUETOOTH_TIMEOUT, PREPARE_BLUETOOTH_TIMEOUT_TIME);
283            return true;
284        }
285    }
286
287    /**
288     * Turning on Bluetooth module's power, loading firmware, starting
289     * event loop thread to listen on Bluetooth module event changes.
290     */
291    private class WarmUp extends State {
292
293        @Override
294        public void enter() {
295            if (DBG) log("Enter WarmUp: " + getCurrentMessage().what);
296        }
297
298        @Override
299        public boolean processMessage(Message message) {
300            log("WarmUp process message: " + message.what);
301
302            boolean retValue = HANDLED;
303            switch(message.what) {
304                case SERVICE_RECORD_LOADED:
305                    removeMessages(PREPARE_BLUETOOTH_TIMEOUT);
306                    transitionTo(mHotOff);
307                    break;
308                case PREPARE_BLUETOOTH_TIMEOUT:
309                    Log.e(TAG, "Bluetooth adapter SDP failed to load");
310                    shutoffBluetooth();
311                    transitionTo(mPowerOff);
312                    broadcastState(BluetoothAdapter.STATE_OFF);
313                    break;
314                case USER_TURN_ON: // handle this at HotOff state
315                case TURN_ON_CONTINUE: // Once in HotOff state, continue turn bluetooth
316                                       // on to the BluetoothOn state
317                case AIRPLANE_MODE_ON:
318                case AIRPLANE_MODE_OFF:
319                case PER_PROCESS_TURN_ON:
320                case PER_PROCESS_TURN_OFF:
321                    deferMessage(message);
322                    break;
323                case USER_TURN_OFF:
324                    Log.w(TAG, "WarmUp received: " + message.what);
325                    break;
326                default:
327                    return NOT_HANDLED;
328            }
329            return retValue;
330        }
331
332    }
333
334    /**
335     * Bluetooth Module has powered, firmware loaded, event loop started,
336     * SDP loaded, but the modules stays non-discoverable and
337     * non-connectable.
338     */
339    private class HotOff extends State {
340        @Override
341        public void enter() {
342            if (DBG) log("Enter HotOff: " + getCurrentMessage().what);
343        }
344
345        @Override
346        public boolean processMessage(Message message) {
347            log("HotOff process message: " + message.what);
348
349            boolean retValue = HANDLED;
350            switch(message.what) {
351                case USER_TURN_ON:
352                    broadcastState(BluetoothAdapter.STATE_TURNING_ON);
353                    if ((Boolean) message.obj) {
354                        persistSwitchSetting(true);
355                    }
356                    // let it fall to TURN_ON_CONTINUE:
357                    //$FALL-THROUGH$
358                case TURN_ON_CONTINUE:
359                    mBluetoothService.switchConnectable(true);
360                    transitionTo(mSwitching);
361                    break;
362                case AIRPLANE_MODE_ON:
363                case TURN_COLD:
364                    shutoffBluetooth();
365                    transitionTo(mPowerOff);
366                    broadcastState(BluetoothAdapter.STATE_OFF);
367                    break;
368                case AIRPLANE_MODE_OFF:
369                    if (getBluetoothPersistedSetting()) {
370                        broadcastState(BluetoothAdapter.STATE_TURNING_ON);
371                        transitionTo(mSwitching);
372                        mBluetoothService.switchConnectable(true);
373                    }
374                    break;
375                case PER_PROCESS_TURN_ON:
376                    transitionTo(mPerProcessState);
377
378                    // Resend the PER_PROCESS_TURN_ON message so that the callback
379                    // can be sent through.
380                    deferMessage(message);
381
382                    mBluetoothService.switchConnectable(true);
383                    break;
384                case PER_PROCESS_TURN_OFF:
385                    perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj);
386                    break;
387                case USER_TURN_OFF: // ignore
388                    break;
389                case POWER_STATE_CHANGED:
390                    if ((Boolean) message.obj) {
391                        recoverStateMachine(TURN_HOT, null);
392                    }
393                    break;
394                default:
395                    return NOT_HANDLED;
396            }
397            return retValue;
398        }
399
400    }
401
402    private class Switching extends State {
403
404        @Override
405        public void enter() {
406            if (DBG) log("Enter Switching: " + getCurrentMessage().what);
407        }
408        @Override
409        public boolean processMessage(Message message) {
410            log("Switching process message: " + message.what);
411
412            boolean retValue = HANDLED;
413            switch(message.what) {
414                case SCAN_MODE_CHANGED:
415                    // This event matches mBluetoothService.switchConnectable action
416                    if (mPublicState == BluetoothAdapter.STATE_TURNING_ON) {
417                        // set pairable if it's not
418                        mBluetoothService.setPairable();
419                        mBluetoothService.initBluetoothAfterTurningOn();
420                        transitionTo(mBluetoothOn);
421                        broadcastState(BluetoothAdapter.STATE_ON);
422                        // run bluetooth now that it's turned on
423                        // Note runBluetooth should be called only in adapter STATE_ON
424                        mBluetoothService.runBluetooth();
425                    }
426                    break;
427                case POWER_STATE_CHANGED:
428                    removeMessages(POWER_DOWN_TIMEOUT);
429                    if (!((Boolean) message.obj)) {
430                        if (mPublicState == BluetoothAdapter.STATE_TURNING_OFF) {
431                            transitionTo(mHotOff);
432                            finishSwitchingOff();
433                            if (!mContext.getResources().getBoolean
434                            (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
435                                deferMessage(obtainMessage(TURN_COLD));
436                            }
437                        }
438                    } else {
439                        if (mPublicState != BluetoothAdapter.STATE_TURNING_ON) {
440                            if (mContext.getResources().getBoolean
441                            (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
442                                recoverStateMachine(TURN_HOT, null);
443                            } else {
444                                recoverStateMachine(TURN_COLD, null);
445                            }
446                        }
447                    }
448                    break;
449                case ALL_DEVICES_DISCONNECTED:
450                    removeMessages(DEVICES_DISCONNECT_TIMEOUT);
451                    mBluetoothService.switchConnectable(false);
452                    sendMessageDelayed(POWER_DOWN_TIMEOUT, POWER_DOWN_TIMEOUT_TIME);
453                    break;
454                case DEVICES_DISCONNECT_TIMEOUT:
455                    sendMessage(ALL_DEVICES_DISCONNECTED);
456                    // reset the hardware for error recovery
457                    Log.e(TAG, "Devices failed to disconnect, reseting...");
458                    deferMessage(obtainMessage(TURN_COLD));
459                    if (mContext.getResources().getBoolean
460                        (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
461                        deferMessage(obtainMessage(TURN_HOT));
462                    }
463                    break;
464                case POWER_DOWN_TIMEOUT:
465                    transitionTo(mHotOff);
466                    finishSwitchingOff();
467                    // reset the hardware for error recovery
468                    Log.e(TAG, "Devices failed to power down, reseting...");
469                    deferMessage(obtainMessage(TURN_COLD));
470                    if (mContext.getResources().getBoolean
471                        (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
472                        deferMessage(obtainMessage(TURN_HOT));
473                    }
474                    break;
475                case USER_TURN_ON:
476                case AIRPLANE_MODE_OFF:
477                case AIRPLANE_MODE_ON:
478                case PER_PROCESS_TURN_ON:
479                case PER_PROCESS_TURN_OFF:
480                case USER_TURN_OFF:
481                    deferMessage(message);
482                    break;
483
484                default:
485                    return NOT_HANDLED;
486            }
487            return retValue;
488        }
489    }
490
491    private class BluetoothOn extends State {
492
493        @Override
494        public void enter() {
495            if (DBG) log("Enter BluetoothOn: " + getCurrentMessage().what);
496        }
497        @Override
498        public boolean processMessage(Message message) {
499            log("BluetoothOn process message: " + message.what);
500
501            boolean retValue = HANDLED;
502            switch(message.what) {
503                case USER_TURN_OFF:
504                    if ((Boolean) message.obj) {
505                        persistSwitchSetting(false);
506                    }
507
508                    if (mBluetoothService.isDiscovering()) {
509                        mBluetoothService.cancelDiscovery();
510                    }
511                    if (!mBluetoothService.isApplicationStateChangeTrackerEmpty()) {
512                        transitionTo(mPerProcessState);
513                        deferMessage(obtainMessage(TURN_HOT));
514                        break;
515                    }
516                    //$FALL-THROUGH$ to AIRPLANE_MODE_ON
517                case AIRPLANE_MODE_ON:
518                    broadcastState(BluetoothAdapter.STATE_TURNING_OFF);
519                    transitionTo(mSwitching);
520                    if (mBluetoothService.getAdapterConnectionState() !=
521                        BluetoothAdapter.STATE_DISCONNECTED) {
522                        mBluetoothService.disconnectDevices();
523                        sendMessageDelayed(DEVICES_DISCONNECT_TIMEOUT,
524                                           DEVICES_DISCONNECT_TIMEOUT_TIME);
525                    } else {
526                        mBluetoothService.switchConnectable(false);
527                        sendMessageDelayed(POWER_DOWN_TIMEOUT, POWER_DOWN_TIMEOUT_TIME);
528                    }
529
530                    // we turn all the way to PowerOff with AIRPLANE_MODE_ON
531                    if (message.what == AIRPLANE_MODE_ON) {
532                        // We inform all the per process callbacks
533                        allProcessesCallback(false);
534                        deferMessage(obtainMessage(AIRPLANE_MODE_ON));
535                    }
536                    break;
537                case AIRPLANE_MODE_OFF:
538                case USER_TURN_ON:
539                    Log.w(TAG, "BluetoothOn received: " + message.what);
540                    break;
541                case PER_PROCESS_TURN_ON:
542                    perProcessCallback(true, (IBluetoothStateChangeCallback)message.obj);
543                    break;
544                case PER_PROCESS_TURN_OFF:
545                    perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj);
546                    break;
547                case POWER_STATE_CHANGED:
548                    if ((Boolean) message.obj) {
549                        // reset the state machine and send it TURN_ON_CONTINUE message
550                        recoverStateMachine(USER_TURN_ON, false);
551                    }
552                    break;
553                default:
554                    return NOT_HANDLED;
555            }
556            return retValue;
557        }
558
559    }
560
561
562    private class PerProcessState extends State {
563        IBluetoothStateChangeCallback mCallback = null;
564        boolean isTurningOn = false;
565
566        @Override
567        public void enter() {
568            int what = getCurrentMessage().what;
569            if (DBG) log("Enter PerProcessState: " + what);
570
571            if (what == PER_PROCESS_TURN_ON) {
572                isTurningOn = true;
573            } else if (what == USER_TURN_OFF) {
574                isTurningOn = false;
575            } else {
576                Log.e(TAG, "enter PerProcessState: wrong msg: " + what);
577            }
578        }
579
580        @Override
581        public boolean processMessage(Message message) {
582            log("PerProcessState process message: " + message.what);
583
584            boolean retValue = HANDLED;
585            switch (message.what) {
586                case PER_PROCESS_TURN_ON:
587                    mCallback = (IBluetoothStateChangeCallback)getCurrentMessage().obj;
588
589                    // If this is not the first application call the callback.
590                    if (mBluetoothService.getNumberOfApplicationStateChangeTrackers() > 1) {
591                        perProcessCallback(true, mCallback);
592                    }
593                    break;
594                case SCAN_MODE_CHANGED:
595                    if (isTurningOn) {
596                        perProcessCallback(true, mCallback);
597                        isTurningOn = false;
598                    }
599                    break;
600                case POWER_STATE_CHANGED:
601                    removeMessages(POWER_DOWN_TIMEOUT);
602                    if (!((Boolean) message.obj)) {
603                        transitionTo(mHotOff);
604                        if (!mContext.getResources().getBoolean
605                            (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
606                            deferMessage(obtainMessage(TURN_COLD));
607                        }
608                    } else {
609                        if (!isTurningOn) {
610                            recoverStateMachine(TURN_COLD, null);
611                            for (IBluetoothStateChangeCallback c:
612                                     mBluetoothService.getApplicationStateChangeCallbacks()) {
613                                perProcessCallback(false, c);
614                                deferMessage(obtainMessage(PER_PROCESS_TURN_ON, c));
615                            }
616                        }
617                    }
618                    break;
619                case POWER_DOWN_TIMEOUT:
620                    transitionTo(mHotOff);
621                    Log.e(TAG, "Power-down timed out, resetting...");
622                    deferMessage(obtainMessage(TURN_COLD));
623                    if (mContext.getResources().getBoolean
624                        (com.android.internal.R.bool.config_bluetooth_adapter_quick_switch)) {
625                        deferMessage(obtainMessage(TURN_HOT));
626                    }
627                    break;
628                case USER_TURN_ON:
629                    broadcastState(BluetoothAdapter.STATE_TURNING_ON);
630                    persistSwitchSetting(true);
631                    mBluetoothService.initBluetoothAfterTurningOn();
632                    transitionTo(mBluetoothOn);
633                    broadcastState(BluetoothAdapter.STATE_ON);
634                    // run bluetooth now that it's turned on
635                    mBluetoothService.runBluetooth();
636                    break;
637                case TURN_HOT:
638                    broadcastState(BluetoothAdapter.STATE_TURNING_OFF);
639                    if (mBluetoothService.getAdapterConnectionState() !=
640                        BluetoothAdapter.STATE_DISCONNECTED) {
641                        mBluetoothService.disconnectDevices();
642                        sendMessageDelayed(DEVICES_DISCONNECT_TIMEOUT,
643                                           DEVICES_DISCONNECT_TIMEOUT_TIME);
644                        break;
645                    }
646                    //$FALL-THROUGH$ all devices are already disconnected
647                case ALL_DEVICES_DISCONNECTED:
648                    removeMessages(DEVICES_DISCONNECT_TIMEOUT);
649                    finishSwitchingOff();
650                    break;
651                case DEVICES_DISCONNECT_TIMEOUT:
652                    finishSwitchingOff();
653                    Log.e(TAG, "Devices fail to disconnect, reseting...");
654                    transitionTo(mHotOff);
655                    deferMessage(obtainMessage(TURN_COLD));
656                    for (IBluetoothStateChangeCallback c:
657                             mBluetoothService.getApplicationStateChangeCallbacks()) {
658                        perProcessCallback(false, c);
659                        deferMessage(obtainMessage(PER_PROCESS_TURN_ON, c));
660                    }
661                    break;
662                case PER_PROCESS_TURN_OFF:
663                    perProcessCallback(false, (IBluetoothStateChangeCallback)message.obj);
664                    if (mBluetoothService.isApplicationStateChangeTrackerEmpty()) {
665                        mBluetoothService.switchConnectable(false);
666                        sendMessageDelayed(POWER_DOWN_TIMEOUT, POWER_DOWN_TIMEOUT_TIME);
667                    }
668                    break;
669                case AIRPLANE_MODE_ON:
670                    mBluetoothService.switchConnectable(false);
671                    sendMessageDelayed(POWER_DOWN_TIMEOUT, POWER_DOWN_TIMEOUT_TIME);
672                    allProcessesCallback(false);
673                    // we turn all the way to PowerOff with AIRPLANE_MODE_ON
674                    deferMessage(obtainMessage(AIRPLANE_MODE_ON));
675                    break;
676                case USER_TURN_OFF:
677                    Log.w(TAG, "PerProcessState received: " + message.what);
678                    break;
679                default:
680                    return NOT_HANDLED;
681            }
682            return retValue;
683        }
684    }
685
686    private void finishSwitchingOff() {
687        mBluetoothService.finishDisable();
688        broadcastState(BluetoothAdapter.STATE_OFF);
689        mBluetoothService.cleanupAfterFinishDisable();
690    }
691
692    private void shutoffBluetooth() {
693        mBluetoothService.shutoffBluetooth();
694        mEventLoop.stop();
695        mBluetoothService.cleanNativeAfterShutoffBluetooth();
696    }
697
698    private void perProcessCallback(boolean on, IBluetoothStateChangeCallback c) {
699        if (c == null) return;
700
701        try {
702            c.onBluetoothStateChange(on);
703        } catch (RemoteException e) {}
704    }
705
706    private void allProcessesCallback(boolean on) {
707        for (IBluetoothStateChangeCallback c:
708             mBluetoothService.getApplicationStateChangeCallbacks()) {
709            perProcessCallback(on, c);
710        }
711        if (!on) {
712            mBluetoothService.clearApplicationStateChangeTracker();
713        }
714    }
715
716    /**
717     * Return the public BluetoothAdapter state
718     */
719    int getBluetoothAdapterState() {
720        return mPublicState;
721    }
722
723    BluetoothEventLoop getBluetoothEventLoop() {
724        return mEventLoop;
725    }
726
727    private void persistSwitchSetting(boolean setOn) {
728        long origCallerIdentityToken = Binder.clearCallingIdentity();
729        Settings.Secure.putInt(mContext.getContentResolver(),
730                               Settings.Secure.BLUETOOTH_ON,
731                               setOn ? 1 : 0);
732        Binder.restoreCallingIdentity(origCallerIdentityToken);
733    }
734
735    private boolean getBluetoothPersistedSetting() {
736        ContentResolver contentResolver = mContext.getContentResolver();
737        return (Settings.Secure.getInt(contentResolver,
738                                       Settings.Secure.BLUETOOTH_ON, 0) > 0);
739    }
740
741    private void broadcastState(int newState) {
742
743        log("Bluetooth state " + mPublicState + " -> " + newState);
744        if (mPublicState == newState) {
745            return;
746        }
747
748        Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
749        intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mPublicState);
750        intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState);
751        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
752        mPublicState = newState;
753
754        mContext.sendBroadcast(intent, BluetoothService.BLUETOOTH_PERM);
755    }
756
757    /**
758     * bluetoothd has crashed and recovered, the adapter state machine has to
759     * reset itself and try to return to previous state
760     */
761    private void recoverStateMachine(int what, Object obj) {
762        Log.e(TAG, "Get unexpected power on event, reset with: " + what);
763        transitionTo(mHotOff);
764        deferMessage(obtainMessage(TURN_COLD));
765        deferMessage(obtainMessage(what, obj));
766    }
767
768    private void dump(PrintWriter pw) {
769        IState currentState = getCurrentState();
770        if (currentState == mPowerOff) {
771            pw.println("Bluetooth OFF - power down\n");
772        } else if (currentState == mWarmUp) {
773            pw.println("Bluetooth OFF - warm up\n");
774        } else if (currentState == mHotOff) {
775            pw.println("Bluetooth OFF - hot but off\n");
776        } else if (currentState == mSwitching) {
777            pw.println("Bluetooth Switching\n");
778        } else if (currentState == mBluetoothOn) {
779            pw.println("Bluetooth ON\n");
780        } else {
781            pw.println("ERROR: Bluetooth UNKNOWN STATE ");
782        }
783    }
784
785    private static void log(String msg) {
786        Log.d(TAG, msg);
787    }
788}
789