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