CallAudioRouteStateMachine.java revision 629ea10deba347f47ff4b79d1afc14c22d700d68
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.ActivityManager;
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.SystemProperties;
29import android.os.UserHandle;
30import android.telecom.CallAudioState;
31import android.telecom.Log;
32import android.telecom.Logging.Session;
33import android.util.SparseArray;
34
35import com.android.internal.annotations.VisibleForTesting;
36import com.android.internal.util.IState;
37import com.android.internal.util.IndentingPrintWriter;
38import com.android.internal.util.State;
39import com.android.internal.util.StateMachine;
40import com.android.server.telecom.bluetooth.BluetoothRouteManager;
41
42import java.util.HashMap;
43
44/**
45 * This class describes the available routes of a call as a state machine.
46 * Transitions are caused solely by the commands sent as messages. Possible values for msg.what
47 * are defined as event constants in this file.
48 *
49 * The eight states are all instances of the abstract base class, {@link AudioState}. Each state
50 * is a combination of one of the four audio routes (earpiece, wired headset, bluetooth, and
51 * speakerphone) and audio focus status (active or quiescent).
52 *
53 * Messages are processed first by the processMessage method in the base class, AudioState.
54 * Any messages not completely handled by AudioState are further processed by the same method in
55 * the route-specific abstract classes: {@link EarpieceRoute}, {@link HeadsetRoute},
56 * {@link BluetoothRoute}, and {@link SpeakerRoute}. Finally, messages that are not handled at
57 * this level are then processed by the classes corresponding to the state instances themselves.
58 *
59 * There are several variables carrying additional state. These include:
60 * mAvailableRoutes: A bitmask describing which audio routes are available
61 * mWasOnSpeaker: A boolean indicating whether we should switch to speakerphone after disconnecting
62 *     from a wired headset
63 * mIsMuted: a boolean indicating whether the audio is muted
64 */
65public class CallAudioRouteStateMachine extends StateMachine {
66    private static final String TELECOM_PACKAGE =
67            CallAudioRouteStateMachine.class.getPackage().getName();
68
69    /** Direct the audio stream through the device's earpiece. */
70    public static final int ROUTE_EARPIECE      = CallAudioState.ROUTE_EARPIECE;
71
72    /** Direct the audio stream through Bluetooth. */
73    public static final int ROUTE_BLUETOOTH     = CallAudioState.ROUTE_BLUETOOTH;
74
75    /** Direct the audio stream through a wired headset. */
76    public static final int ROUTE_WIRED_HEADSET = CallAudioState.ROUTE_WIRED_HEADSET;
77
78    /** Direct the audio stream through the device's speakerphone. */
79    public static final int ROUTE_SPEAKER       = CallAudioState.ROUTE_SPEAKER;
80
81    /** Valid values for msg.what */
82    public static final int CONNECT_WIRED_HEADSET = 1;
83    public static final int DISCONNECT_WIRED_HEADSET = 2;
84    public static final int CONNECT_BLUETOOTH = 3;
85    public static final int DISCONNECT_BLUETOOTH = 4;
86    public static final int CONNECT_DOCK = 5;
87    public static final int DISCONNECT_DOCK = 6;
88
89    public static final int SWITCH_EARPIECE = 1001;
90    public static final int SWITCH_BLUETOOTH = 1002;
91    public static final int SWITCH_HEADSET = 1003;
92    public static final int SWITCH_SPEAKER = 1004;
93    // Wired headset, earpiece, or speakerphone, in that order of precedence.
94    public static final int SWITCH_BASELINE_ROUTE = 1005;
95    public static final int BT_AUDIO_DISCONNECT = 1006;
96
97    public static final int USER_SWITCH_EARPIECE = 1101;
98    public static final int USER_SWITCH_BLUETOOTH = 1102;
99    public static final int USER_SWITCH_HEADSET = 1103;
100    public static final int USER_SWITCH_SPEAKER = 1104;
101    public static final int USER_SWITCH_BASELINE_ROUTE = 1105;
102
103    public static final int UPDATE_SYSTEM_AUDIO_ROUTE = 1201;
104
105    public static final int MUTE_ON = 3001;
106    public static final int MUTE_OFF = 3002;
107    public static final int TOGGLE_MUTE = 3003;
108
109    public static final int SWITCH_FOCUS = 4001;
110
111    // Used in testing to execute verifications. Not compatible with subsessions.
112    public static final int RUN_RUNNABLE = 9001;
113
114    /** Valid values for mAudioFocusType */
115    public static final int NO_FOCUS = 1;
116    public static final int ACTIVE_FOCUS = 2;
117    public static final int RINGING_FOCUS = 3;
118
119    /** Valid values for the argument for SWITCH_BASELINE_ROUTE */
120    public static final int NO_INCLUDE_BLUETOOTH_IN_BASELINE = 0;
121    public static final int INCLUDE_BLUETOOTH_IN_BASELINE = 1;
122
123    @VisibleForTesting
124    public static final SparseArray<String> AUDIO_ROUTE_TO_LOG_EVENT = new SparseArray<String>() {{
125        put(CallAudioState.ROUTE_BLUETOOTH, LogUtils.Events.AUDIO_ROUTE_BT);
126        put(CallAudioState.ROUTE_EARPIECE, LogUtils.Events.AUDIO_ROUTE_EARPIECE);
127        put(CallAudioState.ROUTE_SPEAKER, LogUtils.Events.AUDIO_ROUTE_SPEAKER);
128        put(CallAudioState.ROUTE_WIRED_HEADSET, LogUtils.Events.AUDIO_ROUTE_HEADSET);
129    }};
130
131    private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
132        put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
133        put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET");
134        put(CONNECT_BLUETOOTH, "CONNECT_BLUETOOTH");
135        put(DISCONNECT_BLUETOOTH, "DISCONNECT_BLUETOOTH");
136        put(CONNECT_DOCK, "CONNECT_DOCK");
137        put(DISCONNECT_DOCK, "DISCONNECT_DOCK");
138
139        put(SWITCH_EARPIECE, "SWITCH_EARPIECE");
140        put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH");
141        put(SWITCH_HEADSET, "SWITCH_HEADSET");
142        put(SWITCH_SPEAKER, "SWITCH_SPEAKER");
143        put(SWITCH_BASELINE_ROUTE, "SWITCH_BASELINE_ROUTE");
144        put(BT_AUDIO_DISCONNECT, "BT_AUDIO_DISCONNECT");
145
146        put(USER_SWITCH_EARPIECE, "USER_SWITCH_EARPIECE");
147        put(USER_SWITCH_BLUETOOTH, "USER_SWITCH_BLUETOOTH");
148        put(USER_SWITCH_HEADSET, "USER_SWITCH_HEADSET");
149        put(USER_SWITCH_SPEAKER, "USER_SWITCH_SPEAKER");
150        put(USER_SWITCH_BASELINE_ROUTE, "USER_SWITCH_BASELINE_ROUTE");
151
152        put(UPDATE_SYSTEM_AUDIO_ROUTE, "UPDATE_SYSTEM_AUDIO_ROUTE");
153
154        put(MUTE_ON, "MUTE_ON");
155        put(MUTE_OFF, "MUTE_OFF");
156        put(TOGGLE_MUTE, "TOGGLE_MUTE");
157
158        put(SWITCH_FOCUS, "SWITCH_FOCUS");
159
160        put(RUN_RUNNABLE, "RUN_RUNNABLE");
161    }};
162
163    private static final String ACTIVE_EARPIECE_ROUTE_NAME = "ActiveEarpieceRoute";
164    private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute";
165    private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute";
166    private static final String ACTIVE_HEADSET_ROUTE_NAME = "ActiveHeadsetRoute";
167    private static final String RINGING_BLUETOOTH_ROUTE_NAME = "RingingBluetoothRoute";
168    private static final String QUIESCENT_EARPIECE_ROUTE_NAME = "QuiescentEarpieceRoute";
169    private static final String QUIESCENT_BLUETOOTH_ROUTE_NAME = "QuiescentBluetoothRoute";
170    private static final String QUIESCENT_SPEAKER_ROUTE_NAME = "QuiescentSpeakerRoute";
171    private static final String QUIESCENT_HEADSET_ROUTE_NAME = "QuiescentHeadsetRoute";
172
173    public static final String NAME = CallAudioRouteStateMachine.class.getName();
174
175    @Override
176    protected void onPreHandleMessage(Message msg) {
177        if (msg.obj != null && msg.obj instanceof Session) {
178            String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown");
179            Log.continueSession((Session) msg.obj, "CARSM.pM_" + messageCodeName);
180            Log.i(this, "Message received: %s=%d, arg1=%d", messageCodeName, msg.what, msg.arg1);
181        }
182    }
183
184    @Override
185    protected void onPostHandleMessage(Message msg) {
186        Log.endSession();
187    }
188
189    abstract class AudioState extends State {
190        @Override
191        public void enter() {
192            super.enter();
193            Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
194                    "Entering state " + getName());
195            if (isActive()) {
196                Log.addEvent(mCallsManager.getForegroundCall(),
197                        AUDIO_ROUTE_TO_LOG_EVENT.get(getRouteCode(), LogUtils.Events.AUDIO_ROUTE));
198            }
199        }
200
201        @Override
202        public void exit() {
203            Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
204                    "Leaving state " + getName());
205            super.exit();
206        }
207
208        @Override
209        public boolean processMessage(Message msg) {
210            int addedRoutes = 0;
211            int removedRoutes = 0;
212
213            switch (msg.what) {
214                case CONNECT_WIRED_HEADSET:
215                    Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
216                            "Wired headset connected");
217                    removedRoutes |= ROUTE_EARPIECE;
218                    addedRoutes |= ROUTE_WIRED_HEADSET;
219                    break;
220                case CONNECT_BLUETOOTH:
221                    Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
222                            "Bluetooth connected");
223                    addedRoutes |= ROUTE_BLUETOOTH;
224                    break;
225                case DISCONNECT_WIRED_HEADSET:
226                    Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
227                            "Wired headset disconnected");
228                    removedRoutes |= ROUTE_WIRED_HEADSET;
229                    if (mDoesDeviceSupportEarpieceRoute) {
230                        addedRoutes |= ROUTE_EARPIECE;
231                    }
232                    break;
233                case DISCONNECT_BLUETOOTH:
234                    Log.addEvent(mCallsManager.getForegroundCall(), LogUtils.Events.AUDIO_ROUTE,
235                            "Bluetooth disconnected");
236                    removedRoutes |= ROUTE_BLUETOOTH;
237                    break;
238                case SWITCH_BASELINE_ROUTE:
239                    sendInternalMessage(calculateBaselineRouteMessage(false,
240                            msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE));
241                    return HANDLED;
242                case USER_SWITCH_BASELINE_ROUTE:
243                    sendInternalMessage(calculateBaselineRouteMessage(true,
244                            msg.arg1 == INCLUDE_BLUETOOTH_IN_BASELINE));
245                    return HANDLED;
246                case SWITCH_FOCUS:
247                    mAudioFocusType = msg.arg1;
248                    return NOT_HANDLED;
249                default:
250                    return NOT_HANDLED;
251            }
252
253            if (addedRoutes != 0 || removedRoutes != 0) {
254                mAvailableRoutes = modifyRoutes(mAvailableRoutes, removedRoutes, addedRoutes, true);
255                mDeviceSupportedRoutes = modifyRoutes(mDeviceSupportedRoutes, removedRoutes,
256                        addedRoutes, false);
257            }
258
259            return NOT_HANDLED;
260        }
261
262        // Behavior will depend on whether the state is an active one or a quiescent one.
263        abstract public void updateSystemAudioState();
264        abstract public boolean isActive();
265        abstract public int getRouteCode();
266    }
267
268    class ActiveEarpieceRoute extends EarpieceRoute {
269        @Override
270        public String getName() {
271            return ACTIVE_EARPIECE_ROUTE_NAME;
272        }
273
274        @Override
275        public boolean isActive() {
276            return true;
277        }
278
279        @Override
280        public void enter() {
281            super.enter();
282            setSpeakerphoneOn(false);
283            setBluetoothOn(false);
284            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
285                    mAvailableRoutes);
286            setSystemAudioState(newState, true);
287            updateInternalCallAudioState();
288        }
289
290        @Override
291        public void updateSystemAudioState() {
292            updateInternalCallAudioState();
293            setSystemAudioState(mCurrentCallAudioState);
294        }
295
296        @Override
297        public boolean processMessage(Message msg) {
298            if (super.processMessage(msg) == HANDLED) {
299                return HANDLED;
300            }
301            switch (msg.what) {
302                case SWITCH_EARPIECE:
303                case USER_SWITCH_EARPIECE:
304                    // Nothing to do here
305                    return HANDLED;
306                case SWITCH_BLUETOOTH:
307                case USER_SWITCH_BLUETOOTH:
308                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
309                        transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
310                                mActiveBluetoothRoute : mRingingBluetoothRoute);
311                    } else {
312                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
313                    }
314                    return HANDLED;
315                case SWITCH_HEADSET:
316                case USER_SWITCH_HEADSET:
317                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
318                        transitionTo(mActiveHeadsetRoute);
319                    } else {
320                        Log.w(this, "Ignoring switch to headset command. Not available.");
321                    }
322                    return HANDLED;
323                case SWITCH_SPEAKER:
324                case USER_SWITCH_SPEAKER:
325                    transitionTo(mActiveSpeakerRoute);
326                    return HANDLED;
327                case SWITCH_FOCUS:
328                    if (msg.arg1 == NO_FOCUS) {
329                        reinitialize();
330                    }
331                    return HANDLED;
332                default:
333                    return NOT_HANDLED;
334            }
335        }
336    }
337
338    class QuiescentEarpieceRoute extends EarpieceRoute {
339        @Override
340        public String getName() {
341            return QUIESCENT_EARPIECE_ROUTE_NAME;
342        }
343
344        @Override
345        public boolean isActive() {
346            return false;
347        }
348
349        @Override
350        public void enter() {
351            super.enter();
352            mHasUserExplicitlyLeftBluetooth = false;
353            updateInternalCallAudioState();
354        }
355
356        @Override
357        public void updateSystemAudioState() {
358            updateInternalCallAudioState();
359        }
360
361        @Override
362        public boolean processMessage(Message msg) {
363            if (super.processMessage(msg) == HANDLED) {
364                return HANDLED;
365            }
366            switch (msg.what) {
367                case SWITCH_EARPIECE:
368                case USER_SWITCH_EARPIECE:
369                    // Nothing to do here
370                    return HANDLED;
371                case SWITCH_BLUETOOTH:
372                case USER_SWITCH_BLUETOOTH:
373                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
374                        transitionTo(mQuiescentBluetoothRoute);
375                    } else {
376                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
377                    }
378                    return HANDLED;
379                case SWITCH_HEADSET:
380                case USER_SWITCH_HEADSET:
381                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
382                        transitionTo(mQuiescentHeadsetRoute);
383                    } else {
384                        Log.w(this, "Ignoring switch to headset command. Not available.");
385                    }
386                    return HANDLED;
387                case SWITCH_SPEAKER:
388                case USER_SWITCH_SPEAKER:
389                    transitionTo(mQuiescentSpeakerRoute);
390                    return HANDLED;
391                case SWITCH_FOCUS:
392                    if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
393                        transitionTo(mActiveEarpieceRoute);
394                    }
395                    return HANDLED;
396                default:
397                    return NOT_HANDLED;
398            }
399        }
400    }
401
402    abstract class EarpieceRoute extends AudioState {
403        @Override
404        public int getRouteCode() {
405            return CallAudioState.ROUTE_EARPIECE;
406        }
407
408        @Override
409        public boolean processMessage(Message msg) {
410            if (super.processMessage(msg) == HANDLED) {
411                return HANDLED;
412            }
413            switch (msg.what) {
414                case CONNECT_WIRED_HEADSET:
415                    sendInternalMessage(SWITCH_HEADSET);
416                    return HANDLED;
417                case CONNECT_BLUETOOTH:
418                    if (!mHasUserExplicitlyLeftBluetooth) {
419                        sendInternalMessage(SWITCH_BLUETOOTH);
420                    } else {
421                        Log.i(this, "Not switching to BT route from earpiece because user has " +
422                                "explicitly disconnected.");
423                        updateSystemAudioState();
424                    }
425                    return HANDLED;
426                case DISCONNECT_BLUETOOTH:
427                    updateSystemAudioState();
428                    // No change in audio route required
429                    return HANDLED;
430                case DISCONNECT_WIRED_HEADSET:
431                    Log.e(this, new IllegalStateException(),
432                            "Wired headset should not go from connected to not when on " +
433                            "earpiece");
434                    updateSystemAudioState();
435                    return HANDLED;
436                case BT_AUDIO_DISCONNECT:
437                    // This may be sent as a confirmation by the BT stack after switch off BT.
438                    return HANDLED;
439                case CONNECT_DOCK:
440                    sendInternalMessage(SWITCH_SPEAKER);
441                    return HANDLED;
442                case DISCONNECT_DOCK:
443                    // Nothing to do here
444                    return HANDLED;
445                default:
446                    return NOT_HANDLED;
447            }
448        }
449    }
450
451    class ActiveHeadsetRoute extends HeadsetRoute {
452        @Override
453        public String getName() {
454            return ACTIVE_HEADSET_ROUTE_NAME;
455        }
456
457        @Override
458        public boolean isActive() {
459            return true;
460        }
461
462        @Override
463        public void enter() {
464            super.enter();
465            setSpeakerphoneOn(false);
466            setBluetoothOn(false);
467            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
468                    mAvailableRoutes);
469            setSystemAudioState(newState, true);
470            updateInternalCallAudioState();
471        }
472
473        @Override
474        public void updateSystemAudioState() {
475            updateInternalCallAudioState();
476            setSystemAudioState(mCurrentCallAudioState);
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                case USER_SWITCH_EARPIECE:
487                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
488                        transitionTo(mActiveEarpieceRoute);
489                    } else {
490                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
491                    }
492                    return HANDLED;
493                case SWITCH_BLUETOOTH:
494                case USER_SWITCH_BLUETOOTH:
495                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
496                        transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
497                                mActiveBluetoothRoute : mRingingBluetoothRoute);
498                    } else {
499                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
500                    }
501                    return HANDLED;
502                case SWITCH_HEADSET:
503                case USER_SWITCH_HEADSET:
504                    // Nothing to do
505                    return HANDLED;
506                case SWITCH_SPEAKER:
507                case USER_SWITCH_SPEAKER:
508                    transitionTo(mActiveSpeakerRoute);
509                    return HANDLED;
510                case SWITCH_FOCUS:
511                    if (msg.arg1 == NO_FOCUS) {
512                        reinitialize();
513                    }
514                    return HANDLED;
515                default:
516                    return NOT_HANDLED;
517            }
518        }
519    }
520
521    class QuiescentHeadsetRoute extends HeadsetRoute {
522        @Override
523        public String getName() {
524            return QUIESCENT_HEADSET_ROUTE_NAME;
525        }
526
527        @Override
528        public boolean isActive() {
529            return false;
530        }
531
532        @Override
533        public void enter() {
534            super.enter();
535            mHasUserExplicitlyLeftBluetooth = false;
536            updateInternalCallAudioState();
537        }
538
539        @Override
540        public void updateSystemAudioState() {
541            updateInternalCallAudioState();
542        }
543
544        @Override
545        public boolean processMessage(Message msg) {
546            if (super.processMessage(msg) == HANDLED) {
547                return HANDLED;
548            }
549            switch (msg.what) {
550                case SWITCH_EARPIECE:
551                case USER_SWITCH_EARPIECE:
552                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
553                        transitionTo(mQuiescentEarpieceRoute);
554                    } else {
555                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
556                    }
557                    return HANDLED;
558                case SWITCH_BLUETOOTH:
559                case USER_SWITCH_BLUETOOTH:
560                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
561                        transitionTo(mQuiescentBluetoothRoute);
562                    } else {
563                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
564                    }
565                    return HANDLED;
566                case SWITCH_HEADSET:
567                case USER_SWITCH_HEADSET:
568                    // Nothing to do
569                    return HANDLED;
570                case SWITCH_SPEAKER:
571                case USER_SWITCH_SPEAKER:
572                    transitionTo(mQuiescentSpeakerRoute);
573                    return HANDLED;
574                case SWITCH_FOCUS:
575                    if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
576                        transitionTo(mActiveHeadsetRoute);
577                    }
578                    return HANDLED;
579                default:
580                    return NOT_HANDLED;
581            }
582        }
583    }
584
585    abstract class HeadsetRoute extends AudioState {
586        @Override
587        public int getRouteCode() {
588            return CallAudioState.ROUTE_WIRED_HEADSET;
589        }
590
591        @Override
592        public boolean processMessage(Message msg) {
593            if (super.processMessage(msg) == HANDLED) {
594                return HANDLED;
595            }
596            switch (msg.what) {
597                case CONNECT_WIRED_HEADSET:
598                    Log.e(this, new IllegalStateException(),
599                            "Wired headset should already be connected.");
600                    mAvailableRoutes |= ROUTE_WIRED_HEADSET;
601                    updateSystemAudioState();
602                    return HANDLED;
603                case CONNECT_BLUETOOTH:
604                    if (!mHasUserExplicitlyLeftBluetooth) {
605                        sendInternalMessage(SWITCH_BLUETOOTH);
606                    } else {
607                        Log.i(this, "Not switching to BT route from headset because user has " +
608                                "explicitly disconnected.");
609                        updateSystemAudioState();
610                    }
611                    return HANDLED;
612                case DISCONNECT_BLUETOOTH:
613                    updateSystemAudioState();
614                    // No change in audio route required
615                    return HANDLED;
616                case DISCONNECT_WIRED_HEADSET:
617                    if (mWasOnSpeaker) {
618                        sendInternalMessage(SWITCH_SPEAKER);
619                    } else {
620                        sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
621                    }
622                    return HANDLED;
623                case BT_AUDIO_DISCONNECT:
624                    // This may be sent as a confirmation by the BT stack after switch off BT.
625                    return HANDLED;
626                case CONNECT_DOCK:
627                    // Nothing to do here
628                    return HANDLED;
629                case DISCONNECT_DOCK:
630                    // Nothing to do here
631                    return HANDLED;
632                default:
633                    return NOT_HANDLED;
634            }
635        }
636    }
637
638    class ActiveBluetoothRoute extends BluetoothRoute {
639        @Override
640        public String getName() {
641            return ACTIVE_BLUETOOTH_ROUTE_NAME;
642        }
643
644        @Override
645        public boolean isActive() {
646            return true;
647        }
648
649        @Override
650        public void enter() {
651            super.enter();
652            setSpeakerphoneOn(false);
653            setBluetoothOn(true);
654            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
655                    mAvailableRoutes);
656            setSystemAudioState(newState, true);
657            updateInternalCallAudioState();
658        }
659
660        @Override
661        public void updateSystemAudioState() {
662            updateInternalCallAudioState();
663            setSystemAudioState(mCurrentCallAudioState);
664        }
665
666        @Override
667        public boolean processMessage(Message msg) {
668            if (super.processMessage(msg) == HANDLED) {
669                return HANDLED;
670            }
671            switch (msg.what) {
672                case USER_SWITCH_EARPIECE:
673                    mHasUserExplicitlyLeftBluetooth = true;
674                    // fall through
675                case SWITCH_EARPIECE:
676                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
677                        transitionTo(mActiveEarpieceRoute);
678                    } else {
679                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
680                    }
681                    return HANDLED;
682                case SWITCH_BLUETOOTH:
683                case USER_SWITCH_BLUETOOTH:
684                    // Nothing to do
685                    return HANDLED;
686                case USER_SWITCH_HEADSET:
687                    mHasUserExplicitlyLeftBluetooth = true;
688                    // fall through
689                case SWITCH_HEADSET:
690                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
691                        transitionTo(mActiveHeadsetRoute);
692                    } else {
693                        Log.w(this, "Ignoring switch to headset command. Not available.");
694                    }
695                    return HANDLED;
696                case USER_SWITCH_SPEAKER:
697                    mHasUserExplicitlyLeftBluetooth = true;
698                    // fall through
699                case SWITCH_SPEAKER:
700                    transitionTo(mActiveSpeakerRoute);
701                    return HANDLED;
702                case SWITCH_FOCUS:
703                    if (msg.arg1 == NO_FOCUS) {
704                        reinitialize();
705                    } else if (msg.arg1 == RINGING_FOCUS) {
706                        transitionTo(mRingingBluetoothRoute);
707                    }
708                    return HANDLED;
709                case BT_AUDIO_DISCONNECT:
710                    sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
711                    return HANDLED;
712                default:
713                    return NOT_HANDLED;
714            }
715        }
716    }
717
718    class RingingBluetoothRoute extends BluetoothRoute {
719        @Override
720        public String getName() {
721            return RINGING_BLUETOOTH_ROUTE_NAME;
722        }
723
724        @Override
725        public boolean isActive() {
726            return false;
727        }
728
729        @Override
730        public void enter() {
731            super.enter();
732            setSpeakerphoneOn(false);
733            // Do not enable SCO audio here, since RING is being sent to the headset.
734            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
735                    mAvailableRoutes);
736            setSystemAudioState(newState);
737            updateInternalCallAudioState();
738        }
739
740        @Override
741        public void updateSystemAudioState() {
742            updateInternalCallAudioState();
743            setSystemAudioState(mCurrentCallAudioState);
744        }
745
746        @Override
747        public boolean processMessage(Message msg) {
748            if (super.processMessage(msg) == HANDLED) {
749                return HANDLED;
750            }
751            switch (msg.what) {
752                case USER_SWITCH_EARPIECE:
753                    mHasUserExplicitlyLeftBluetooth = true;
754                    // fall through
755                case SWITCH_EARPIECE:
756                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
757                        transitionTo(mActiveEarpieceRoute);
758                    } else {
759                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
760                    }
761                    return HANDLED;
762                case SWITCH_BLUETOOTH:
763                case USER_SWITCH_BLUETOOTH:
764                    // Nothing to do
765                    return HANDLED;
766                case USER_SWITCH_HEADSET:
767                    mHasUserExplicitlyLeftBluetooth = true;
768                    // fall through
769                case SWITCH_HEADSET:
770                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
771                        transitionTo(mActiveHeadsetRoute);
772                    } else {
773                        Log.w(this, "Ignoring switch to headset command. Not available.");
774                    }
775                    return HANDLED;
776                case USER_SWITCH_SPEAKER:
777                    mHasUserExplicitlyLeftBluetooth = true;
778                    // fall through
779                case SWITCH_SPEAKER:
780                    transitionTo(mActiveSpeakerRoute);
781                    return HANDLED;
782                case SWITCH_FOCUS:
783                    if (msg.arg1 == NO_FOCUS) {
784                        reinitialize();
785                    } else if (msg.arg1 == ACTIVE_FOCUS) {
786                        transitionTo(mActiveBluetoothRoute);
787                    }
788                    return HANDLED;
789                case BT_AUDIO_DISCONNECT:
790                    // BT SCO might be connected when in-band ringing is enabled
791                    sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
792                    return HANDLED;
793                default:
794                    return NOT_HANDLED;
795            }
796        }
797    }
798
799    class QuiescentBluetoothRoute extends BluetoothRoute {
800        @Override
801        public String getName() {
802            return QUIESCENT_BLUETOOTH_ROUTE_NAME;
803        }
804
805        @Override
806        public boolean isActive() {
807            return false;
808        }
809
810        @Override
811        public void enter() {
812            super.enter();
813            mHasUserExplicitlyLeftBluetooth = false;
814            updateInternalCallAudioState();
815            setBluetoothOn(false);
816        }
817
818        @Override
819        public void updateSystemAudioState() {
820            updateInternalCallAudioState();
821        }
822
823        @Override
824        public boolean processMessage(Message msg) {
825            if (super.processMessage(msg) == HANDLED) {
826                return HANDLED;
827            }
828            switch (msg.what) {
829                case SWITCH_EARPIECE:
830                case USER_SWITCH_EARPIECE:
831                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
832                        transitionTo(mQuiescentEarpieceRoute);
833                    } else {
834                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
835                    }
836                    return HANDLED;
837                case SWITCH_BLUETOOTH:
838                case USER_SWITCH_BLUETOOTH:
839                    // Nothing to do
840                    return HANDLED;
841                case SWITCH_HEADSET:
842                case USER_SWITCH_HEADSET:
843                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
844                        transitionTo(mQuiescentHeadsetRoute);
845                    } else {
846                        Log.w(this, "Ignoring switch to headset command. Not available.");
847                    }
848                    return HANDLED;
849                case SWITCH_SPEAKER:
850                case USER_SWITCH_SPEAKER:
851                    transitionTo(mQuiescentSpeakerRoute);
852                    return HANDLED;
853                case SWITCH_FOCUS:
854                    if (msg.arg1 == ACTIVE_FOCUS) {
855                        transitionTo(mActiveBluetoothRoute);
856                    } else if (msg.arg1 == RINGING_FOCUS) {
857                        transitionTo(mRingingBluetoothRoute);
858                    }
859                    return HANDLED;
860                case BT_AUDIO_DISCONNECT:
861                    // Ignore this -- audio disconnecting while quiescent should not cause a
862                    // route switch, since the device is still connected.
863                    return HANDLED;
864                default:
865                    return NOT_HANDLED;
866            }
867        }
868    }
869
870    abstract class BluetoothRoute extends AudioState {
871        @Override
872        public int getRouteCode() {
873            return CallAudioState.ROUTE_BLUETOOTH;
874        }
875
876        @Override
877        public boolean processMessage(Message msg) {
878            if (super.processMessage(msg) == HANDLED) {
879                return HANDLED;
880            }
881            switch (msg.what) {
882                case CONNECT_WIRED_HEADSET:
883                    sendInternalMessage(SWITCH_HEADSET);
884                    return HANDLED;
885                case CONNECT_BLUETOOTH:
886                    // We can't tell when a change in bluetooth state corresponds to an
887                    // actual connection or disconnection, so we'll just ignore it if we're already
888                    // in the bluetooth route.
889                    return HANDLED;
890                case DISCONNECT_BLUETOOTH:
891                    sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
892                    mWasOnSpeaker = false;
893                    return HANDLED;
894                case DISCONNECT_WIRED_HEADSET:
895                    updateSystemAudioState();
896                    // No change in audio route required
897                    return HANDLED;
898                case CONNECT_DOCK:
899                    // Nothing to do here
900                    return HANDLED;
901                case DISCONNECT_DOCK:
902                    // Nothing to do here
903                    return HANDLED;
904                default:
905                    return NOT_HANDLED;
906            }
907        }
908    }
909
910    class ActiveSpeakerRoute extends SpeakerRoute {
911        @Override
912        public String getName() {
913            return ACTIVE_SPEAKER_ROUTE_NAME;
914        }
915
916        @Override
917        public boolean isActive() {
918            return true;
919        }
920
921        @Override
922        public void enter() {
923            super.enter();
924            mWasOnSpeaker = true;
925            setSpeakerphoneOn(true);
926            setBluetoothOn(false);
927            CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_SPEAKER,
928                    mAvailableRoutes);
929            setSystemAudioState(newState);
930            updateInternalCallAudioState();
931        }
932
933        @Override
934        public void updateSystemAudioState() {
935            updateInternalCallAudioState();
936            setSystemAudioState(mCurrentCallAudioState);
937        }
938
939        @Override
940        public boolean processMessage(Message msg) {
941            if (super.processMessage(msg) == HANDLED) {
942                return HANDLED;
943            }
944            switch(msg.what) {
945                case USER_SWITCH_EARPIECE:
946                    mWasOnSpeaker = false;
947                    // fall through
948                case SWITCH_EARPIECE:
949                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
950                        transitionTo(mActiveEarpieceRoute);
951                    } else {
952                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
953                    }
954                    return HANDLED;
955                case USER_SWITCH_BLUETOOTH:
956                    mWasOnSpeaker = false;
957                    // fall through
958                case SWITCH_BLUETOOTH:
959                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
960                        transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
961                                mActiveBluetoothRoute : mRingingBluetoothRoute);
962                    } else {
963                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
964                    }
965                    return HANDLED;
966                case USER_SWITCH_HEADSET:
967                    mWasOnSpeaker = false;
968                    // fall through
969                case SWITCH_HEADSET:
970                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
971                        transitionTo(mActiveHeadsetRoute);
972                    } else {
973                        Log.w(this, "Ignoring switch to headset command. Not available.");
974                    }
975                    return HANDLED;
976                case SWITCH_SPEAKER:
977                case USER_SWITCH_SPEAKER:
978                    // Nothing to do
979                    return HANDLED;
980                case SWITCH_FOCUS:
981                    if (msg.arg1 == NO_FOCUS) {
982                        reinitialize();
983                    }
984                    return HANDLED;
985                default:
986                    return NOT_HANDLED;
987            }
988        }
989    }
990
991    class QuiescentSpeakerRoute extends SpeakerRoute {
992        @Override
993        public String getName() {
994            return QUIESCENT_SPEAKER_ROUTE_NAME;
995        }
996
997        @Override
998        public boolean isActive() {
999            return false;
1000        }
1001
1002        @Override
1003        public void enter() {
1004            super.enter();
1005            mHasUserExplicitlyLeftBluetooth = false;
1006            // Omit setting mWasOnSpeaker to true here, since this does not reflect a call
1007            // actually being on speakerphone.
1008            updateInternalCallAudioState();
1009        }
1010
1011        @Override
1012        public void updateSystemAudioState() {
1013            updateInternalCallAudioState();
1014        }
1015
1016        @Override
1017        public boolean processMessage(Message msg) {
1018            if (super.processMessage(msg) == HANDLED) {
1019                return HANDLED;
1020            }
1021            switch(msg.what) {
1022                case SWITCH_EARPIECE:
1023                case USER_SWITCH_EARPIECE:
1024                    if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
1025                        transitionTo(mQuiescentEarpieceRoute);
1026                    } else {
1027                        Log.w(this, "Ignoring switch to earpiece command. Not available.");
1028                    }
1029                    return HANDLED;
1030                case SWITCH_BLUETOOTH:
1031                case USER_SWITCH_BLUETOOTH:
1032                    if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
1033                        transitionTo(mQuiescentBluetoothRoute);
1034                    } else {
1035                        Log.w(this, "Ignoring switch to bluetooth command. Not available.");
1036                    }
1037                    return HANDLED;
1038                case SWITCH_HEADSET:
1039                case USER_SWITCH_HEADSET:
1040                    if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
1041                        transitionTo(mQuiescentHeadsetRoute);
1042                    } else {
1043                        Log.w(this, "Ignoring switch to headset command. Not available.");
1044                    }
1045                    return HANDLED;
1046                case SWITCH_SPEAKER:
1047                case USER_SWITCH_SPEAKER:
1048                    // Nothing to do
1049                    return HANDLED;
1050                case SWITCH_FOCUS:
1051                    if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
1052                        transitionTo(mActiveSpeakerRoute);
1053                    }
1054                    return HANDLED;
1055                default:
1056                    return NOT_HANDLED;
1057            }
1058        }
1059    }
1060
1061    abstract class SpeakerRoute extends AudioState {
1062        @Override
1063        public int getRouteCode() {
1064            return CallAudioState.ROUTE_SPEAKER;
1065        }
1066
1067        @Override
1068        public boolean processMessage(Message msg) {
1069            if (super.processMessage(msg) == HANDLED) {
1070                return HANDLED;
1071            }
1072            switch (msg.what) {
1073                case CONNECT_WIRED_HEADSET:
1074                    sendInternalMessage(SWITCH_HEADSET);
1075                    return HANDLED;
1076                case CONNECT_BLUETOOTH:
1077                    if (!mHasUserExplicitlyLeftBluetooth) {
1078                        sendInternalMessage(SWITCH_BLUETOOTH);
1079                    } else {
1080                        Log.i(this, "Not switching to BT route from speaker because user has " +
1081                                "explicitly disconnected.");
1082                        updateSystemAudioState();
1083                    }
1084                    return HANDLED;
1085                case DISCONNECT_BLUETOOTH:
1086                    updateSystemAudioState();
1087                    // No change in audio route required
1088                    return HANDLED;
1089                case DISCONNECT_WIRED_HEADSET:
1090                    updateSystemAudioState();
1091                    // No change in audio route required
1092                    return HANDLED;
1093                case BT_AUDIO_DISCONNECT:
1094                    // This may be sent as a confirmation by the BT stack after switch off BT.
1095                    return HANDLED;
1096                case CONNECT_DOCK:
1097                    // Nothing to do here
1098                    return HANDLED;
1099                case DISCONNECT_DOCK:
1100                    sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
1101                    return HANDLED;
1102               default:
1103                    return NOT_HANDLED;
1104            }
1105        }
1106    }
1107
1108    private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute();
1109    private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
1110    private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
1111    private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute();
1112    private final RingingBluetoothRoute mRingingBluetoothRoute = new RingingBluetoothRoute();
1113    private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute();
1114    private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
1115    private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
1116    private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
1117
1118    /**
1119     * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
1120     * states
1121     */
1122    private int mDeviceSupportedRoutes;
1123    private int mAvailableRoutes;
1124    private int mAudioFocusType;
1125    private boolean mWasOnSpeaker;
1126    private boolean mIsMuted;
1127
1128    private final Context mContext;
1129    private final CallsManager mCallsManager;
1130    private final AudioManager mAudioManager;
1131    private final BluetoothRouteManager mBluetoothRouteManager;
1132    private final WiredHeadsetManager mWiredHeadsetManager;
1133    private final StatusBarNotifier mStatusBarNotifier;
1134    private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
1135    private final boolean mDoesDeviceSupportEarpieceRoute;
1136    private final TelecomSystem.SyncRoot mLock;
1137    private boolean mHasUserExplicitlyLeftBluetooth = false;
1138
1139    private HashMap<String, Integer> mStateNameToRouteCode;
1140    private HashMap<Integer, AudioState> mRouteCodeToQuiescentState;
1141
1142    // CallAudioState is used as an interface to communicate with many other system components.
1143    // No internal state transitions should depend on this variable.
1144    private CallAudioState mCurrentCallAudioState;
1145    private CallAudioState mLastKnownCallAudioState;
1146
1147    public CallAudioRouteStateMachine(
1148            Context context,
1149            CallsManager callsManager,
1150            BluetoothRouteManager bluetoothManager,
1151            WiredHeadsetManager wiredHeadsetManager,
1152            StatusBarNotifier statusBarNotifier,
1153            CallAudioManager.AudioServiceFactory audioServiceFactory,
1154            boolean doesDeviceSupportEarpieceRoute) {
1155        super(NAME);
1156        addState(mActiveEarpieceRoute);
1157        addState(mActiveHeadsetRoute);
1158        addState(mActiveBluetoothRoute);
1159        addState(mActiveSpeakerRoute);
1160        addState(mRingingBluetoothRoute);
1161        addState(mQuiescentEarpieceRoute);
1162        addState(mQuiescentHeadsetRoute);
1163        addState(mQuiescentBluetoothRoute);
1164        addState(mQuiescentSpeakerRoute);
1165
1166        mContext = context;
1167        mCallsManager = callsManager;
1168        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
1169        mBluetoothRouteManager = bluetoothManager;
1170        mWiredHeadsetManager = wiredHeadsetManager;
1171        mStatusBarNotifier = statusBarNotifier;
1172        mAudioServiceFactory = audioServiceFactory;
1173        mDoesDeviceSupportEarpieceRoute = doesDeviceSupportEarpieceRoute;
1174        mLock = callsManager.getLock();
1175
1176        mStateNameToRouteCode = new HashMap<>(8);
1177        mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE);
1178        mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH);
1179        mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
1180        mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER);
1181        mStateNameToRouteCode.put(mRingingBluetoothRoute.getName(), ROUTE_BLUETOOTH);
1182        mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE);
1183        mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
1184        mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
1185        mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER);
1186
1187        mRouteCodeToQuiescentState = new HashMap<>(4);
1188        mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute);
1189        mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute);
1190        mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute);
1191        mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute);
1192    }
1193
1194    /**
1195     * Initializes the state machine with info on initial audio route, supported audio routes,
1196     * and mute status.
1197     */
1198    public void initialize() {
1199        CallAudioState initState = getInitialAudioState();
1200        initialize(initState);
1201    }
1202
1203    public void initialize(CallAudioState initState) {
1204        if ((initState.getRoute() & getCurrentCallSupportedRoutes()) == 0) {
1205            Log.e(this, new IllegalArgumentException(), "Route %d specified when supported call" +
1206                    " routes are: %d", initState.getRoute(), getCurrentCallSupportedRoutes());
1207        }
1208
1209        mCurrentCallAudioState = initState;
1210        mLastKnownCallAudioState = initState;
1211        mDeviceSupportedRoutes = initState.getSupportedRouteMask();
1212        mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
1213        mIsMuted = initState.isMuted();
1214        mWasOnSpeaker = false;
1215
1216        mStatusBarNotifier.notifyMute(initState.isMuted());
1217        mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
1218        setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
1219        start();
1220    }
1221
1222    /**
1223     * Getter for the current CallAudioState object that the state machine is keeping track of.
1224     * Used for compatibility purposes.
1225     */
1226    public CallAudioState getCurrentCallAudioState() {
1227        return mCurrentCallAudioState;
1228    }
1229
1230    public void sendMessageWithSessionInfo(int message, int arg) {
1231        sendMessage(message, arg, 0, Log.createSubsession());
1232    }
1233
1234    public void sendMessageWithSessionInfo(int message) {
1235        sendMessage(message, 0, 0, Log.createSubsession());
1236    }
1237
1238    /**
1239     * This is for state-independent changes in audio route (i.e. muting or runnables)
1240     * @param msg that couldn't be handled.
1241     */
1242    @Override
1243    protected void unhandledMessage(Message msg) {
1244        CallAudioState newCallAudioState;
1245        switch (msg.what) {
1246            case MUTE_ON:
1247                setMuteOn(true);
1248                newCallAudioState = new CallAudioState(mIsMuted,
1249                        mCurrentCallAudioState.getRoute(),
1250                        mAvailableRoutes);
1251                setSystemAudioState(newCallAudioState);
1252                updateInternalCallAudioState();
1253                return;
1254            case MUTE_OFF:
1255                setMuteOn(false);
1256                newCallAudioState = new CallAudioState(mIsMuted,
1257                        mCurrentCallAudioState.getRoute(),
1258                        mAvailableRoutes);
1259                setSystemAudioState(newCallAudioState);
1260                updateInternalCallAudioState();
1261                return;
1262            case TOGGLE_MUTE:
1263                if (mIsMuted) {
1264                    sendInternalMessage(MUTE_OFF);
1265                } else {
1266                    sendInternalMessage(MUTE_ON);
1267                }
1268                return;
1269            case UPDATE_SYSTEM_AUDIO_ROUTE:
1270                updateRouteForForegroundCall();
1271                resendSystemAudioState();
1272                return;
1273            case RUN_RUNNABLE:
1274                java.lang.Runnable r = (java.lang.Runnable) msg.obj;
1275                r.run();
1276                return;
1277            default:
1278                Log.e(this, new IllegalStateException(),
1279                        "Unexpected message code %d", msg.what);
1280        }
1281    }
1282
1283    public void quitStateMachine() {
1284        quitNow();
1285    }
1286
1287    public void dumpPendingMessages(IndentingPrintWriter pw) {
1288        getHandler().getLooper().dump(pw::println, "");
1289    }
1290
1291    public boolean isHfpDeviceAvailable() {
1292        return mBluetoothRouteManager.isBluetoothAvailable();
1293    }
1294
1295    private void setSpeakerphoneOn(boolean on) {
1296        if (mAudioManager.isSpeakerphoneOn() != on) {
1297            Log.i(this, "turning speaker phone %s", on);
1298            mAudioManager.setSpeakerphoneOn(on);
1299            mStatusBarNotifier.notifySpeakerphone(on);
1300        }
1301    }
1302
1303    private void setBluetoothOn(boolean on) {
1304        if (mBluetoothRouteManager.isBluetoothAvailable()) {
1305            if (on != mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()) {
1306                Log.i(this, "connecting bluetooth %s", on);
1307                if (on) {
1308                    mBluetoothRouteManager.connectBluetoothAudio(null /*TODO: add real address*/);
1309                } else {
1310                    mBluetoothRouteManager.disconnectBluetoothAudio();
1311                }
1312            }
1313        }
1314    }
1315
1316    private void setMuteOn(boolean mute) {
1317        mIsMuted = mute;
1318        Log.addEvent(mCallsManager.getForegroundCall(), mute ?
1319                LogUtils.Events.MUTE : LogUtils.Events.UNMUTE);
1320        if (mute != mAudioManager.isMicrophoneMute() && isInActiveState()) {
1321            IAudioService audio = mAudioServiceFactory.getAudioService();
1322            Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]",
1323                    mute, audio == null);
1324            if (audio != null) {
1325                try {
1326                    // We use the audio service directly here so that we can specify
1327                    // the current user. Telecom runs in the system_server process which
1328                    // may run as a separate user from the foreground user. If we
1329                    // used AudioManager directly, we would change mute for the system's
1330                    // user and not the current foreground, which we want to avoid.
1331                    audio.setMicrophoneMute(
1332                            mute, mContext.getOpPackageName(), getCurrentUserId());
1333                    mStatusBarNotifier.notifyMute(mute);
1334
1335                } catch (RemoteException e) {
1336                    Log.e(this, e, "Remote exception while toggling mute.");
1337                }
1338                // TODO: Check microphone state after attempting to set to ensure that
1339                // our state corroborates AudioManager's state.
1340            }
1341        }
1342    }
1343
1344    /**
1345     * Updates the CallAudioState object from current internal state. The result is used for
1346     * external communication only.
1347     */
1348    private void updateInternalCallAudioState() {
1349        IState currentState = getCurrentState();
1350        if (currentState == null) {
1351            Log.e(this, new IllegalStateException(), "Current state should never be null" +
1352                    " when updateInternalCallAudioState is called.");
1353            mCurrentCallAudioState = new CallAudioState(
1354                    mIsMuted, mCurrentCallAudioState.getRoute(), mAvailableRoutes);
1355            return;
1356        }
1357        int currentRoute = mStateNameToRouteCode.get(currentState.getName());
1358        mCurrentCallAudioState = new CallAudioState(mIsMuted, currentRoute, mAvailableRoutes);
1359    }
1360
1361    private void setSystemAudioState(CallAudioState newCallAudioState) {
1362        setSystemAudioState(newCallAudioState, false);
1363    }
1364
1365    private void resendSystemAudioState() {
1366        setSystemAudioState(mLastKnownCallAudioState, true);
1367    }
1368
1369    private void setSystemAudioState(CallAudioState newCallAudioState, boolean force) {
1370        synchronized (mLock) {
1371            Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
1372                    newCallAudioState);
1373            if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) {
1374
1375                mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
1376                updateAudioForForegroundCall(newCallAudioState);
1377                mLastKnownCallAudioState = newCallAudioState;
1378            }
1379        }
1380    }
1381
1382    private void updateAudioForForegroundCall(CallAudioState newCallAudioState) {
1383        Call call = mCallsManager.getForegroundCall();
1384        if (call != null && call.getConnectionService() != null) {
1385            call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
1386        }
1387    }
1388
1389    private int calculateSupportedRoutes() {
1390        int routeMask = CallAudioState.ROUTE_SPEAKER;
1391
1392        if (mWiredHeadsetManager.isPluggedIn()) {
1393            routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
1394        } else if (mDoesDeviceSupportEarpieceRoute){
1395            routeMask |= CallAudioState.ROUTE_EARPIECE;
1396        }
1397
1398        if (mBluetoothRouteManager.isBluetoothAvailable()) {
1399            routeMask |=  CallAudioState.ROUTE_BLUETOOTH;
1400        }
1401
1402        return routeMask;
1403    }
1404
1405    private void sendInternalMessage(int messageCode) {
1406        sendInternalMessage(messageCode, 0);
1407    }
1408
1409    private void sendInternalMessage(int messageCode, int arg1) {
1410        // Internal messages are messages which the state machine sends to itself in the
1411        // course of processing externally-sourced messages. We want to send these messages at
1412        // the front of the queue in order to make actions appear atomic to the user and to
1413        // prevent scenarios such as these:
1414        // 1. State machine handler thread is suspended for some reason.
1415        // 2. Headset gets connected (sends CONNECT_HEADSET).
1416        // 3. User switches to speakerphone in the UI (sends SWITCH_SPEAKER).
1417        // 4. State machine handler is un-suspended.
1418        // 5. State machine handler processes the CONNECT_HEADSET message and sends
1419        //    SWITCH_HEADSET at end of queue.
1420        // 6. State machine handler processes SWITCH_SPEAKER.
1421        // 7. State machine handler processes SWITCH_HEADSET.
1422        Session subsession = Log.createSubsession();
1423        if(subsession != null) {
1424            sendMessageAtFrontOfQueue(messageCode, arg1, 0, subsession);
1425        } else {
1426            sendMessageAtFrontOfQueue(messageCode, arg1);
1427        }
1428    }
1429
1430    private CallAudioState getInitialAudioState() {
1431        int supportedRouteMask = calculateSupportedRoutes() & getCurrentCallSupportedRoutes();
1432        final int route;
1433
1434        if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0) {
1435            route = ROUTE_BLUETOOTH;
1436        } else if ((supportedRouteMask & ROUTE_WIRED_HEADSET) != 0) {
1437            route = ROUTE_WIRED_HEADSET;
1438        } else if ((supportedRouteMask & ROUTE_EARPIECE) != 0) {
1439            route = ROUTE_EARPIECE;
1440        } else {
1441            route = ROUTE_SPEAKER;
1442        }
1443
1444        return new CallAudioState(false, route, supportedRouteMask);
1445    }
1446
1447    private int getCurrentUserId() {
1448        final long ident = Binder.clearCallingIdentity();
1449        try {
1450            UserInfo currentUser = ActivityManager.getService().getCurrentUser();
1451            return currentUser.id;
1452        } catch (RemoteException e) {
1453            // Activity manager not running, nothing we can do assume user 0.
1454        } finally {
1455            Binder.restoreCallingIdentity(ident);
1456        }
1457        return UserHandle.USER_OWNER;
1458    }
1459
1460    private boolean isInActiveState() {
1461        AudioState currentState = (AudioState) getCurrentState();
1462        if (currentState == null) {
1463            Log.w(this, "Current state is null, assuming inactive state");
1464            return false;
1465        }
1466        return currentState.isActive();
1467    }
1468
1469    public static boolean doesDeviceSupportEarpieceRoute() {
1470        String[] characteristics = SystemProperties.get("ro.build.characteristics").split(",");
1471        for (String characteristic : characteristics) {
1472            if ("watch".equals(characteristic)) {
1473                return false;
1474            }
1475        }
1476        return true;
1477    }
1478
1479    private int calculateBaselineRouteMessage(boolean isExplicitUserRequest,
1480            boolean includeBluetooth) {
1481        boolean isSkipEarpiece = false;
1482        if (!isExplicitUserRequest) {
1483            synchronized (mLock) {
1484                // Check video calls to skip earpiece since the baseline for video
1485                // calls should be the speakerphone route
1486                isSkipEarpiece = mCallsManager.hasVideoCall();
1487            }
1488        }
1489        if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0
1490                && !mHasUserExplicitlyLeftBluetooth
1491                && includeBluetooth) {
1492            return isExplicitUserRequest ? USER_SWITCH_BLUETOOTH : SWITCH_BLUETOOTH;
1493        } else if ((mAvailableRoutes & ROUTE_EARPIECE) != 0 && !isSkipEarpiece) {
1494            return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE;
1495        } else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
1496            return isExplicitUserRequest ? USER_SWITCH_HEADSET : SWITCH_HEADSET;
1497        } else {
1498            return isExplicitUserRequest ? USER_SWITCH_SPEAKER : SWITCH_SPEAKER;
1499        }
1500    }
1501
1502    private void reinitialize() {
1503        CallAudioState initState = getInitialAudioState();
1504        mDeviceSupportedRoutes = initState.getSupportedRouteMask();
1505        mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
1506        mIsMuted = initState.isMuted();
1507        setMuteOn(mIsMuted);
1508        mWasOnSpeaker = false;
1509        mHasUserExplicitlyLeftBluetooth = false;
1510        mLastKnownCallAudioState = initState;
1511        transitionTo(mRouteCodeToQuiescentState.get(initState.getRoute()));
1512    }
1513
1514    private void updateRouteForForegroundCall() {
1515        mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
1516
1517        CallAudioState currentState = getCurrentCallAudioState();
1518
1519        // Move to baseline route in the case the current route is no longer available.
1520        if ((mAvailableRoutes & currentState.getRoute()) == 0) {
1521            sendInternalMessage(calculateBaselineRouteMessage(false, true));
1522        }
1523    }
1524
1525    private int getCurrentCallSupportedRoutes() {
1526        int supportedRoutes = CallAudioState.ROUTE_ALL;
1527
1528        if (mCallsManager.getForegroundCall() != null) {
1529            supportedRoutes &= mCallsManager.getForegroundCall().getSupportedAudioRoutes();
1530        }
1531
1532        return supportedRoutes;
1533    }
1534
1535    private int modifyRoutes(int base, int remove, int add, boolean considerCurrentCall) {
1536        base &= ~remove;
1537
1538        if (considerCurrentCall) {
1539            add &= getCurrentCallSupportedRoutes();
1540        }
1541
1542        base |= add;
1543
1544        return base;
1545    }
1546}
1547