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