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