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