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