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_ON:
463                     // Ignore speakerphone state changes outside of calls.
464                 case SPEAKER_OFF:
465                     // Nothing to do here
466                     return HANDLED;
467                 case BT_AUDIO_CONNECTED:
468                     Log.w(this, "BT Audio came on in quiescent earpiece route.");
469                     transitionTo(mActiveBluetoothRoute);
470                     return HANDLED;
471                 case SWITCH_BLUETOOTH:
472                 case USER_SWITCH_BLUETOOTH:
473                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
474                         transitionTo(mQuiescentBluetoothRoute);
475                     } else {
476                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
477                     }
478                     return HANDLED;
479                 case SWITCH_HEADSET:
480                 case USER_SWITCH_HEADSET:
481                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
482                         transitionTo(mQuiescentHeadsetRoute);
483                     } else {
484                         Log.w(this, "Ignoring switch to headset command. Not available.");
485                     }
486                     return HANDLED;
487                 case SWITCH_SPEAKER:
488                 case USER_SWITCH_SPEAKER:
489                     transitionTo(mQuiescentSpeakerRoute);
490                     return HANDLED;
491                 case SWITCH_FOCUS:
492                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
493                         transitionTo(mActiveEarpieceRoute);
494                     }
495                     return HANDLED;
496                 default:
497                     return NOT_HANDLED;
498             }
499         }
500     }
501 
502     abstract class EarpieceRoute extends AudioState {
503         @Override
getRouteCode()504         public int getRouteCode() {
505             return CallAudioState.ROUTE_EARPIECE;
506         }
507 
508         @Override
processMessage(Message msg)509         public boolean processMessage(Message msg) {
510             if (super.processMessage(msg) == HANDLED) {
511                 return HANDLED;
512             }
513             switch (msg.what) {
514                 case CONNECT_WIRED_HEADSET:
515                     sendInternalMessage(SWITCH_HEADSET);
516                     return HANDLED;
517                 case BT_ACTIVE_DEVICE_PRESENT:
518                     if (!mHasUserExplicitlyLeftBluetooth) {
519                         sendInternalMessage(SWITCH_BLUETOOTH);
520                     } else {
521                         Log.i(this, "Not switching to BT route from earpiece because user has " +
522                                 "explicitly disconnected.");
523                     }
524                     return HANDLED;
525                 case BT_ACTIVE_DEVICE_GONE:
526                     // No change in audio route required
527                     return HANDLED;
528                 case DISCONNECT_WIRED_HEADSET:
529                     Log.e(this, new IllegalStateException(),
530                             "Wired headset should not go from connected to not when on " +
531                             "earpiece");
532                     return HANDLED;
533                 case BT_AUDIO_DISCONNECTED:
534                     // This may be sent as a confirmation by the BT stack after switch off BT.
535                     return HANDLED;
536                 case CONNECT_DOCK:
537                     sendInternalMessage(SWITCH_SPEAKER);
538                     return HANDLED;
539                 case DISCONNECT_DOCK:
540                     // Nothing to do here
541                     return HANDLED;
542                 default:
543                     return NOT_HANDLED;
544             }
545         }
546     }
547 
548     class ActiveHeadsetRoute extends HeadsetRoute {
549         @Override
getName()550         public String getName() {
551             return ACTIVE_HEADSET_ROUTE_NAME;
552         }
553 
554         @Override
isActive()555         public boolean isActive() {
556             return true;
557         }
558 
559         @Override
enter()560         public void enter() {
561             super.enter();
562             setSpeakerphoneOn(false);
563             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
564                     mAvailableRoutes, null, mBluetoothRouteManager.getConnectedDevices());
565             setSystemAudioState(newState, true);
566             updateInternalCallAudioState();
567         }
568 
569         @Override
updateSystemAudioState()570         public void updateSystemAudioState() {
571             updateInternalCallAudioState();
572             setSystemAudioState(mCurrentCallAudioState);
573         }
574 
575         @Override
processMessage(Message msg)576         public boolean processMessage(Message msg) {
577             if (super.processMessage(msg) == HANDLED) {
578                 return HANDLED;
579             }
580             switch (msg.what) {
581                 case SWITCH_EARPIECE:
582                 case USER_SWITCH_EARPIECE:
583                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
584                         transitionTo(mActiveEarpieceRoute);
585                     } else {
586                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
587                     }
588                     return HANDLED;
589                 case BT_AUDIO_CONNECTED:
590                     transitionTo(mActiveBluetoothRoute);
591                     return HANDLED;
592                 case SWITCH_BLUETOOTH:
593                 case USER_SWITCH_BLUETOOTH:
594                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
595                         if (mAudioFocusType == ACTIVE_FOCUS
596                                 || mBluetoothRouteManager.isInbandRingingEnabled()) {
597                             String address = (msg.obj instanceof SomeArgs) ?
598                                     (String) ((SomeArgs) msg.obj).arg2 : null;
599                             // Omit transition to ActiveBluetoothRoute until actual connection.
600                             setBluetoothOn(address);
601                         } else {
602                             transitionTo(mRingingBluetoothRoute);
603                         }
604                     } else {
605                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
606                     }
607                     return HANDLED;
608                 case SWITCH_HEADSET:
609                 case USER_SWITCH_HEADSET:
610                 case SPEAKER_OFF:
611                     // Nothing to do
612                     return HANDLED;
613                 case SWITCH_SPEAKER:
614                 case USER_SWITCH_SPEAKER:
615                 case SPEAKER_ON:
616                     transitionTo(mActiveSpeakerRoute);
617                     return HANDLED;
618                 case SWITCH_FOCUS:
619                     if (msg.arg1 == NO_FOCUS) {
620                         reinitialize();
621                         mCallAudioManager.notifyAudioOperationsComplete();
622                     }
623                     return HANDLED;
624                 default:
625                     return NOT_HANDLED;
626             }
627         }
628     }
629 
630     class QuiescentHeadsetRoute extends HeadsetRoute {
631         @Override
getName()632         public String getName() {
633             return QUIESCENT_HEADSET_ROUTE_NAME;
634         }
635 
636         @Override
isActive()637         public boolean isActive() {
638             return false;
639         }
640 
641         @Override
enter()642         public void enter() {
643             super.enter();
644             mHasUserExplicitlyLeftBluetooth = false;
645             updateInternalCallAudioState();
646         }
647 
648         @Override
updateSystemAudioState()649         public void updateSystemAudioState() {
650             updateInternalCallAudioState();
651         }
652 
653         @Override
processMessage(Message msg)654         public boolean processMessage(Message msg) {
655             if (super.processMessage(msg) == HANDLED) {
656                 return HANDLED;
657             }
658             switch (msg.what) {
659                 case SWITCH_EARPIECE:
660                 case USER_SWITCH_EARPIECE:
661                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
662                         transitionTo(mQuiescentEarpieceRoute);
663                     } else {
664                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
665                     }
666                     return HANDLED;
667                 case BT_AUDIO_CONNECTED:
668                     transitionTo(mActiveBluetoothRoute);
669                     Log.w(this, "BT Audio came on in quiescent headset route.");
670                     return HANDLED;
671                 case SWITCH_BLUETOOTH:
672                 case USER_SWITCH_BLUETOOTH:
673                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
674                         transitionTo(mQuiescentBluetoothRoute);
675                     } else {
676                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
677                     }
678                     return HANDLED;
679                 case SWITCH_HEADSET:
680                 case USER_SWITCH_HEADSET:
681                 case SPEAKER_ON:
682                     // Ignore speakerphone state changes outside of calls.
683                 case SPEAKER_OFF:
684                     // Nothing to do
685                     return HANDLED;
686                 case SWITCH_SPEAKER:
687                 case USER_SWITCH_SPEAKER:
688                     transitionTo(mQuiescentSpeakerRoute);
689                     return HANDLED;
690                 case SWITCH_FOCUS:
691                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
692                         transitionTo(mActiveHeadsetRoute);
693                     }
694                     return HANDLED;
695                 default:
696                     return NOT_HANDLED;
697             }
698         }
699     }
700 
701     abstract class HeadsetRoute extends AudioState {
702         @Override
getRouteCode()703         public int getRouteCode() {
704             return CallAudioState.ROUTE_WIRED_HEADSET;
705         }
706 
707         @Override
processMessage(Message msg)708         public boolean processMessage(Message msg) {
709             if (super.processMessage(msg) == HANDLED) {
710                 return HANDLED;
711             }
712             switch (msg.what) {
713                 case CONNECT_WIRED_HEADSET:
714                     Log.e(this, new IllegalStateException(),
715                             "Wired headset should already be connected.");
716                     return HANDLED;
717                 case BT_ACTIVE_DEVICE_PRESENT:
718                     if (!mHasUserExplicitlyLeftBluetooth) {
719                         sendInternalMessage(SWITCH_BLUETOOTH);
720                     } else {
721                         Log.i(this, "Not switching to BT route from headset because user has " +
722                                 "explicitly disconnected.");
723                     }
724                     return HANDLED;
725                 case BT_ACTIVE_DEVICE_GONE:
726                     // No change in audio route required
727                     return HANDLED;
728                 case DISCONNECT_WIRED_HEADSET:
729                     if (mWasOnSpeaker) {
730                         sendInternalMessage(SWITCH_SPEAKER);
731                     } else {
732                         sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
733                     }
734                     return HANDLED;
735                 case BT_AUDIO_DISCONNECTED:
736                     // This may be sent as a confirmation by the BT stack after switch off BT.
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     // Note: transitions to/from this class work a bit differently -- we delegate to
751     // BluetoothRouteManager to manage all Bluetooth state, so instead of transitioning to one of
752     // the bluetooth states immediately when there's an request to do so, we wait for
753     // BluetoothRouteManager to report its state before we go into this state.
754     class ActiveBluetoothRoute extends BluetoothRoute {
755         @Override
getName()756         public String getName() {
757             return ACTIVE_BLUETOOTH_ROUTE_NAME;
758         }
759 
760         @Override
isActive()761         public boolean isActive() {
762             return true;
763         }
764 
765         @Override
enter()766         public void enter() {
767             super.enter();
768             setSpeakerphoneOn(false);
769             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
770                     mAvailableRoutes, mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
771                     mBluetoothRouteManager.getConnectedDevices());
772             setSystemAudioState(newState, true);
773             updateInternalCallAudioState();
774             // Do not send RINGER_MODE_CHANGE if no Bluetooth SCO audio device is available
775             if (mBluetoothRouteManager.getBluetoothAudioConnectedDevice() != null) {
776                 mCallAudioManager.onRingerModeChange();
777             }
778         }
779 
780         @Override
updateSystemAudioState()781         public void updateSystemAudioState() {
782             updateInternalCallAudioState();
783             setSystemAudioState(mCurrentCallAudioState);
784         }
785 
786         @Override
handleBtInitiatedDisconnect()787         public void handleBtInitiatedDisconnect() {
788             // There's special-case state transitioning here -- if BT tells us that
789             // something got disconnected, we don't want to disconnect BT before
790             // transitioning, since BT might be trying to connect another device in the
791             // meantime.
792             int command = calculateBaselineRouteMessage(false, false);
793             switch (command) {
794                 case SWITCH_EARPIECE:
795                     transitionTo(mActiveEarpieceRoute);
796                     break;
797                 case SWITCH_HEADSET:
798                     transitionTo(mActiveHeadsetRoute);
799                     break;
800                 case SWITCH_SPEAKER:
801                     transitionTo(mActiveSpeakerRoute);
802                     break;
803                 default:
804                     Log.w(this, "Got unexpected code " + command + " when processing a"
805                             + " BT-initiated audio disconnect");
806                     // Some fallback logic to make sure we make it off the bluetooth route.
807                     super.handleBtInitiatedDisconnect();
808                     break;
809             }
810         }
811 
812         @Override
processMessage(Message msg)813         public boolean processMessage(Message msg) {
814             if (super.processMessage(msg) == HANDLED) {
815                 return HANDLED;
816             }
817             switch (msg.what) {
818                 case USER_SWITCH_EARPIECE:
819                     mHasUserExplicitlyLeftBluetooth = true;
820                     // fall through
821                 case SWITCH_EARPIECE:
822                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
823                         setBluetoothOff();
824                         transitionTo(mActiveEarpieceRoute);
825                     } else {
826                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
827                     }
828                     return HANDLED;
829                 case BT_AUDIO_CONNECTED:
830                     // Send ringer mode change because we transit to ActiveBluetoothState even
831                     // when HFP is connecting
832                     mCallAudioManager.onRingerModeChange();
833                     // Update the in-call app on the new active BT device in case that changed.
834                     updateSystemAudioState();
835                     return HANDLED;
836                 case SWITCH_BLUETOOTH:
837                 case USER_SWITCH_BLUETOOTH:
838                     String address = (msg.obj instanceof SomeArgs) ?
839                             (String) ((SomeArgs) msg.obj).arg2 : null;
840                     setBluetoothOn(address);
841                     return HANDLED;
842                 case USER_SWITCH_HEADSET:
843                     mHasUserExplicitlyLeftBluetooth = true;
844                     // fall through
845                 case SWITCH_HEADSET:
846                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
847                         setBluetoothOff();
848                         transitionTo(mActiveHeadsetRoute);
849                     } else {
850                         Log.w(this, "Ignoring switch to headset command. Not available.");
851                     }
852                     return HANDLED;
853                 case USER_SWITCH_SPEAKER:
854                     mHasUserExplicitlyLeftBluetooth = true;
855                     // fall through
856                 case SWITCH_SPEAKER:
857                 case SPEAKER_ON:
858                     setBluetoothOff();
859                     transitionTo(mActiveSpeakerRoute);
860                     return HANDLED;
861                 case SPEAKER_OFF:
862                     return HANDLED;
863                 case SWITCH_FOCUS:
864                     if (msg.arg1 == NO_FOCUS) {
865                         // Only disconnect SCO audio here instead of routing away from BT entirely.
866                         mBluetoothRouteManager.disconnectSco();
867                         reinitialize();
868                         mCallAudioManager.notifyAudioOperationsComplete();
869                     } else if (msg.arg1 == RINGING_FOCUS
870                             && !mBluetoothRouteManager.isInbandRingingEnabled()) {
871                         setBluetoothOff();
872                         transitionTo(mRingingBluetoothRoute);
873                     }
874                     return HANDLED;
875                 case BT_AUDIO_DISCONNECTED:
876                     handleBtInitiatedDisconnect();
877                     return HANDLED;
878                 default:
879                     return NOT_HANDLED;
880             }
881         }
882     }
883 
884     // This state is only used when the device doesn't support in-band ring. If it does,
885     // ActiveBluetoothRoute is used instead.
886     class RingingBluetoothRoute extends BluetoothRoute {
887         @Override
getName()888         public String getName() {
889             return RINGING_BLUETOOTH_ROUTE_NAME;
890         }
891 
892         @Override
isActive()893         public boolean isActive() {
894             return false;
895         }
896 
897         @Override
enter()898         public void enter() {
899             super.enter();
900             setSpeakerphoneOn(false);
901             // Do not enable SCO audio here, since RING is being sent to the headset.
902             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
903                     mAvailableRoutes, mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
904                     mBluetoothRouteManager.getConnectedDevices());
905             setSystemAudioState(newState);
906             updateInternalCallAudioState();
907         }
908 
909         @Override
updateSystemAudioState()910         public void updateSystemAudioState() {
911             updateInternalCallAudioState();
912             setSystemAudioState(mCurrentCallAudioState);
913         }
914 
915         @Override
processMessage(Message msg)916         public boolean processMessage(Message msg) {
917             if (super.processMessage(msg) == HANDLED) {
918                 return HANDLED;
919             }
920             switch (msg.what) {
921                 case USER_SWITCH_EARPIECE:
922                     mHasUserExplicitlyLeftBluetooth = true;
923                     // fall through
924                 case SWITCH_EARPIECE:
925                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
926                         transitionTo(mActiveEarpieceRoute);
927                     } else {
928                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
929                     }
930                     return HANDLED;
931                 case BT_AUDIO_CONNECTED:
932                     transitionTo(mActiveBluetoothRoute);
933                     return HANDLED;
934                 case SWITCH_BLUETOOTH:
935                 case USER_SWITCH_BLUETOOTH:
936                     // Nothing to do
937                     return HANDLED;
938                 case USER_SWITCH_HEADSET:
939                     mHasUserExplicitlyLeftBluetooth = true;
940                     // fall through
941                 case SWITCH_HEADSET:
942                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
943                         transitionTo(mActiveHeadsetRoute);
944                     } else {
945                         Log.w(this, "Ignoring switch to headset command. Not available.");
946                     }
947                     return HANDLED;
948                 case USER_SWITCH_SPEAKER:
949                     mHasUserExplicitlyLeftBluetooth = true;
950                     // fall through
951                 case SWITCH_SPEAKER:
952                 case SPEAKER_ON:
953                     transitionTo(mActiveSpeakerRoute);
954                     return HANDLED;
955                 case SPEAKER_OFF:
956                     return HANDLED;
957                 case SWITCH_FOCUS:
958                     if (msg.arg1 == NO_FOCUS) {
959                         reinitialize();
960                         mCallAudioManager.notifyAudioOperationsComplete();
961                     } else if (msg.arg1 == ACTIVE_FOCUS) {
962                         setBluetoothOn(null);
963                     }
964                     return HANDLED;
965                 case BT_AUDIO_DISCONNECTED:
966                     // Ignore this -- audio disconnecting while ringing w/o in-band should not
967                     // cause a route switch, since the device is still connected.
968                     return HANDLED;
969                 default:
970                     return NOT_HANDLED;
971             }
972         }
973     }
974 
975     class QuiescentBluetoothRoute extends BluetoothRoute {
976         @Override
getName()977         public String getName() {
978             return QUIESCENT_BLUETOOTH_ROUTE_NAME;
979         }
980 
981         @Override
isActive()982         public boolean isActive() {
983             return false;
984         }
985 
986         @Override
enter()987         public void enter() {
988             super.enter();
989             mHasUserExplicitlyLeftBluetooth = false;
990             updateInternalCallAudioState();
991         }
992 
993         @Override
updateSystemAudioState()994         public void updateSystemAudioState() {
995             updateInternalCallAudioState();
996         }
997 
998         @Override
processMessage(Message msg)999         public boolean processMessage(Message msg) {
1000             if (super.processMessage(msg) == HANDLED) {
1001                 return HANDLED;
1002             }
1003             switch (msg.what) {
1004                 case SWITCH_EARPIECE:
1005                 case USER_SWITCH_EARPIECE:
1006                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
1007                         transitionTo(mQuiescentEarpieceRoute);
1008                     } else {
1009                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
1010                     }
1011                     return HANDLED;
1012                 case BT_AUDIO_CONNECTED:
1013                     transitionTo(mActiveBluetoothRoute);
1014                     return HANDLED;
1015                 case SWITCH_BLUETOOTH:
1016                 case USER_SWITCH_BLUETOOTH:
1017                 case SPEAKER_ON:
1018                     // Ignore speakerphone state changes outside of calls.
1019                 case SPEAKER_OFF:
1020                     // Nothing to do
1021                     return HANDLED;
1022                 case SWITCH_HEADSET:
1023                 case USER_SWITCH_HEADSET:
1024                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
1025                         transitionTo(mQuiescentHeadsetRoute);
1026                     } else {
1027                         Log.w(this, "Ignoring switch to headset command. Not available.");
1028                     }
1029                     return HANDLED;
1030                 case SWITCH_SPEAKER:
1031                 case USER_SWITCH_SPEAKER:
1032                     transitionTo(mQuiescentSpeakerRoute);
1033                     return HANDLED;
1034                 case SWITCH_FOCUS:
1035                     if (msg.arg1 == ACTIVE_FOCUS) {
1036                         setBluetoothOn(null);
1037                     } else if (msg.arg1 == RINGING_FOCUS) {
1038                         if (mBluetoothRouteManager.isInbandRingingEnabled()) {
1039                             setBluetoothOn(null);
1040                         } else {
1041                             transitionTo(mRingingBluetoothRoute);
1042                         }
1043                     }
1044                     return HANDLED;
1045                 case BT_AUDIO_DISCONNECTED:
1046                     // Ignore this -- audio disconnecting while quiescent should not cause a
1047                     // route switch, since the device is still connected.
1048                     return HANDLED;
1049                 default:
1050                     return NOT_HANDLED;
1051             }
1052         }
1053     }
1054 
1055     abstract class BluetoothRoute extends AudioState {
1056         @Override
getRouteCode()1057         public int getRouteCode() {
1058             return CallAudioState.ROUTE_BLUETOOTH;
1059         }
1060 
handleBtInitiatedDisconnect()1061         public void handleBtInitiatedDisconnect() {
1062             sendInternalMessage(SWITCH_BASELINE_ROUTE, NO_INCLUDE_BLUETOOTH_IN_BASELINE);
1063         }
1064 
1065         @Override
processMessage(Message msg)1066         public boolean processMessage(Message msg) {
1067             if (super.processMessage(msg) == HANDLED) {
1068                 return HANDLED;
1069             }
1070             switch (msg.what) {
1071                 case CONNECT_WIRED_HEADSET:
1072                     sendInternalMessage(SWITCH_HEADSET);
1073                     return HANDLED;
1074                 case BT_ACTIVE_DEVICE_PRESENT:
1075                     Log.w(this, "Bluetooth active device should not"
1076                             + " have been null while we were in BT route.");
1077                     return HANDLED;
1078                 case BT_ACTIVE_DEVICE_GONE:
1079                     handleBtInitiatedDisconnect();
1080                     mWasOnSpeaker = false;
1081                     return HANDLED;
1082                 case DISCONNECT_WIRED_HEADSET:
1083                     // No change in audio route required
1084                     return HANDLED;
1085                 case CONNECT_DOCK:
1086                     // Nothing to do here
1087                     return HANDLED;
1088                 case DISCONNECT_DOCK:
1089                     // Nothing to do here
1090                     return HANDLED;
1091                 default:
1092                     return NOT_HANDLED;
1093             }
1094         }
1095     }
1096 
1097     class ActiveSpeakerRoute extends SpeakerRoute {
1098         @Override
getName()1099         public String getName() {
1100             return ACTIVE_SPEAKER_ROUTE_NAME;
1101         }
1102 
1103         @Override
isActive()1104         public boolean isActive() {
1105             return true;
1106         }
1107 
1108         @Override
enter()1109         public void enter() {
1110             super.enter();
1111             mWasOnSpeaker = true;
1112             setSpeakerphoneOn(true);
1113             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_SPEAKER,
1114                     mAvailableRoutes, null, mBluetoothRouteManager.getConnectedDevices());
1115             setSystemAudioState(newState, true);
1116             updateInternalCallAudioState();
1117         }
1118 
1119         @Override
updateSystemAudioState()1120         public void updateSystemAudioState() {
1121             updateInternalCallAudioState();
1122             setSystemAudioState(mCurrentCallAudioState);
1123         }
1124 
1125         @Override
processMessage(Message msg)1126         public boolean processMessage(Message msg) {
1127             if (super.processMessage(msg) == HANDLED) {
1128                 return HANDLED;
1129             }
1130             switch(msg.what) {
1131                 case USER_SWITCH_EARPIECE:
1132                     mWasOnSpeaker = false;
1133                     // fall through
1134                 case SWITCH_EARPIECE:
1135                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
1136                         transitionTo(mActiveEarpieceRoute);
1137                     } else {
1138                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
1139                     }
1140                     return HANDLED;
1141                 case BT_AUDIO_CONNECTED:
1142                     transitionTo(mActiveBluetoothRoute);
1143                     return HANDLED;
1144                 case USER_SWITCH_BLUETOOTH:
1145                     mWasOnSpeaker = false;
1146                     // fall through
1147                 case SWITCH_BLUETOOTH:
1148                     String address = (msg.obj instanceof SomeArgs) ?
1149                             (String) ((SomeArgs) msg.obj).arg2 : null;
1150                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
1151                         if (mAudioFocusType == ACTIVE_FOCUS
1152                                 || mBluetoothRouteManager.isInbandRingingEnabled()) {
1153                             // Omit transition to ActiveBluetoothRoute
1154                             setBluetoothOn(address);
1155                         } else {
1156                             transitionTo(mRingingBluetoothRoute);
1157                         }
1158                     } else {
1159                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
1160                     }
1161                     return HANDLED;
1162                 case USER_SWITCH_HEADSET:
1163                     mWasOnSpeaker = false;
1164                     // fall through
1165                 case SWITCH_HEADSET:
1166                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
1167                         transitionTo(mActiveHeadsetRoute);
1168                     } else {
1169                         Log.w(this, "Ignoring switch to headset command. Not available.");
1170                     }
1171                     return HANDLED;
1172                 case SWITCH_SPEAKER:
1173                 case USER_SWITCH_SPEAKER:
1174                     // Nothing to do
1175                     return HANDLED;
1176                 case SPEAKER_ON:
1177                     // Expected, since we just transitioned here
1178                     return HANDLED;
1179                 case SPEAKER_OFF:
1180                     sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
1181                     return HANDLED;
1182                 case SWITCH_FOCUS:
1183                     if (msg.arg1 == NO_FOCUS) {
1184                         reinitialize();
1185                         mCallAudioManager.notifyAudioOperationsComplete();
1186                     }
1187                     return HANDLED;
1188                 default:
1189                     return NOT_HANDLED;
1190             }
1191         }
1192     }
1193 
1194     class QuiescentSpeakerRoute extends SpeakerRoute {
1195         @Override
getName()1196         public String getName() {
1197             return QUIESCENT_SPEAKER_ROUTE_NAME;
1198         }
1199 
1200         @Override
isActive()1201         public boolean isActive() {
1202             return false;
1203         }
1204 
1205         @Override
enter()1206         public void enter() {
1207             super.enter();
1208             mHasUserExplicitlyLeftBluetooth = false;
1209             // Omit setting mWasOnSpeaker to true here, since this does not reflect a call
1210             // actually being on speakerphone.
1211             updateInternalCallAudioState();
1212         }
1213 
1214         @Override
updateSystemAudioState()1215         public void updateSystemAudioState() {
1216             updateInternalCallAudioState();
1217         }
1218 
1219         @Override
processMessage(Message msg)1220         public boolean processMessage(Message msg) {
1221             if (super.processMessage(msg) == HANDLED) {
1222                 return HANDLED;
1223             }
1224             switch(msg.what) {
1225                 case SWITCH_EARPIECE:
1226                 case USER_SWITCH_EARPIECE:
1227                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
1228                         transitionTo(mQuiescentEarpieceRoute);
1229                     } else {
1230                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
1231                     }
1232                     return HANDLED;
1233                 case BT_AUDIO_CONNECTED:
1234                     transitionTo(mActiveBluetoothRoute);
1235                     Log.w(this, "BT audio reported as connected while in quiescent speaker");
1236                     return HANDLED;
1237                 case SWITCH_BLUETOOTH:
1238                 case USER_SWITCH_BLUETOOTH:
1239                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
1240                         transitionTo(mQuiescentBluetoothRoute);
1241                     } else {
1242                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
1243                     }
1244                     return HANDLED;
1245                 case SWITCH_HEADSET:
1246                 case USER_SWITCH_HEADSET:
1247                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
1248                         transitionTo(mQuiescentHeadsetRoute);
1249                     } else {
1250                         Log.w(this, "Ignoring switch to headset command. Not available.");
1251                     }
1252                     return HANDLED;
1253                 case SWITCH_SPEAKER:
1254                 case USER_SWITCH_SPEAKER:
1255                 case SPEAKER_ON:
1256                     // Nothing to do
1257                     return HANDLED;
1258                 case SPEAKER_OFF:
1259                     sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
1260                     return HANDLED;
1261                 case SWITCH_FOCUS:
1262                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
1263                         transitionTo(mActiveSpeakerRoute);
1264                     }
1265                     return HANDLED;
1266                 default:
1267                     return NOT_HANDLED;
1268             }
1269         }
1270     }
1271 
1272     abstract class SpeakerRoute extends AudioState {
1273         @Override
getRouteCode()1274         public int getRouteCode() {
1275             return CallAudioState.ROUTE_SPEAKER;
1276         }
1277 
1278         @Override
processMessage(Message msg)1279         public boolean processMessage(Message msg) {
1280             if (super.processMessage(msg) == HANDLED) {
1281                 return HANDLED;
1282             }
1283             switch (msg.what) {
1284                 case CONNECT_WIRED_HEADSET:
1285                     sendInternalMessage(SWITCH_HEADSET);
1286                     return HANDLED;
1287                 case BT_ACTIVE_DEVICE_PRESENT:
1288                     if (!mHasUserExplicitlyLeftBluetooth) {
1289                         sendInternalMessage(SWITCH_BLUETOOTH);
1290                     } else {
1291                         Log.i(this, "Not switching to BT route from speaker because user has " +
1292                                 "explicitly disconnected.");
1293                     }
1294                     return HANDLED;
1295                 case BT_ACTIVE_DEVICE_GONE:
1296                     // No change in audio route required
1297                     return HANDLED;
1298                 case DISCONNECT_WIRED_HEADSET:
1299                     // No change in audio route required
1300                     return HANDLED;
1301                 case BT_AUDIO_DISCONNECTED:
1302                     // This may be sent as a confirmation by the BT stack after switch off BT.
1303                     return HANDLED;
1304                 case CONNECT_DOCK:
1305                     // Nothing to do here
1306                     return HANDLED;
1307                 case DISCONNECT_DOCK:
1308                     sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
1309                     return HANDLED;
1310                default:
1311                     return NOT_HANDLED;
1312             }
1313         }
1314     }
1315 
1316     private final BroadcastReceiver mMuteChangeReceiver = new BroadcastReceiver() {
1317         @Override
1318         public void onReceive(Context context, Intent intent) {
1319             Log.startSession("CARSM.mCR");
1320             try {
1321                 if (AudioManager.ACTION_MICROPHONE_MUTE_CHANGED.equals(intent.getAction())) {
1322                     if (mCallsManager.isInEmergencyCall()) {
1323                         Log.i(this, "Mute was externally changed when there's an emergency call. " +
1324                                 "Forcing mute back off.");
1325                         sendInternalMessage(MUTE_OFF);
1326                     } else {
1327                         sendInternalMessage(MUTE_EXTERNALLY_CHANGED);
1328                     }
1329                 } else {
1330                     Log.w(this, "Received non-mute-change intent");
1331                 }
1332             } finally {
1333                 Log.endSession();
1334             }
1335         }
1336     };
1337 
1338     private final BroadcastReceiver mSpeakerPhoneChangeReceiver = new BroadcastReceiver() {
1339         @Override
1340         public void onReceive(Context context, Intent intent) {
1341             Log.startSession("CARSM.mSPCR");
1342             try {
1343                 if (AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED.equals(intent.getAction())) {
1344                     if (mAudioManager != null) {
1345                         if (mAudioManager.isSpeakerphoneOn()) {
1346                             sendInternalMessage(SPEAKER_ON);
1347                         } else {
1348                             sendInternalMessage(SPEAKER_OFF);
1349                         }
1350                     }
1351                 } else {
1352                     Log.w(this, "Received non-speakerphone-change intent");
1353                 }
1354             } finally {
1355                 Log.endSession();
1356             }
1357         }
1358     };
1359 
1360     private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute();
1361     private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
1362     private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
1363     private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute();
1364     private final RingingBluetoothRoute mRingingBluetoothRoute = new RingingBluetoothRoute();
1365     private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute();
1366     private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
1367     private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
1368     private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
1369 
1370     /**
1371      * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
1372      * states
1373      */
1374     private int mDeviceSupportedRoutes;
1375     private int mAvailableRoutes;
1376     private int mAudioFocusType = NO_FOCUS;
1377     private boolean mWasOnSpeaker;
1378     private boolean mIsMuted;
1379 
1380     private final Context mContext;
1381     private final CallsManager mCallsManager;
1382     private final AudioManager mAudioManager;
1383     private final BluetoothRouteManager mBluetoothRouteManager;
1384     private final WiredHeadsetManager mWiredHeadsetManager;
1385     private final StatusBarNotifier mStatusBarNotifier;
1386     private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
1387     private boolean mDoesDeviceSupportEarpieceRoute;
1388     private final TelecomSystem.SyncRoot mLock;
1389     private boolean mHasUserExplicitlyLeftBluetooth = false;
1390 
1391     private HashMap<String, Integer> mStateNameToRouteCode;
1392     private HashMap<Integer, AudioState> mRouteCodeToQuiescentState;
1393 
1394     // CallAudioState is used as an interface to communicate with many other system components.
1395     // No internal state transitions should depend on this variable.
1396     private CallAudioState mCurrentCallAudioState;
1397     private CallAudioState mLastKnownCallAudioState;
1398 
1399     private CallAudioManager mCallAudioManager;
1400 
CallAudioRouteStateMachine( Context context, CallsManager callsManager, BluetoothRouteManager bluetoothManager, WiredHeadsetManager wiredHeadsetManager, StatusBarNotifier statusBarNotifier, CallAudioManager.AudioServiceFactory audioServiceFactory, int earpieceControl)1401     public CallAudioRouteStateMachine(
1402             Context context,
1403             CallsManager callsManager,
1404             BluetoothRouteManager bluetoothManager,
1405             WiredHeadsetManager wiredHeadsetManager,
1406             StatusBarNotifier statusBarNotifier,
1407             CallAudioManager.AudioServiceFactory audioServiceFactory,
1408             int earpieceControl) {
1409         super(NAME);
1410         mContext = context;
1411         mCallsManager = callsManager;
1412         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
1413         mBluetoothRouteManager = bluetoothManager;
1414         mWiredHeadsetManager = wiredHeadsetManager;
1415         mStatusBarNotifier = statusBarNotifier;
1416         mAudioServiceFactory = audioServiceFactory;
1417         mLock = callsManager.getLock();
1418 
1419         createStates(earpieceControl);
1420     }
1421 
1422     /** Used for testing only */
CallAudioRouteStateMachine( Context context, CallsManager callsManager, BluetoothRouteManager bluetoothManager, WiredHeadsetManager wiredHeadsetManager, StatusBarNotifier statusBarNotifier, CallAudioManager.AudioServiceFactory audioServiceFactory, int earpieceControl, Looper looper)1423     public CallAudioRouteStateMachine(
1424             Context context,
1425             CallsManager callsManager,
1426             BluetoothRouteManager bluetoothManager,
1427             WiredHeadsetManager wiredHeadsetManager,
1428             StatusBarNotifier statusBarNotifier,
1429             CallAudioManager.AudioServiceFactory audioServiceFactory,
1430             int earpieceControl, Looper looper) {
1431         super(NAME, looper);
1432         mContext = context;
1433         mCallsManager = callsManager;
1434         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
1435         mBluetoothRouteManager = bluetoothManager;
1436         mWiredHeadsetManager = wiredHeadsetManager;
1437         mStatusBarNotifier = statusBarNotifier;
1438         mAudioServiceFactory = audioServiceFactory;
1439         mLock = callsManager.getLock();
1440 
1441         createStates(earpieceControl);
1442     }
1443 
createStates(int earpieceControl)1444     private void createStates(int earpieceControl) {
1445         switch (earpieceControl) {
1446             case EARPIECE_FORCE_DISABLED:
1447                 mDoesDeviceSupportEarpieceRoute = false;
1448                 break;
1449             case EARPIECE_FORCE_ENABLED:
1450                 mDoesDeviceSupportEarpieceRoute = true;
1451                 break;
1452             default:
1453                 mDoesDeviceSupportEarpieceRoute = checkForEarpieceSupport();
1454         }
1455 
1456         addState(mActiveEarpieceRoute);
1457         addState(mActiveHeadsetRoute);
1458         addState(mActiveBluetoothRoute);
1459         addState(mActiveSpeakerRoute);
1460         addState(mRingingBluetoothRoute);
1461         addState(mQuiescentEarpieceRoute);
1462         addState(mQuiescentHeadsetRoute);
1463         addState(mQuiescentBluetoothRoute);
1464         addState(mQuiescentSpeakerRoute);
1465 
1466 
1467         mStateNameToRouteCode = new HashMap<>(8);
1468         mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE);
1469         mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH);
1470         mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
1471         mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER);
1472         mStateNameToRouteCode.put(mRingingBluetoothRoute.getName(), ROUTE_BLUETOOTH);
1473         mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE);
1474         mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
1475         mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
1476         mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER);
1477 
1478         mRouteCodeToQuiescentState = new HashMap<>(4);
1479         mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute);
1480         mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute);
1481         mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute);
1482         mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute);
1483     }
1484 
setCallAudioManager(CallAudioManager callAudioManager)1485     public void setCallAudioManager(CallAudioManager callAudioManager) {
1486         mCallAudioManager = callAudioManager;
1487     }
1488 
1489     /**
1490      * Initializes the state machine with info on initial audio route, supported audio routes,
1491      * and mute status.
1492      */
initialize()1493     public void initialize() {
1494         CallAudioState initState = getInitialAudioState();
1495         initialize(initState);
1496     }
1497 
initialize(CallAudioState initState)1498     public void initialize(CallAudioState initState) {
1499         if ((initState.getRoute() & getCurrentCallSupportedRoutes()) == 0) {
1500             Log.e(this, new IllegalArgumentException(), "Route %d specified when supported call" +
1501                     " routes are: %d", initState.getRoute(), getCurrentCallSupportedRoutes());
1502         }
1503 
1504         mCurrentCallAudioState = initState;
1505         mLastKnownCallAudioState = initState;
1506         mDeviceSupportedRoutes = initState.getSupportedRouteMask();
1507         mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
1508         mIsMuted = initState.isMuted();
1509         mWasOnSpeaker = false;
1510         mContext.registerReceiver(mMuteChangeReceiver,
1511                 new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
1512         mContext.registerReceiver(mSpeakerPhoneChangeReceiver,
1513                 new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
1514 
1515         mStatusBarNotifier.notifyMute(initState.isMuted());
1516         mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
1517         setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
1518         start();
1519     }
1520 
1521     /**
1522      * Getter for the current CallAudioState object that the state machine is keeping track of.
1523      * Used for compatibility purposes.
1524      */
getCurrentCallAudioState()1525     public CallAudioState getCurrentCallAudioState() {
1526         return mCurrentCallAudioState;
1527     }
1528 
sendMessageWithSessionInfo(int message, int arg)1529     public void sendMessageWithSessionInfo(int message, int arg) {
1530         sendMessageWithSessionInfo(message, arg, null);
1531     }
1532 
sendMessageWithSessionInfo(int message)1533     public void sendMessageWithSessionInfo(int message) {
1534         sendMessageWithSessionInfo(message, 0, null);
1535     }
1536 
sendMessageWithSessionInfo(int message, int arg, String data)1537     public void sendMessageWithSessionInfo(int message, int arg, String data) {
1538         SomeArgs args = SomeArgs.obtain();
1539         args.arg1 = Log.createSubsession();
1540         args.arg2 = data;
1541         sendMessage(message, arg, 0, args);
1542     }
1543 
1544     /**
1545      * This is for state-independent changes in audio route (i.e. muting or runnables)
1546      * @param msg that couldn't be handled.
1547      */
1548     @Override
unhandledMessage(Message msg)1549     protected void unhandledMessage(Message msg) {
1550         switch (msg.what) {
1551             case MUTE_ON:
1552                 setMuteOn(true);
1553                 updateSystemMuteState();
1554                 return;
1555             case MUTE_OFF:
1556                 setMuteOn(false);
1557                 updateSystemMuteState();
1558                 return;
1559             case MUTE_EXTERNALLY_CHANGED:
1560                 mIsMuted = mAudioManager.isMicrophoneMute();
1561                 if (isInActiveState()) {
1562                     updateSystemMuteState();
1563                 }
1564                 return;
1565             case TOGGLE_MUTE:
1566                 if (mIsMuted) {
1567                     sendInternalMessage(MUTE_OFF);
1568                 } else {
1569                     sendInternalMessage(MUTE_ON);
1570                 }
1571                 return;
1572             case UPDATE_SYSTEM_AUDIO_ROUTE:
1573                 updateInternalCallAudioState();
1574                 updateRouteForForegroundCall();
1575                 resendSystemAudioState();
1576                 return;
1577             case RUN_RUNNABLE:
1578                 java.lang.Runnable r = (java.lang.Runnable) msg.obj;
1579                 r.run();
1580                 return;
1581             default:
1582                 Log.e(this, new IllegalStateException(), "Unexpected message code %d", msg.what);
1583         }
1584     }
1585 
quitStateMachine()1586     public void quitStateMachine() {
1587         quitNow();
1588     }
1589 
dumpPendingMessages(IndentingPrintWriter pw)1590     public void dumpPendingMessages(IndentingPrintWriter pw) {
1591         getHandler().getLooper().dump(pw::println, "");
1592     }
1593 
isHfpDeviceAvailable()1594     public boolean isHfpDeviceAvailable() {
1595         return mBluetoothRouteManager.isBluetoothAvailable();
1596     }
1597 
setSpeakerphoneOn(boolean on)1598     private void setSpeakerphoneOn(boolean on) {
1599         if (mAudioManager.isSpeakerphoneOn() != on) {
1600             Log.i(this, "turning speaker phone %s", on);
1601             mAudioManager.setSpeakerphoneOn(on);
1602         } else {
1603             Log.i(this, "Ignoring speakerphone request -- already %s", on);
1604         }
1605         mStatusBarNotifier.notifySpeakerphone(on);
1606     }
1607 
setBluetoothOn(String address)1608     private void setBluetoothOn(String address) {
1609         if (mBluetoothRouteManager.isBluetoothAvailable()) {
1610             BluetoothDevice connectedDevice =
1611                     mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
1612             if (address == null && connectedDevice != null) {
1613                 // null means connect to any device, so if we're already connected to some device,
1614                 // that means we can just tell ourselves that it's connected.
1615                 // Do still try to connect audio though, so that BluetoothRouteManager knows that
1616                 // there's an active call.
1617                 Log.i(this, "Bluetooth audio already on.");
1618                 sendInternalMessage(BT_AUDIO_CONNECTED);
1619                 mBluetoothRouteManager.connectBluetoothAudio(connectedDevice.getAddress());
1620                 return;
1621             }
1622             if (connectedDevice == null || !Objects.equals(address, connectedDevice.getAddress())) {
1623                 Log.i(this, "connecting bluetooth audio: %s", address);
1624                 mBluetoothRouteManager.connectBluetoothAudio(address);
1625             }
1626         }
1627     }
1628 
setBluetoothOff()1629     private void setBluetoothOff() {
1630         if (mBluetoothRouteManager.isBluetoothAvailable()) {
1631             if (mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()) {
1632                 Log.i(this, "disconnecting bluetooth audio");
1633                 mBluetoothRouteManager.disconnectBluetoothAudio();
1634             }
1635         }
1636     }
1637 
setMuteOn(boolean mute)1638     private void setMuteOn(boolean mute) {
1639         mIsMuted = mute;
1640         Log.addEvent(mCallsManager.getForegroundCall(), mute ?
1641                 LogUtils.Events.MUTE : LogUtils.Events.UNMUTE);
1642         if (mute != mAudioManager.isMicrophoneMute() && isInActiveState()) {
1643             IAudioService audio = mAudioServiceFactory.getAudioService();
1644             Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]",
1645                     mute, audio == null);
1646             if (audio != null) {
1647                 try {
1648                     // We use the audio service directly here so that we can specify
1649                     // the current user. Telecom runs in the system_server process which
1650                     // may run as a separate user from the foreground user. If we
1651                     // used AudioManager directly, we would change mute for the system's
1652                     // user and not the current foreground, which we want to avoid.
1653                     audio.setMicrophoneMute(
1654                             mute, mContext.getOpPackageName(), getCurrentUserId());
1655                 } catch (RemoteException e) {
1656                     Log.e(this, e, "Remote exception while toggling mute.");
1657                 }
1658                 // TODO: Check microphone state after attempting to set to ensure that
1659                 // our state corroborates AudioManager's state.
1660             }
1661         }
1662     }
1663 
updateSystemMuteState()1664     private void updateSystemMuteState() {
1665         CallAudioState newCallAudioState = new CallAudioState(mIsMuted,
1666                 mCurrentCallAudioState.getRoute(),
1667                 mAvailableRoutes,
1668                 mCurrentCallAudioState.getActiveBluetoothDevice(),
1669                 mBluetoothRouteManager.getConnectedDevices());
1670         setSystemAudioState(newCallAudioState);
1671         updateInternalCallAudioState();
1672     }
1673 
1674     /**
1675      * Updates the CallAudioState object from current internal state. The result is used for
1676      * external communication only.
1677      */
updateInternalCallAudioState()1678     private void updateInternalCallAudioState() {
1679         IState currentState = getCurrentState();
1680         if (currentState == null) {
1681             Log.e(this, new IllegalStateException(), "Current state should never be null" +
1682                     " when updateInternalCallAudioState is called.");
1683             mCurrentCallAudioState = new CallAudioState(
1684                     mIsMuted, mCurrentCallAudioState.getRoute(), mAvailableRoutes,
1685                     mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
1686                     mBluetoothRouteManager.getConnectedDevices());
1687             return;
1688         }
1689         int currentRoute = mStateNameToRouteCode.get(currentState.getName());
1690         mCurrentCallAudioState = new CallAudioState(mIsMuted, currentRoute, mAvailableRoutes,
1691                 mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
1692                 mBluetoothRouteManager.getConnectedDevices());
1693     }
1694 
setSystemAudioState(CallAudioState newCallAudioState)1695     private void setSystemAudioState(CallAudioState newCallAudioState) {
1696         setSystemAudioState(newCallAudioState, false);
1697     }
1698 
resendSystemAudioState()1699     private void resendSystemAudioState() {
1700         setSystemAudioState(mLastKnownCallAudioState, true);
1701     }
1702 
setSystemAudioState(CallAudioState newCallAudioState, boolean force)1703     private void setSystemAudioState(CallAudioState newCallAudioState, boolean force) {
1704         synchronized (mLock) {
1705             Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
1706                     newCallAudioState);
1707             if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) {
1708                 mStatusBarNotifier.notifyMute(newCallAudioState.isMuted());
1709                 mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
1710                 updateAudioForForegroundCall(newCallAudioState);
1711                 mLastKnownCallAudioState = newCallAudioState;
1712             }
1713         }
1714     }
1715 
updateAudioForForegroundCall(CallAudioState newCallAudioState)1716     private void updateAudioForForegroundCall(CallAudioState newCallAudioState) {
1717         Call call = mCallsManager.getForegroundCall();
1718         if (call != null && call.getConnectionService() != null) {
1719             call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
1720         }
1721     }
1722 
calculateSupportedRoutes()1723     private int calculateSupportedRoutes() {
1724         int routeMask = CallAudioState.ROUTE_SPEAKER;
1725 
1726         if (mWiredHeadsetManager.isPluggedIn()) {
1727             routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
1728         } else if (mDoesDeviceSupportEarpieceRoute){
1729             routeMask |= CallAudioState.ROUTE_EARPIECE;
1730         }
1731 
1732         if (mBluetoothRouteManager.isBluetoothAvailable()) {
1733             routeMask |=  CallAudioState.ROUTE_BLUETOOTH;
1734         }
1735 
1736         return routeMask;
1737     }
1738 
sendInternalMessage(int messageCode)1739     private void sendInternalMessage(int messageCode) {
1740         sendInternalMessage(messageCode, 0);
1741     }
1742 
sendInternalMessage(int messageCode, int arg1)1743     private void sendInternalMessage(int messageCode, int arg1) {
1744         // Internal messages are messages which the state machine sends to itself in the
1745         // course of processing externally-sourced messages. We want to send these messages at
1746         // the front of the queue in order to make actions appear atomic to the user and to
1747         // prevent scenarios such as these:
1748         // 1. State machine handler thread is suspended for some reason.
1749         // 2. Headset gets connected (sends CONNECT_HEADSET).
1750         // 3. User switches to speakerphone in the UI (sends SWITCH_SPEAKER).
1751         // 4. State machine handler is un-suspended.
1752         // 5. State machine handler processes the CONNECT_HEADSET message and sends
1753         //    SWITCH_HEADSET at end of queue.
1754         // 6. State machine handler processes SWITCH_SPEAKER.
1755         // 7. State machine handler processes SWITCH_HEADSET.
1756         Session subsession = Log.createSubsession();
1757         if(subsession != null) {
1758             SomeArgs args = SomeArgs.obtain();
1759             args.arg1 = subsession;
1760             sendMessageAtFrontOfQueue(messageCode, arg1, 0, args);
1761         } else {
1762             sendMessageAtFrontOfQueue(messageCode, arg1);
1763         }
1764     }
1765 
getInitialAudioState()1766     private CallAudioState getInitialAudioState() {
1767         int supportedRouteMask = calculateSupportedRoutes() & getCurrentCallSupportedRoutes();
1768         final int route;
1769 
1770         if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0
1771                 && mBluetoothRouteManager.hasBtActiveDevice()) {
1772             route = ROUTE_BLUETOOTH;
1773         } else if ((supportedRouteMask & ROUTE_WIRED_HEADSET) != 0) {
1774             route = ROUTE_WIRED_HEADSET;
1775         } else if ((supportedRouteMask & ROUTE_EARPIECE) != 0) {
1776             route = ROUTE_EARPIECE;
1777         } else {
1778             route = ROUTE_SPEAKER;
1779         }
1780 
1781         return new CallAudioState(false, route, supportedRouteMask, null,
1782                 mBluetoothRouteManager.getConnectedDevices());
1783     }
1784 
getCurrentUserId()1785     private int getCurrentUserId() {
1786         final long ident = Binder.clearCallingIdentity();
1787         try {
1788             UserInfo currentUser = ActivityManager.getService().getCurrentUser();
1789             return currentUser.id;
1790         } catch (RemoteException e) {
1791             // Activity manager not running, nothing we can do assume user 0.
1792         } finally {
1793             Binder.restoreCallingIdentity(ident);
1794         }
1795         return UserHandle.USER_OWNER;
1796     }
1797 
isInActiveState()1798     public boolean isInActiveState() {
1799         AudioState currentState = (AudioState) getCurrentState();
1800         if (currentState == null) {
1801             Log.w(this, "Current state is null, assuming inactive state");
1802             return false;
1803         }
1804         return currentState.isActive();
1805     }
1806 
checkForEarpieceSupport()1807     private boolean checkForEarpieceSupport() {
1808         AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
1809         for (AudioDeviceInfo device: deviceList) {
1810             if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
1811                 return true;
1812             }
1813         }
1814         // No earpiece found
1815         return false;
1816     }
1817 
calculateBaselineRouteMessage(boolean isExplicitUserRequest, boolean includeBluetooth)1818     private int calculateBaselineRouteMessage(boolean isExplicitUserRequest,
1819             boolean includeBluetooth) {
1820         boolean isSkipEarpiece = false;
1821         if (!isExplicitUserRequest) {
1822             synchronized (mLock) {
1823                 // Check video calls to skip earpiece since the baseline for video
1824                 // calls should be the speakerphone route
1825                 isSkipEarpiece = mCallsManager.hasVideoCall();
1826             }
1827         }
1828         if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0
1829                 && !mHasUserExplicitlyLeftBluetooth
1830                 && includeBluetooth) {
1831             return isExplicitUserRequest ? USER_SWITCH_BLUETOOTH : SWITCH_BLUETOOTH;
1832         } else if ((mAvailableRoutes & ROUTE_EARPIECE) != 0 && !isSkipEarpiece) {
1833             return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE;
1834         } else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
1835             return isExplicitUserRequest ? USER_SWITCH_HEADSET : SWITCH_HEADSET;
1836         } else {
1837             return isExplicitUserRequest ? USER_SWITCH_SPEAKER : SWITCH_SPEAKER;
1838         }
1839     }
1840 
reinitialize()1841     private void reinitialize() {
1842         CallAudioState initState = getInitialAudioState();
1843         mDeviceSupportedRoutes = initState.getSupportedRouteMask();
1844         mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
1845         mIsMuted = initState.isMuted();
1846         setSpeakerphoneOn(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
1847         setMuteOn(mIsMuted);
1848         mWasOnSpeaker = false;
1849         mHasUserExplicitlyLeftBluetooth = false;
1850         mLastKnownCallAudioState = initState;
1851         transitionTo(mRouteCodeToQuiescentState.get(initState.getRoute()));
1852     }
1853 
updateRouteForForegroundCall()1854     private void updateRouteForForegroundCall() {
1855         mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
1856 
1857         CallAudioState currentState = getCurrentCallAudioState();
1858 
1859         // Move to baseline route in the case the current route is no longer available.
1860         if ((mAvailableRoutes & currentState.getRoute()) == 0) {
1861             sendInternalMessage(calculateBaselineRouteMessage(false, true));
1862         }
1863     }
1864 
getCurrentCallSupportedRoutes()1865     private int getCurrentCallSupportedRoutes() {
1866         int supportedRoutes = CallAudioState.ROUTE_ALL;
1867 
1868         if (mCallsManager.getForegroundCall() != null) {
1869             supportedRoutes &= mCallsManager.getForegroundCall().getSupportedAudioRoutes();
1870         }
1871 
1872         return supportedRoutes;
1873     }
1874 
modifyRoutes(int base, int remove, int add, boolean considerCurrentCall)1875     private int modifyRoutes(int base, int remove, int add, boolean considerCurrentCall) {
1876         base &= ~remove;
1877 
1878         if (considerCurrentCall) {
1879             add &= getCurrentCallSupportedRoutes();
1880         }
1881 
1882         base |= add;
1883 
1884         return base;
1885     }
1886 }
1887