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