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