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