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