CallAudioRouteStateMachine.java revision ef15aea6ef54e3a8b04d534b14266b7a9009f085
1/*
2 * Copyright (C) 2015 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.server.telecom;
18
19
20import android.app.ActivityManagerNative;
21import android.content.Context;
22import android.content.pm.UserInfo;
23import android.media.AudioManager;
24import android.media.IAudioService;
25import android.os.Binder;
26import android.os.Message;
27import android.os.RemoteException;
28import android.os.UserHandle;
29import android.telecom.CallAudioState;
30import android.util.SparseArray;
31
32import com.android.internal.util.IState;
33import com.android.internal.util.State;
34import com.android.internal.util.StateMachine;
35
36import java.util.HashMap;
37
38/**
39 * This class describes the available routes of a call as a state machine.
40 * Transitions are caused solely by the commands sent as messages. Possible values for msg.what
41 * are defined as event constants in this file.
42 *
43 * The eight states are all instances of the abstract base class, {@link AudioState}. Each state
44 * is a combination of one of the four audio routes (earpiece, wired headset, bluetooth, and
45 * speakerphone) and audio focus status (active or quiescent).
46 *
47 * Messages are processed first by the processMessage method in the base class, AudioState.
48 * Any messages not completely handled by AudioState are further processed by the same method in
49 * the route-specific abstract classes: {@link EarpieceRoute}, {@link HeadsetRoute},
50 * {@link BluetoothRoute}, and {@link SpeakerRoute}. Finally, messages that are not handled at
51 * this level are then processed by the classes corresponding to the state instances themselves.
52 *
53 * There are several variables carrying additional state. These include:
54 * mAvailableRoutes: A bitmask describing which audio routes are available
55 * mWasOnSpeaker: A boolean indicating whether we should switch to speakerphone after disconnecting
56 *     from a wired headset
57 * mIsMuted: a boolean indicating whether the audio is muted
58 */
59public class CallAudioRouteStateMachine extends StateMachine {
60    /** Direct the audio stream through the device's earpiece. */
61    public static final int ROUTE_EARPIECE      = CallAudioState.ROUTE_EARPIECE;
62
63    /** Direct the audio stream through Bluetooth. */
64    public static final int ROUTE_BLUETOOTH     = CallAudioState.ROUTE_BLUETOOTH;
65
66    /** Direct the audio stream through a wired headset. */
67    public static final int ROUTE_WIRED_HEADSET = CallAudioState.ROUTE_WIRED_HEADSET;
68
69    /** Direct the audio stream through the device's speakerphone. */
70    public static final int ROUTE_SPEAKER       = CallAudioState.ROUTE_SPEAKER;
71
72    /** Valid values for msg.what */
73    public static final int CONNECT_WIRED_HEADSET = 1;
74    public static final int DISCONNECT_WIRED_HEADSET = 2;
75    public static final int CONNECT_BLUETOOTH = 3;
76    public static final int DISCONNECT_BLUETOOTH = 4;
77    public static final int CONNECT_DOCK = 5;
78    public static final int DISCONNECT_DOCK = 6;
79
80    public static final int SWITCH_EARPIECE = 1001;
81    public static final int SWITCH_BLUETOOTH = 1002;
82    public static final int SWITCH_HEADSET = 1003;
83    public static final int SWITCH_SPEAKER = 1004;
84    public static final int SWITCH_WIRED_OR_EARPIECE = 1005;
85
86    public static final int REINITIALIZE = 2001;
87
88    public static final int MUTE_ON = 3001;
89    public static final int MUTE_OFF = 3002;
90    public static final int TOGGLE_MUTE = 3003;
91
92    public static final int SWITCH_FOCUS = 4001;
93
94    // Used in testing to execute verifications. Not compatible with subsessions.
95    public static final int RUN_RUNNABLE = 9001;
96
97    /** Valid values for mAudioFocusType */
98    public static final int NO_FOCUS = 1;
99    public static final int HAS_FOCUS = 2;
100
101    private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
102        put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
103        put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET");
104        put(CONNECT_BLUETOOTH, "CONNECT_BLUETOOTH");
105        put(DISCONNECT_BLUETOOTH, "DISCONNECT_BLUETOOTH");
106        put(CONNECT_DOCK, "CONNECT_DOCK");
107        put(DISCONNECT_DOCK, "DISCONNECT_DOCK");
108
109        put(SWITCH_EARPIECE, "SWITCH_EARPIECE");
110        put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH");
111        put(SWITCH_HEADSET, "SWITCH_HEADSET");
112        put(SWITCH_SPEAKER, "SWITCH_SPEAKER");
113        put(SWITCH_WIRED_OR_EARPIECE, "SWITCH_WIRED_OR_EARPIECE");
114
115        put(REINITIALIZE, "REINITIALIZE");
116
117        put(MUTE_ON, "MUTE_ON");
118        put(MUTE_OFF, "MUTE_OFF");
119        put(TOGGLE_MUTE, "TOGGLE_MUTE");
120
121        put(SWITCH_FOCUS, "SWITCH_FOCUS");
122
123        put(RUN_RUNNABLE, "RUN_RUNNABLE");
124    }};
125
126    private static final String ACTIVE_EARPIECE_ROUTE_NAME = "ActiveEarpieceRoute";
127    private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute";
128    private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute";
129    private static final String ACTIVE_HEADSET_ROUTE_NAME = "ActiveHeadsetRoute";
130    private static final String QUIESCENT_EARPIECE_ROUTE_NAME = "QuiescentEarpieceRoute";
131    private static final String QUIESCENT_BLUETOOTH_ROUTE_NAME = "QuiescentBluetoothRoute";
132    private static final String QUIESCENT_SPEAKER_ROUTE_NAME = "QuiescentSpeakerRoute";
133    private static final String QUIESCENT_HEADSET_ROUTE_NAME = "QuiescentHeadsetRoute";
134
135    public static final String NAME = CallAudioRouteStateMachine.class.getName();
136
137    @Override
138    protected void onPreHandleMessage(Message msg) {
139        if (msg.obj != null && msg.obj instanceof Session) {
140            String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown");
141            Log.continueSession((Session) msg.obj, "CARSM.pM_" + messageCodeName);
142            Log.i(this, "Message received: %s=%d", messageCodeName, msg.what);
143        }
144    }
145
146    @Override
147    protected void onPostHandleMessage(Message msg) {
148        Log.endSession();
149    }
150
151    abstract class AudioState extends State {
152        @Override
153        public void enter() {
154            super.enter();
155            Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
156                    "Entering state " + getName());
157        }
158
159        @Override
160        public void exit() {
161            Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
162                    "Leaving state " + getName());
163            super.exit();
164        }
165
166        @Override
167        public boolean processMessage(Message msg) {
168            switch (msg.what) {
169                case CONNECT_WIRED_HEADSET:
170                    Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
171                            "Wired headset connected");
172                    mAvailableRoutes &= ~ROUTE_EARPIECE;
173                    mAvailableRoutes |= ROUTE_WIRED_HEADSET;
174                    return NOT_HANDLED;
175                case CONNECT_BLUETOOTH:
176                    // This case is here because the bluetooth manager sends out a lot of spurious
177                    // state changes, and no layers above this one can tell which are actual changes
178                    // in connection/disconnection status. This filters it out.
179                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
180                        return HANDLED; // Do nothing if we already have bluetooth as enabled.
181                    } else {
182                        Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
183                                "Bluetooth connected");
184                        mAvailableRoutes |= ROUTE_BLUETOOTH;
185                        return NOT_HANDLED;
186                    }
187                case DISCONNECT_WIRED_HEADSET:
188                    Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
189                            "Wired headset disconnected");
190                    mAvailableRoutes &= ~ROUTE_WIRED_HEADSET;
191                    mAvailableRoutes |= ROUTE_EARPIECE;
192                    return NOT_HANDLED;
193                case DISCONNECT_BLUETOOTH:
194                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) == 0) {
195                        return HANDLED;
196                    } else {
197                        Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
198                                "Bluetooth disconnected");
199                        mAvailableRoutes &= ~ROUTE_BLUETOOTH;
200                        return NOT_HANDLED;
201                    }
202                case SWITCH_WIRED_OR_EARPIECE:
203                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
204                        sendInternalMessage(SWITCH_EARPIECE);
205                    } else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
206                        sendInternalMessage(SWITCH_HEADSET);
207                    } else {
208                        Log.e(this, new IllegalStateException(),
209                                "Neither headset nor earpiece are available. Defaulting to " +
210                                        "earpiece.");
211                        sendInternalMessage(SWITCH_EARPIECE);
212                    }
213                    return HANDLED;
214                case REINITIALIZE:
215                    CallAudioState initState = getInitialAudioState();
216                    mAvailableRoutes = initState.getSupportedRouteMask();
217                    mIsMuted = initState.isMuted();
218                    mWasOnSpeaker = initState.getRoute() == ROUTE_SPEAKER;
219                    transitionTo(mRouteCodeToQuiescentState.get(initState.getRoute()));
220                    return HANDLED;
221                default:
222                    return NOT_HANDLED;
223            }
224        }
225
226        // Behavior will depend on whether the state is an active one or a quiescent one.
227        abstract public void updateSystemAudioState();
228        abstract public boolean isActive();
229    }
230
231    class ActiveEarpieceRoute extends EarpieceRoute {
232        @Override
233        public String getName() {
234            return ACTIVE_EARPIECE_ROUTE_NAME;
235        }
236
237        @Override
238        public boolean isActive() {
239            return true;
240        }
241
242        @Override
243        public void enter() {
244            super.enter();
245            setSpeakerphoneOn(false);
246            setBluetoothOn(false);
247            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
248                    mAvailableRoutes);
249            setSystemAudioState(mCurrentCallAudioState, newState);
250            updateInternalCallAudioState();
251        }
252
253        @Override
254        public void updateSystemAudioState() {
255            CallAudioState oldAudioState = mCurrentCallAudioState;
256            updateInternalCallAudioState();
257            setSystemAudioState(oldAudioState, mCurrentCallAudioState);
258        }
259
260        @Override
261        public boolean processMessage(Message msg) {
262            if (super.processMessage(msg) == HANDLED) {
263                return HANDLED;
264            }
265            switch (msg.what) {
266                case SWITCH_EARPIECE:
267                    // Nothing to do here
268                    return HANDLED;
269                case SWITCH_BLUETOOTH:
270                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
271                        transitionTo(mActiveBluetoothRoute);
272                    } else {
273                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
274                    }
275                    return HANDLED;
276                case SWITCH_HEADSET:
277                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
278                        transitionTo(mActiveHeadsetRoute);
279                    } else {
280                        Log.w(this, "Ignoring switch to headset command. Not available.");
281                    }
282                    return HANDLED;
283                case SWITCH_SPEAKER:
284                    transitionTo(mActiveSpeakerRoute);
285                    return HANDLED;
286                case SWITCH_FOCUS:
287                    if (msg.arg1 == NO_FOCUS) {
288                        transitionTo(mQuiescentEarpieceRoute);
289                    }
290                    return HANDLED;
291                default:
292                    return NOT_HANDLED;
293            }
294        }
295    }
296
297    class QuiescentEarpieceRoute extends EarpieceRoute {
298        @Override
299        public String getName() {
300            return QUIESCENT_EARPIECE_ROUTE_NAME;
301        }
302
303        @Override
304        public boolean isActive() {
305            return false;
306        }
307
308        @Override
309        public void enter() {
310            super.enter();
311            updateInternalCallAudioState();
312        }
313
314        @Override
315        public void updateSystemAudioState() {
316            updateInternalCallAudioState();
317        }
318
319        @Override
320        public boolean processMessage(Message msg) {
321            if (super.processMessage(msg) == HANDLED) {
322                return HANDLED;
323            }
324            switch (msg.what) {
325                case SWITCH_EARPIECE:
326                    // Nothing to do here
327                    return HANDLED;
328                case SWITCH_BLUETOOTH:
329                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
330                        transitionTo(mQuiescentBluetoothRoute);
331                    } else {
332                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
333                    }
334                    return HANDLED;
335                case SWITCH_HEADSET:
336                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
337                        transitionTo(mQuiescentHeadsetRoute);
338                    } else {
339                        Log.w(this, "Ignoring switch to headset command. Not available.");
340                    }
341                    return HANDLED;
342                case SWITCH_SPEAKER:
343                    transitionTo(mQuiescentSpeakerRoute);
344                    return HANDLED;
345                case SWITCH_FOCUS:
346                    if (msg.arg1 == HAS_FOCUS) {
347                        transitionTo(mActiveEarpieceRoute);
348                    }
349                    return HANDLED;
350                default:
351                    return NOT_HANDLED;
352            }
353        }
354    }
355
356    abstract class EarpieceRoute extends AudioState {
357        @Override
358        public boolean processMessage(Message msg) {
359            if (super.processMessage(msg) == HANDLED) {
360                return HANDLED;
361            }
362            switch (msg.what) {
363                case CONNECT_WIRED_HEADSET:
364                    sendInternalMessage(SWITCH_HEADSET);
365                    return HANDLED;
366                case CONNECT_BLUETOOTH:
367                    sendInternalMessage(SWITCH_BLUETOOTH);
368                    return HANDLED;
369                case DISCONNECT_BLUETOOTH:
370                    updateSystemAudioState();
371                    // No change in audio route required
372                    return HANDLED;
373                case DISCONNECT_WIRED_HEADSET:
374                    Log.e(this, new IllegalStateException(),
375                            "Wired headset should not go from connected to not when on " +
376                            "earpiece");
377                    updateSystemAudioState();
378                    return HANDLED;
379                case CONNECT_DOCK:
380                    sendInternalMessage(SWITCH_SPEAKER);
381                    return HANDLED;
382                case DISCONNECT_DOCK:
383                    // Nothing to do here
384                    return HANDLED;
385                default:
386                    return NOT_HANDLED;
387            }
388        }
389    }
390
391    class ActiveHeadsetRoute extends HeadsetRoute {
392        @Override
393        public String getName() {
394            return ACTIVE_HEADSET_ROUTE_NAME;
395        }
396
397        @Override
398        public boolean isActive() {
399            return true;
400        }
401
402        @Override
403        public void enter() {
404            super.enter();
405            setSpeakerphoneOn(false);
406            setBluetoothOn(false);
407            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
408                    mAvailableRoutes);
409            setSystemAudioState(mCurrentCallAudioState, newState);
410            updateInternalCallAudioState();
411        }
412
413        @Override
414        public void updateSystemAudioState() {
415            CallAudioState oldAudioState = mCurrentCallAudioState;
416            updateInternalCallAudioState();
417            setSystemAudioState(oldAudioState, mCurrentCallAudioState);
418        }
419
420        @Override
421        public boolean processMessage(Message msg) {
422            if (super.processMessage(msg) == HANDLED) {
423                return HANDLED;
424            }
425            switch (msg.what) {
426                case SWITCH_EARPIECE:
427                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
428                        transitionTo(mActiveEarpieceRoute);
429                    } else {
430                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
431                    }
432                    return HANDLED;
433                case SWITCH_BLUETOOTH:
434                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
435                        transitionTo(mActiveBluetoothRoute);
436                    } else {
437                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
438                    }
439                    return HANDLED;
440                case SWITCH_HEADSET:
441                    // Nothing to do
442                    return HANDLED;
443                case SWITCH_SPEAKER:
444                    transitionTo(mActiveSpeakerRoute);
445                    return HANDLED;
446                case SWITCH_FOCUS:
447                    if (msg.arg1 == NO_FOCUS) {
448                        transitionTo(mQuiescentHeadsetRoute);
449                    }
450                    return HANDLED;
451                default:
452                    return NOT_HANDLED;
453            }
454        }
455    }
456
457    class QuiescentHeadsetRoute extends HeadsetRoute {
458        @Override
459        public String getName() {
460            return QUIESCENT_HEADSET_ROUTE_NAME;
461        }
462
463        @Override
464        public boolean isActive() {
465            return false;
466        }
467
468        @Override
469        public void enter() {
470            super.enter();
471            updateInternalCallAudioState();
472        }
473
474        @Override
475        public void updateSystemAudioState() {
476            updateInternalCallAudioState();
477        }
478
479        @Override
480        public boolean processMessage(Message msg) {
481            if (super.processMessage(msg) == HANDLED) {
482                return HANDLED;
483            }
484            switch (msg.what) {
485                case SWITCH_EARPIECE:
486                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
487                        transitionTo(mQuiescentEarpieceRoute);
488                    } else {
489                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
490                    }
491                    return HANDLED;
492                case SWITCH_BLUETOOTH:
493                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
494                        transitionTo(mQuiescentBluetoothRoute);
495                    } else {
496                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
497                    }
498                    return HANDLED;
499                case SWITCH_HEADSET:
500                    // Nothing to do
501                    return HANDLED;
502                case SWITCH_SPEAKER:
503                    transitionTo(mQuiescentSpeakerRoute);
504                    return HANDLED;
505                case SWITCH_FOCUS:
506                    if (msg.arg1 == HAS_FOCUS) {
507                        transitionTo(mActiveHeadsetRoute);
508                    }
509                    return HANDLED;
510                default:
511                    return NOT_HANDLED;
512            }
513        }
514    }
515
516    abstract class HeadsetRoute extends AudioState {
517        @Override
518        public boolean processMessage(Message msg) {
519            if (super.processMessage(msg) == HANDLED) {
520                return HANDLED;
521            }
522            switch (msg.what) {
523                case CONNECT_WIRED_HEADSET:
524                    Log.e(this, new IllegalStateException(),
525                            "Wired headset should already be connected.");
526                    mAvailableRoutes |= ROUTE_WIRED_HEADSET;
527                    updateSystemAudioState();
528                    return HANDLED;
529                case CONNECT_BLUETOOTH:
530                    sendInternalMessage(SWITCH_BLUETOOTH);
531                    return HANDLED;
532                case DISCONNECT_BLUETOOTH:
533                    updateSystemAudioState();
534                    // No change in audio route required
535                    return HANDLED;
536                case DISCONNECT_WIRED_HEADSET:
537                    if (mWasOnSpeaker) {
538                        sendInternalMessage(SWITCH_SPEAKER);
539                    } else {
540                        sendInternalMessage(SWITCH_EARPIECE);
541                    }
542                    return HANDLED;
543                case CONNECT_DOCK:
544                    // Nothing to do here
545                    return HANDLED;
546                case DISCONNECT_DOCK:
547                    // Nothing to do here
548                    return HANDLED;
549                default:
550                    return NOT_HANDLED;
551            }
552        }
553    }
554
555    class ActiveBluetoothRoute extends BluetoothRoute {
556        @Override
557        public String getName() {
558            return ACTIVE_BLUETOOTH_ROUTE_NAME;
559        }
560
561        @Override
562        public boolean isActive() {
563            return true;
564        }
565
566        @Override
567        public void enter() {
568            super.enter();
569            setSpeakerphoneOn(false);
570            setBluetoothOn(true);
571            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
572                    mAvailableRoutes);
573            setSystemAudioState(mCurrentCallAudioState, newState);
574            updateInternalCallAudioState();
575        }
576
577        @Override
578        public void updateSystemAudioState() {
579            CallAudioState oldAudioState = mCurrentCallAudioState;
580            updateInternalCallAudioState();
581            setSystemAudioState(oldAudioState, mCurrentCallAudioState);
582        }
583
584        @Override
585        public boolean processMessage(Message msg) {
586            if (super.processMessage(msg) == HANDLED) {
587                return HANDLED;
588            }
589            switch (msg.what) {
590                case SWITCH_EARPIECE:
591                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
592                        transitionTo(mActiveEarpieceRoute);
593                    } else {
594                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
595                    }
596                    return HANDLED;
597                case SWITCH_BLUETOOTH:
598                    // Nothing to do
599                    return HANDLED;
600                case SWITCH_HEADSET:
601                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
602                        transitionTo(mActiveHeadsetRoute);
603                    } else {
604                        Log.w(this, "Ignoring switch to headset command. Not available.");
605                    }
606                    return HANDLED;
607                case SWITCH_SPEAKER:
608                    transitionTo(mActiveSpeakerRoute);
609                    return HANDLED;
610                case SWITCH_FOCUS:
611                    if (msg.arg1 == NO_FOCUS) {
612                        transitionTo(mQuiescentBluetoothRoute);
613                    }
614                    return HANDLED;
615                default:
616                    return NOT_HANDLED;
617            }
618        }
619    }
620
621    class QuiescentBluetoothRoute extends BluetoothRoute {
622        @Override
623        public String getName() {
624            return QUIESCENT_BLUETOOTH_ROUTE_NAME;
625        }
626
627        @Override
628        public boolean isActive() {
629            return false;
630        }
631
632        @Override
633        public void enter() {
634            super.enter();
635            updateInternalCallAudioState();
636        }
637
638        @Override
639        public void updateSystemAudioState() {
640            updateInternalCallAudioState();
641        }
642
643        @Override
644        public boolean processMessage(Message msg) {
645            if (super.processMessage(msg) == HANDLED) {
646                return HANDLED;
647            }
648            switch (msg.what) {
649                case SWITCH_EARPIECE:
650                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
651                        transitionTo(mQuiescentEarpieceRoute);
652                    } else {
653                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
654                    }
655                    return HANDLED;
656                case SWITCH_BLUETOOTH:
657                    // Nothing to do
658                    return HANDLED;
659                case SWITCH_HEADSET:
660                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
661                        transitionTo(mQuiescentHeadsetRoute);
662                    } else {
663                        Log.w(this, "Ignoring switch to headset command. Not available.");
664                    }
665                    return HANDLED;
666                case SWITCH_SPEAKER:
667                    transitionTo(mQuiescentSpeakerRoute);
668                    return HANDLED;
669                case SWITCH_FOCUS:
670                    if (msg.arg1 == HAS_FOCUS) {
671                        transitionTo(mActiveBluetoothRoute);
672                    }
673                    return HANDLED;
674                default:
675                    return NOT_HANDLED;
676            }
677        }
678    }
679
680    abstract class BluetoothRoute extends AudioState {
681        @Override
682        public boolean processMessage(Message msg) {
683            if (super.processMessage(msg) == HANDLED) {
684                return HANDLED;
685            }
686            switch (msg.what) {
687                case CONNECT_WIRED_HEADSET:
688                    sendInternalMessage(SWITCH_HEADSET);
689                    return HANDLED;
690                case CONNECT_BLUETOOTH:
691                    // We can't tell when a change in bluetooth state corresponds to an
692                    // actual connection or disconnection, so we'll just ignore it if we're already
693                    // in the bluetooth route.
694                    return HANDLED;
695                case DISCONNECT_BLUETOOTH:
696                    sendInternalMessage(SWITCH_WIRED_OR_EARPIECE);
697                    mWasOnSpeaker = false;
698                    return HANDLED;
699                case DISCONNECT_WIRED_HEADSET:
700                    updateSystemAudioState();
701                    // No change in audio route required
702                    return HANDLED;
703                case CONNECT_DOCK:
704                    // Nothing to do here
705                    return HANDLED;
706                case DISCONNECT_DOCK:
707                    // Nothing to do here
708                    return HANDLED;
709                default:
710                    return NOT_HANDLED;
711            }
712        }
713    }
714
715    class ActiveSpeakerRoute extends SpeakerRoute {
716        @Override
717        public String getName() {
718            return ACTIVE_SPEAKER_ROUTE_NAME;
719        }
720
721        @Override
722        public boolean isActive() {
723            return true;
724        }
725
726        @Override
727        public void enter() {
728            super.enter();
729            mWasOnSpeaker = true;
730            setSpeakerphoneOn(true);
731            setBluetoothOn(false);
732            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_SPEAKER,
733                    mAvailableRoutes);
734            setSystemAudioState(mCurrentCallAudioState, newState);
735            updateInternalCallAudioState();
736        }
737
738        @Override
739        public void updateSystemAudioState() {
740            CallAudioState oldAudioState = mCurrentCallAudioState;
741            updateInternalCallAudioState();
742            setSystemAudioState(oldAudioState, mCurrentCallAudioState);
743        }
744
745        @Override
746        public boolean processMessage(Message msg) {
747            if (super.processMessage(msg) == HANDLED) {
748                return HANDLED;
749            }
750            switch(msg.what) {
751                case SWITCH_EARPIECE:
752                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
753                        transitionTo(mActiveEarpieceRoute);
754                    } else {
755                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
756                    }
757                    return HANDLED;
758                case SWITCH_BLUETOOTH:
759                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
760                        transitionTo(mActiveBluetoothRoute);
761                    } else {
762                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
763                    }
764                    return HANDLED;
765                case SWITCH_HEADSET:
766                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
767                        transitionTo(mActiveHeadsetRoute);
768                    } else {
769                        Log.w(this, "Ignoring switch to headset command. Not available.");
770                    }
771                    return HANDLED;
772                case SWITCH_SPEAKER:
773                    // Nothing to do
774                    return HANDLED;
775                case SWITCH_FOCUS:
776                    if (msg.arg1 == NO_FOCUS) {
777                        transitionTo(mQuiescentSpeakerRoute);
778                    }
779                    return HANDLED;
780                default:
781                    return NOT_HANDLED;
782            }
783        }
784    }
785
786    class QuiescentSpeakerRoute extends SpeakerRoute {
787        @Override
788        public String getName() {
789            return QUIESCENT_SPEAKER_ROUTE_NAME;
790        }
791
792        @Override
793        public boolean isActive() {
794            return false;
795        }
796
797        @Override
798        public void enter() {
799            super.enter();
800            // Omit setting mWasOnSpeaker to true here, since this does not reflect a call
801            // actually being on speakerphone.
802            updateInternalCallAudioState();
803        }
804
805        @Override
806        public void updateSystemAudioState() {
807            updateInternalCallAudioState();
808        }
809
810        @Override
811        public boolean processMessage(Message msg) {
812            if (super.processMessage(msg) == HANDLED) {
813                return HANDLED;
814            }
815            switch(msg.what) {
816                case SWITCH_EARPIECE:
817                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
818                        transitionTo(mQuiescentEarpieceRoute);
819                    } else {
820                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
821                    }
822                    return HANDLED;
823                case SWITCH_BLUETOOTH:
824                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
825                        transitionTo(mQuiescentBluetoothRoute);
826                    } else {
827                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
828                    }
829                    return HANDLED;
830                case SWITCH_HEADSET:
831                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
832                        transitionTo(mQuiescentHeadsetRoute);
833                    } else {
834                        Log.w(this, "Ignoring switch to headset command. Not available.");
835                    }
836                    return HANDLED;
837                case SWITCH_SPEAKER:
838                    // Nothing to do
839                    return HANDLED;
840                case SWITCH_FOCUS:
841                    if (msg.arg1 == HAS_FOCUS) {
842                        transitionTo(mActiveSpeakerRoute);
843                    }
844                    return HANDLED;
845                default:
846                    return NOT_HANDLED;
847            }
848        }
849    }
850
851    abstract class SpeakerRoute extends AudioState {
852        @Override
853        public boolean processMessage(Message msg) {
854            if (super.processMessage(msg) == HANDLED) {
855                return HANDLED;
856            }
857            switch (msg.what) {
858                case CONNECT_WIRED_HEADSET:
859                    sendInternalMessage(SWITCH_HEADSET);
860                    return HANDLED;
861                case CONNECT_BLUETOOTH:
862                    sendInternalMessage(SWITCH_BLUETOOTH);
863                    return HANDLED;
864                case DISCONNECT_BLUETOOTH:
865                    updateSystemAudioState();
866                    // No change in audio route required
867                    return HANDLED;
868                case DISCONNECT_WIRED_HEADSET:
869                    updateSystemAudioState();
870                    // No change in audio route required
871                    return HANDLED;
872                case CONNECT_DOCK:
873                    // Nothing to do here
874                    return HANDLED;
875                case DISCONNECT_DOCK:
876                    sendInternalMessage(SWITCH_WIRED_OR_EARPIECE);
877                    return HANDLED;
878               default:
879                    return NOT_HANDLED;
880            }
881        }
882    }
883
884    private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute();
885    private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
886    private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
887    private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute();
888    private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute();
889    private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
890    private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
891    private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
892
893    /**
894     * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
895     * states
896     */
897    private int mAvailableRoutes;
898    private boolean mWasOnSpeaker;
899    private boolean mIsMuted;
900
901    private final Context mContext;
902    private final CallsManager mCallsManager;
903    private final AudioManager mAudioManager;
904    private final BluetoothManager mBluetoothManager;
905    private final WiredHeadsetManager mWiredHeadsetManager;
906    private final StatusBarNotifier mStatusBarNotifier;
907    private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
908
909    private HashMap<String, Integer> mStateNameToRouteCode;
910    private HashMap<Integer, AudioState> mRouteCodeToQuiescentState;
911
912    // CallAudioState is used as an interface to communicate with many other system components.
913    // No internal state transitions should depend on this variable.
914    private CallAudioState mCurrentCallAudioState;
915
916    public CallAudioRouteStateMachine(
917            Context context,
918            CallsManager callsManager,
919            BluetoothManager bluetoothManager,
920            WiredHeadsetManager wiredHeadsetManager,
921            StatusBarNotifier statusBarNotifier,
922            CallAudioManager.AudioServiceFactory audioServiceFactory) {
923        super(NAME);
924        addState(mActiveEarpieceRoute);
925        addState(mActiveHeadsetRoute);
926        addState(mActiveBluetoothRoute);
927        addState(mActiveSpeakerRoute);
928        addState(mQuiescentEarpieceRoute);
929        addState(mQuiescentHeadsetRoute);
930        addState(mQuiescentBluetoothRoute);
931        addState(mQuiescentSpeakerRoute);
932
933        mContext = context;
934        mCallsManager = callsManager;
935        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
936        mBluetoothManager = bluetoothManager;
937        mWiredHeadsetManager = wiredHeadsetManager;
938        mStatusBarNotifier = statusBarNotifier;
939        mAudioServiceFactory = audioServiceFactory;
940
941        mStateNameToRouteCode = new HashMap<>(8);
942        mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE);
943        mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH);
944        mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
945        mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER);
946        mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE);
947        mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
948        mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
949        mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER);
950
951        mRouteCodeToQuiescentState = new HashMap<>(4);
952        mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute);
953        mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute);
954        mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute);
955        mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute);
956    }
957
958    /**
959     * Initializes the state machine with info on initial audio route, supported audio routes,
960     * and mute status.
961     */
962    public void initialize() {
963        CallAudioState initState = getInitialAudioState();
964        initialize(initState);
965    }
966
967    public void initialize(CallAudioState initState) {
968        mCurrentCallAudioState = initState;
969        mAvailableRoutes = initState.getSupportedRouteMask();
970        mIsMuted = initState.isMuted();
971        mWasOnSpeaker = initState.getRoute() == ROUTE_SPEAKER;
972
973        mStatusBarNotifier.notifyMute(initState.isMuted());
974        mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
975        setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
976        start();
977    }
978
979    /**
980     * Getter for the current CallAudioState object that the state machine is keeping track of.
981     * Used for compatibility purposes.
982     */
983    public CallAudioState getCurrentCallAudioState() {
984        return mCurrentCallAudioState;
985    }
986
987    public void sendMessageWithSessionInfo(int message, int arg) {
988        sendMessage(message, arg, 0, Log.createSubsession());
989    }
990
991    public void sendMessageWithSessionInfo(int message) {
992        sendMessage(message, 0, 0, Log.createSubsession());
993    }
994
995    /**
996     * This is for state-independent changes in audio route (i.e. muting or runnables)
997     * @param msg that couldn't be handled.
998     */
999    @Override
1000    protected void unhandledMessage(Message msg) {
1001        CallAudioState newCallAudioState;
1002        switch (msg.what) {
1003            case MUTE_ON:
1004                setMuteOn(true);
1005                newCallAudioState = new CallAudioState(mIsMuted,
1006                        mCurrentCallAudioState.getRoute(),
1007                        mAvailableRoutes);
1008                setSystemAudioState(mCurrentCallAudioState, newCallAudioState);
1009                updateInternalCallAudioState();
1010                return;
1011            case MUTE_OFF:
1012                setMuteOn(false);
1013                newCallAudioState = new CallAudioState(mIsMuted,
1014                        mCurrentCallAudioState.getRoute(),
1015                        mAvailableRoutes);
1016                setSystemAudioState(mCurrentCallAudioState, newCallAudioState);
1017                updateInternalCallAudioState();
1018                return;
1019            case TOGGLE_MUTE:
1020                if (mIsMuted) {
1021                    sendInternalMessage(MUTE_OFF);
1022                } else {
1023                    sendInternalMessage(MUTE_ON);
1024                }
1025                return;
1026            case RUN_RUNNABLE:
1027                Runnable r = (Runnable) msg.obj;
1028                r.run();
1029                return;
1030            default:
1031                Log.e(this, new IllegalStateException(),
1032                        "Unexpected message code");
1033        }
1034    }
1035
1036    public void quitStateMachine() {
1037        quitNow();
1038    }
1039
1040    private void setSpeakerphoneOn(boolean on) {
1041        if (mAudioManager.isSpeakerphoneOn() != on) {
1042            Log.i(this, "turning speaker phone %s", on);
1043            mAudioManager.setSpeakerphoneOn(on);
1044        }
1045    }
1046
1047    private void setBluetoothOn(boolean on) {
1048        if (mBluetoothManager.isBluetoothAvailable()) {
1049            boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending();
1050            if (on != isAlreadyOn) {
1051                Log.i(this, "connecting bluetooth %s", on);
1052                if (on) {
1053                    mBluetoothManager.connectBluetoothAudio();
1054                } else {
1055                    mBluetoothManager.disconnectBluetoothAudio();
1056                }
1057            }
1058        }
1059    }
1060
1061    private void setMuteOn(boolean mute) {
1062        mIsMuted = mute;
1063        Log.event(mCallsManager.getForegroundCall(), Log.Events.MUTE,
1064                mute ? "on" : "off");
1065        if (mute != mAudioManager.isMicrophoneMute() && isInActiveState()) {
1066            IAudioService audio = mAudioServiceFactory.getAudioService();
1067            Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]",
1068                    mute, audio == null);
1069            if (audio != null) {
1070                try {
1071                    // We use the audio service directly here so that we can specify
1072                    // the current user. Telecom runs in the system_server process which
1073                    // may run as a separate user from the foreground user. If we
1074                    // used AudioManager directly, we would change mute for the system's
1075                    // user and not the current foreground, which we want to avoid.
1076                    audio.setMicrophoneMute(
1077                            mute, mContext.getOpPackageName(), getCurrentUserId());
1078
1079                } catch (RemoteException e) {
1080                    Log.e(this, e, "Remote exception while toggling mute.");
1081                }
1082                // TODO: Check microphone state after attempting to set to ensure that
1083                // our state corroborates AudioManager's state.
1084            }
1085        }
1086    }
1087
1088    /**
1089     * Updates the CallAudioState object from current internal state. The result is used for
1090     * external communication only.
1091     */
1092    private void updateInternalCallAudioState() {
1093        IState currentState = getCurrentState();
1094        if (currentState == null) {
1095            Log.e(this, new IllegalStateException(), "Current state should never be null" +
1096                    " when updateInternalCallAudioState is called.");
1097            mCurrentCallAudioState = new CallAudioState(
1098                    mIsMuted, mCurrentCallAudioState.getRoute(), mAvailableRoutes);
1099            return;
1100        }
1101        int currentRoute = mStateNameToRouteCode.get(currentState.getName());
1102        mCurrentCallAudioState = new CallAudioState(mIsMuted, currentRoute, mAvailableRoutes);
1103    }
1104
1105    private void setSystemAudioState(CallAudioState oldCallAudioState,
1106            CallAudioState newCallAudioState) {
1107        Log.i(this, "setSystemAudioState: changing from %s to %s", oldCallAudioState,
1108                newCallAudioState);
1109        Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
1110                CallAudioState.audioRouteToString(newCallAudioState.getRoute()));
1111
1112        if (!oldCallAudioState.equals(newCallAudioState)) {
1113            mCallsManager.onCallAudioStateChanged(oldCallAudioState, newCallAudioState);
1114            updateAudioForForegroundCall(newCallAudioState);
1115        }
1116    }
1117
1118    private void updateAudioForForegroundCall(CallAudioState newCallAudioState) {
1119        Call call = mCallsManager.getForegroundCall();
1120        if (call != null && call.getConnectionService() != null) {
1121            call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
1122        }
1123    }
1124
1125    private int calculateSupportedRoutes() {
1126        int routeMask = CallAudioState.ROUTE_SPEAKER;
1127
1128        if (mWiredHeadsetManager.isPluggedIn()) {
1129            routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
1130        } else {
1131            routeMask |= CallAudioState.ROUTE_EARPIECE;
1132        }
1133
1134        if (mBluetoothManager.isBluetoothAvailable()) {
1135            routeMask |=  CallAudioState.ROUTE_BLUETOOTH;
1136        }
1137
1138        return routeMask;
1139    }
1140
1141    private void sendInternalMessage(int messageCode) {
1142        // Internal messages are messages which the state machine sends to itself in the
1143        // course of processing externally-sourced messages. We want to send these messages at
1144        // the front of the queue in order to make actions appear atomic to the user and to
1145        // prevent scenarios such as these:
1146        // 1. State machine handler thread is suspended for some reason.
1147        // 2. Headset gets connected (sends CONNECT_HEADSET).
1148        // 3. User switches to speakerphone in the UI (sends SWITCH_SPEAKER).
1149        // 4. State machine handler is un-suspended.
1150        // 5. State machine handler processes the CONNECT_HEADSET message and sends
1151        //    SWITCH_HEADSET at end of queue.
1152        // 6. State machine handler processes SWITCH_SPEAKER.
1153        // 7. State machine handler processes SWITCH_HEADSET.
1154        Session subsession = Log.createSubsession();
1155        if(subsession != null) {
1156            sendMessageAtFrontOfQueue(messageCode, subsession);
1157        } else {
1158            sendMessageAtFrontOfQueue(messageCode);
1159        }
1160    }
1161
1162    private CallAudioState getInitialAudioState() {
1163        int supportedRouteMask = calculateSupportedRoutes();
1164        int route = (supportedRouteMask & ROUTE_WIRED_HEADSET) != 0
1165                ? ROUTE_WIRED_HEADSET : ROUTE_EARPIECE;
1166        if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0) {
1167            route = ROUTE_BLUETOOTH;
1168        }
1169
1170        return new CallAudioState(false, route, supportedRouteMask);
1171    }
1172
1173    private int getCurrentUserId() {
1174        final long ident = Binder.clearCallingIdentity();
1175        try {
1176            UserInfo currentUser = ActivityManagerNative.getDefault().getCurrentUser();
1177            return currentUser.id;
1178        } catch (RemoteException e) {
1179            // Activity manager not running, nothing we can do assume user 0.
1180        } finally {
1181            Binder.restoreCallingIdentity(ident);
1182        }
1183        return UserHandle.USER_OWNER;
1184    }
1185
1186    private boolean isInActiveState() {
1187        AudioState currentState = (AudioState) getCurrentState();
1188        if (currentState == null) {
1189            Log.w(this, "Current state is null, assuming inactive state");
1190            return false;
1191        }
1192        return currentState.isActive();
1193    }
1194}