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