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