• 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 CONNECT_DOCK:
415                     // fall through; we want to switch to speaker mode when docked and in a call.
416                 case SWITCH_SPEAKER:
417                 case USER_SWITCH_SPEAKER:
418                     setSpeakerphoneOn(true);
419                     // fall through
420                 case SPEAKER_ON:
421                     transitionTo(mActiveSpeakerRoute);
422                     return HANDLED;
423                 case SWITCH_FOCUS:
424                     if (msg.arg1 == NO_FOCUS) {
425                         reinitialize();
426                         mCallAudioManager.notifyAudioOperationsComplete();
427                     }
428                     return HANDLED;
429                 default:
430                     return NOT_HANDLED;
431             }
432         }
433     }
434 
435     class QuiescentEarpieceRoute extends EarpieceRoute {
436         @Override
getName()437         public String getName() {
438             return QUIESCENT_EARPIECE_ROUTE_NAME;
439         }
440 
441         @Override
isActive()442         public boolean isActive() {
443             return false;
444         }
445 
446         @Override
enter()447         public void enter() {
448             super.enter();
449             mHasUserExplicitlyLeftBluetooth = false;
450             updateInternalCallAudioState();
451         }
452 
453         @Override
updateSystemAudioState()454         public void updateSystemAudioState() {
455             updateInternalCallAudioState();
456         }
457 
458         @Override
processMessage(Message msg)459         public boolean processMessage(Message msg) {
460             if (super.processMessage(msg) == HANDLED) {
461                 return HANDLED;
462             }
463             switch (msg.what) {
464                 case SWITCH_EARPIECE:
465                 case USER_SWITCH_EARPIECE:
466                 case SPEAKER_ON:
467                     // Ignore speakerphone state changes outside of calls.
468                 case SPEAKER_OFF:
469                     // Nothing to do here
470                     return HANDLED;
471                 case BT_AUDIO_CONNECTED:
472                     Log.w(this, "BT Audio came on in quiescent earpiece route.");
473                     transitionTo(mActiveBluetoothRoute);
474                     return HANDLED;
475                 case SWITCH_BLUETOOTH:
476                 case USER_SWITCH_BLUETOOTH:
477                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
478                         transitionTo(mQuiescentBluetoothRoute);
479                     } else {
480                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
481                     }
482                     return HANDLED;
483                 case SWITCH_HEADSET:
484                 case USER_SWITCH_HEADSET:
485                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
486                         transitionTo(mQuiescentHeadsetRoute);
487                     } else {
488                         Log.w(this, "Ignoring switch to headset command. Not available.");
489                     }
490                     return HANDLED;
491                 case CONNECT_DOCK:
492                     // fall through; we want to go to the quiescent speaker route when out of a call
493                 case SWITCH_SPEAKER:
494                 case USER_SWITCH_SPEAKER:
495                     transitionTo(mQuiescentSpeakerRoute);
496                     return HANDLED;
497                 case SWITCH_FOCUS:
498                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
499                         transitionTo(mActiveEarpieceRoute);
500                     } else {
501                         mCallAudioManager.notifyAudioOperationsComplete();
502                     }
503                     return HANDLED;
504                 default:
505                     return NOT_HANDLED;
506             }
507         }
508     }
509 
510     abstract class EarpieceRoute extends AudioState {
511         @Override
getRouteCode()512         public int getRouteCode() {
513             return CallAudioState.ROUTE_EARPIECE;
514         }
515 
516         @Override
processMessage(Message msg)517         public boolean processMessage(Message msg) {
518             if (super.processMessage(msg) == HANDLED) {
519                 return HANDLED;
520             }
521             switch (msg.what) {
522                 case CONNECT_WIRED_HEADSET:
523                     sendInternalMessage(SWITCH_HEADSET);
524                     return HANDLED;
525                 case BT_ACTIVE_DEVICE_PRESENT:
526                     if (!mHasUserExplicitlyLeftBluetooth) {
527                         sendInternalMessage(SWITCH_BLUETOOTH);
528                     } else {
529                         Log.i(this, "Not switching to BT route from earpiece because user has " +
530                                 "explicitly disconnected.");
531                     }
532                     return HANDLED;
533                 case BT_ACTIVE_DEVICE_GONE:
534                     // No change in audio route required
535                     return HANDLED;
536                 case DISCONNECT_WIRED_HEADSET:
537                     Log.e(this, new IllegalStateException(),
538                             "Wired headset should not go from connected to not when on " +
539                             "earpiece");
540                     return HANDLED;
541                 case BT_AUDIO_DISCONNECTED:
542                     // This may be sent as a confirmation by the BT stack after switch off BT.
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 audio here instead of routing away from BT entirely.
879                         mBluetoothRouteManager.disconnectAudio();
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 DISCONNECT_DOCK:
1277                     // Fall-through; same as if speaker goes off, we want to switch baseline.
1278                 case SPEAKER_OFF:
1279                     sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
1280                     return HANDLED;
1281                 case SWITCH_FOCUS:
1282                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
1283                         setSpeakerphoneOn(true);
1284                         transitionTo(mActiveSpeakerRoute);
1285                     } else {
1286                         mCallAudioManager.notifyAudioOperationsComplete();
1287                     }
1288                     return HANDLED;
1289                 default:
1290                     return NOT_HANDLED;
1291             }
1292         }
1293     }
1294 
1295     abstract class SpeakerRoute extends AudioState {
1296         @Override
getRouteCode()1297         public int getRouteCode() {
1298             return CallAudioState.ROUTE_SPEAKER;
1299         }
1300 
1301         @Override
processMessage(Message msg)1302         public boolean processMessage(Message msg) {
1303             if (super.processMessage(msg) == HANDLED) {
1304                 return HANDLED;
1305             }
1306             switch (msg.what) {
1307                 case CONNECT_WIRED_HEADSET:
1308                     sendInternalMessage(SWITCH_HEADSET);
1309                     return HANDLED;
1310                 case BT_ACTIVE_DEVICE_PRESENT:
1311                     if (!mHasUserExplicitlyLeftBluetooth) {
1312                         sendInternalMessage(SWITCH_BLUETOOTH);
1313                     } else {
1314                         Log.i(this, "Not switching to BT route from speaker because user has " +
1315                                 "explicitly disconnected.");
1316                     }
1317                     return HANDLED;
1318                 case BT_ACTIVE_DEVICE_GONE:
1319                     // No change in audio route required
1320                     return HANDLED;
1321                 case DISCONNECT_WIRED_HEADSET:
1322                     // No change in audio route required
1323                     return HANDLED;
1324                 case BT_AUDIO_DISCONNECTED:
1325                     // This may be sent as a confirmation by the BT stack after switch off BT.
1326                     return HANDLED;
1327                 case CONNECT_DOCK:
1328                     // Nothing to do here
1329                     return HANDLED;
1330                 case DISCONNECT_DOCK:
1331                     sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
1332                     return HANDLED;
1333                default:
1334                     return NOT_HANDLED;
1335             }
1336         }
1337     }
1338 
1339     private final BroadcastReceiver mMuteChangeReceiver = new BroadcastReceiver() {
1340         @Override
1341         public void onReceive(Context context, Intent intent) {
1342             Log.startSession("CARSM.mCR");
1343             try {
1344                 if (AudioManager.ACTION_MICROPHONE_MUTE_CHANGED.equals(intent.getAction())) {
1345                     if (mCallsManager.isInEmergencyCall()) {
1346                         Log.i(this, "Mute was externally changed when there's an emergency call. " +
1347                                 "Forcing mute back off.");
1348                         sendInternalMessage(MUTE_OFF);
1349                     } else {
1350                         sendInternalMessage(MUTE_EXTERNALLY_CHANGED);
1351                     }
1352                 } else if (AudioManager.STREAM_MUTE_CHANGED_ACTION.equals(intent.getAction())) {
1353                     int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
1354                     boolean isStreamMuted = intent.getBooleanExtra(
1355                             AudioManager.EXTRA_STREAM_VOLUME_MUTED, false);
1356 
1357                     if (streamType == AudioManager.STREAM_RING && !isStreamMuted) {
1358                         Log.i(this, "Ring stream was un-muted.");
1359                         mCallAudioManager.onRingerModeChange();
1360                     }
1361                 } else {
1362                     Log.w(this, "Received non-mute-change intent");
1363                 }
1364             } finally {
1365                 Log.endSession();
1366             }
1367         }
1368     };
1369 
1370     private final BroadcastReceiver mSpeakerPhoneChangeReceiver = new BroadcastReceiver() {
1371         @Override
1372         public void onReceive(Context context, Intent intent) {
1373             Log.startSession("CARSM.mSPCR");
1374             try {
1375                 if (AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED.equals(intent.getAction())) {
1376                     if (mAudioManager != null) {
1377                         if (mAudioManager.isSpeakerphoneOn()) {
1378                             sendInternalMessage(SPEAKER_ON);
1379                         } else {
1380                             sendInternalMessage(SPEAKER_OFF);
1381                         }
1382                     }
1383                 } else {
1384                     Log.w(this, "Received non-speakerphone-change intent");
1385                 }
1386             } finally {
1387                 Log.endSession();
1388             }
1389         }
1390     };
1391 
1392     private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute();
1393     private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
1394     private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
1395     private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute();
1396     private final RingingBluetoothRoute mRingingBluetoothRoute = new RingingBluetoothRoute();
1397     private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute();
1398     private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
1399     private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
1400     private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
1401 
1402     /**
1403      * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
1404      * states
1405      */
1406     private int mDeviceSupportedRoutes;
1407     private int mAvailableRoutes;
1408     private int mAudioFocusType = NO_FOCUS;
1409     private boolean mWasOnSpeaker;
1410     private boolean mIsMuted;
1411 
1412     private final Context mContext;
1413     private final CallsManager mCallsManager;
1414     private final AudioManager mAudioManager;
1415     private final BluetoothRouteManager mBluetoothRouteManager;
1416     private final WiredHeadsetManager mWiredHeadsetManager;
1417     private final StatusBarNotifier mStatusBarNotifier;
1418     private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
1419     private boolean mDoesDeviceSupportEarpieceRoute;
1420     private final TelecomSystem.SyncRoot mLock;
1421     private boolean mHasUserExplicitlyLeftBluetooth = false;
1422 
1423     private HashMap<String, Integer> mStateNameToRouteCode;
1424     private HashMap<Integer, AudioState> mRouteCodeToQuiescentState;
1425 
1426     // CallAudioState is used as an interface to communicate with many other system components.
1427     // No internal state transitions should depend on this variable.
1428     private CallAudioState mCurrentCallAudioState;
1429     private CallAudioState mLastKnownCallAudioState;
1430 
1431     private CallAudioManager mCallAudioManager;
1432 
CallAudioRouteStateMachine( Context context, CallsManager callsManager, BluetoothRouteManager bluetoothManager, WiredHeadsetManager wiredHeadsetManager, StatusBarNotifier statusBarNotifier, CallAudioManager.AudioServiceFactory audioServiceFactory, int earpieceControl)1433     public CallAudioRouteStateMachine(
1434             Context context,
1435             CallsManager callsManager,
1436             BluetoothRouteManager bluetoothManager,
1437             WiredHeadsetManager wiredHeadsetManager,
1438             StatusBarNotifier statusBarNotifier,
1439             CallAudioManager.AudioServiceFactory audioServiceFactory,
1440             int earpieceControl) {
1441         super(NAME);
1442         mContext = context;
1443         mCallsManager = callsManager;
1444         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
1445         mBluetoothRouteManager = bluetoothManager;
1446         mWiredHeadsetManager = wiredHeadsetManager;
1447         mStatusBarNotifier = statusBarNotifier;
1448         mAudioServiceFactory = audioServiceFactory;
1449         mLock = callsManager.getLock();
1450 
1451         createStates(earpieceControl);
1452     }
1453 
1454     /** Used for testing only */
CallAudioRouteStateMachine( Context context, CallsManager callsManager, BluetoothRouteManager bluetoothManager, WiredHeadsetManager wiredHeadsetManager, StatusBarNotifier statusBarNotifier, CallAudioManager.AudioServiceFactory audioServiceFactory, int earpieceControl, Looper looper)1455     public CallAudioRouteStateMachine(
1456             Context context,
1457             CallsManager callsManager,
1458             BluetoothRouteManager bluetoothManager,
1459             WiredHeadsetManager wiredHeadsetManager,
1460             StatusBarNotifier statusBarNotifier,
1461             CallAudioManager.AudioServiceFactory audioServiceFactory,
1462             int earpieceControl, Looper looper) {
1463         super(NAME, looper);
1464         mContext = context;
1465         mCallsManager = callsManager;
1466         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
1467         mBluetoothRouteManager = bluetoothManager;
1468         mWiredHeadsetManager = wiredHeadsetManager;
1469         mStatusBarNotifier = statusBarNotifier;
1470         mAudioServiceFactory = audioServiceFactory;
1471         mLock = callsManager.getLock();
1472 
1473         createStates(earpieceControl);
1474     }
1475 
createStates(int earpieceControl)1476     private void createStates(int earpieceControl) {
1477         switch (earpieceControl) {
1478             case EARPIECE_FORCE_DISABLED:
1479                 mDoesDeviceSupportEarpieceRoute = false;
1480                 break;
1481             case EARPIECE_FORCE_ENABLED:
1482                 mDoesDeviceSupportEarpieceRoute = true;
1483                 break;
1484             default:
1485                 mDoesDeviceSupportEarpieceRoute = checkForEarpieceSupport();
1486         }
1487 
1488         addState(mActiveEarpieceRoute);
1489         addState(mActiveHeadsetRoute);
1490         addState(mActiveBluetoothRoute);
1491         addState(mActiveSpeakerRoute);
1492         addState(mRingingBluetoothRoute);
1493         addState(mQuiescentEarpieceRoute);
1494         addState(mQuiescentHeadsetRoute);
1495         addState(mQuiescentBluetoothRoute);
1496         addState(mQuiescentSpeakerRoute);
1497 
1498 
1499         mStateNameToRouteCode = new HashMap<>(8);
1500         mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE);
1501         mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH);
1502         mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
1503         mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER);
1504         mStateNameToRouteCode.put(mRingingBluetoothRoute.getName(), ROUTE_BLUETOOTH);
1505         mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE);
1506         mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
1507         mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
1508         mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER);
1509 
1510         mRouteCodeToQuiescentState = new HashMap<>(4);
1511         mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute);
1512         mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute);
1513         mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute);
1514         mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute);
1515     }
1516 
setCallAudioManager(CallAudioManager callAudioManager)1517     public void setCallAudioManager(CallAudioManager callAudioManager) {
1518         mCallAudioManager = callAudioManager;
1519     }
1520 
1521     /**
1522      * Initializes the state machine with info on initial audio route, supported audio routes,
1523      * and mute status.
1524      */
initialize()1525     public void initialize() {
1526         CallAudioState initState = getInitialAudioState();
1527         initialize(initState);
1528     }
1529 
initialize(CallAudioState initState)1530     public void initialize(CallAudioState initState) {
1531         if ((initState.getRoute() & getCurrentCallSupportedRoutes()) == 0) {
1532             Log.e(this, new IllegalArgumentException(), "Route %d specified when supported call" +
1533                     " routes are: %d", initState.getRoute(), getCurrentCallSupportedRoutes());
1534         }
1535 
1536         mCurrentCallAudioState = initState;
1537         mLastKnownCallAudioState = initState;
1538         mDeviceSupportedRoutes = initState.getSupportedRouteMask();
1539         mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
1540         mIsMuted = initState.isMuted();
1541         mWasOnSpeaker = false;
1542         mContext.registerReceiver(mMuteChangeReceiver,
1543                 new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
1544         mContext.registerReceiver(mMuteChangeReceiver,
1545                 new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION));
1546         mContext.registerReceiver(mSpeakerPhoneChangeReceiver,
1547                 new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
1548 
1549         mStatusBarNotifier.notifyMute(initState.isMuted());
1550         mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
1551         setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
1552         start();
1553     }
1554 
1555     /**
1556      * Getter for the current CallAudioState object that the state machine is keeping track of.
1557      * Used for compatibility purposes.
1558      */
getCurrentCallAudioState()1559     public CallAudioState getCurrentCallAudioState() {
1560         return mCurrentCallAudioState;
1561     }
1562 
sendMessageWithSessionInfo(int message, int arg)1563     public void sendMessageWithSessionInfo(int message, int arg) {
1564         sendMessageWithSessionInfo(message, arg, null);
1565     }
1566 
sendMessageWithSessionInfo(int message)1567     public void sendMessageWithSessionInfo(int message) {
1568         sendMessageWithSessionInfo(message, 0, null);
1569     }
1570 
sendMessageWithSessionInfo(int message, int arg, String data)1571     public void sendMessageWithSessionInfo(int message, int arg, String data) {
1572         SomeArgs args = SomeArgs.obtain();
1573         args.arg1 = Log.createSubsession();
1574         args.arg2 = data;
1575         sendMessage(message, arg, 0, args);
1576     }
1577 
1578     /**
1579      * This is for state-independent changes in audio route (i.e. muting or runnables)
1580      * @param msg that couldn't be handled.
1581      */
1582     @Override
unhandledMessage(Message msg)1583     protected void unhandledMessage(Message msg) {
1584         switch (msg.what) {
1585             case MUTE_ON:
1586                 setMuteOn(true);
1587                 updateSystemMuteState();
1588                 return;
1589             case MUTE_OFF:
1590                 setMuteOn(false);
1591                 updateSystemMuteState();
1592                 return;
1593             case MUTE_EXTERNALLY_CHANGED:
1594                 mIsMuted = mAudioManager.isMicrophoneMute();
1595                 if (isInActiveState()) {
1596                     updateSystemMuteState();
1597                 }
1598                 return;
1599             case TOGGLE_MUTE:
1600                 if (mIsMuted) {
1601                     sendInternalMessage(MUTE_OFF);
1602                 } else {
1603                     sendInternalMessage(MUTE_ON);
1604                 }
1605                 return;
1606             case UPDATE_SYSTEM_AUDIO_ROUTE:
1607                 updateInternalCallAudioState();
1608                 updateRouteForForegroundCall();
1609                 resendSystemAudioState();
1610                 return;
1611             case RUN_RUNNABLE:
1612                 java.lang.Runnable r = (java.lang.Runnable) msg.obj;
1613                 r.run();
1614                 return;
1615             default:
1616                 Log.e(this, new IllegalStateException(), "Unexpected message code %d", msg.what);
1617         }
1618     }
1619 
quitStateMachine()1620     public void quitStateMachine() {
1621         quitNow();
1622     }
1623 
dump(IndentingPrintWriter pw)1624     public void dump(IndentingPrintWriter pw) {
1625         pw.print("Current state: ");
1626         pw.println(getCurrentState().getName());
1627         pw.println("Pending messages:");
1628         pw.increaseIndent();
1629         dumpPendingMessages(pw);
1630         pw.decreaseIndent();
1631     }
1632 
dumpPendingMessages(IndentingPrintWriter pw)1633     public void dumpPendingMessages(IndentingPrintWriter pw) {
1634         getHandler().getLooper().dump(pw::println, "");
1635     }
1636 
isHfpDeviceAvailable()1637     public boolean isHfpDeviceAvailable() {
1638         return mBluetoothRouteManager.isBluetoothAvailable();
1639     }
1640 
setSpeakerphoneOn(boolean on)1641     private void setSpeakerphoneOn(boolean on) {
1642         Log.i(this, "turning speaker phone %s", on);
1643         AudioDeviceInfo speakerDevice = null;
1644         for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
1645             if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
1646                 speakerDevice = info;
1647                 break;
1648             }
1649         }
1650         boolean speakerOn = false;
1651         if (speakerDevice != null && on) {
1652             boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
1653             if (result) {
1654                 speakerOn = true;
1655             }
1656         } else {
1657             AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
1658             if (curDevice != null && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
1659                 mAudioManager.clearCommunicationDevice();
1660             }
1661         }
1662         mStatusBarNotifier.notifySpeakerphone(speakerOn);
1663     }
1664 
setBluetoothOn(String address)1665     private void setBluetoothOn(String address) {
1666         if (mBluetoothRouteManager.isBluetoothAvailable()) {
1667             BluetoothDevice connectedDevice =
1668                     mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
1669             if (address == null && connectedDevice != null) {
1670                 // null means connect to any device, so if we're already connected to some device,
1671                 // that means we can just tell ourselves that it's connected.
1672                 // Do still try to connect audio though, so that BluetoothRouteManager knows that
1673                 // there's an active call.
1674                 Log.i(this, "Bluetooth audio already on.");
1675                 sendInternalMessage(BT_AUDIO_CONNECTED);
1676                 mBluetoothRouteManager.connectBluetoothAudio(connectedDevice.getAddress());
1677                 return;
1678             }
1679             if (connectedDevice == null || !Objects.equals(address, connectedDevice.getAddress())) {
1680                 Log.i(this, "connecting bluetooth audio: %s", address);
1681                 mBluetoothRouteManager.connectBluetoothAudio(address);
1682             }
1683         }
1684     }
1685 
setBluetoothOff()1686     private void setBluetoothOff() {
1687         if (mBluetoothRouteManager.isBluetoothAvailable()) {
1688             if (mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()) {
1689                 Log.i(this, "disconnecting bluetooth audio");
1690                 mBluetoothRouteManager.disconnectBluetoothAudio();
1691             }
1692         }
1693     }
1694 
setMuteOn(boolean mute)1695     private void setMuteOn(boolean mute) {
1696         mIsMuted = mute;
1697         Log.addEvent(mCallsManager.getForegroundCall(), mute ?
1698                 LogUtils.Events.MUTE : LogUtils.Events.UNMUTE);
1699         if (mute != mAudioManager.isMicrophoneMute() && isInActiveState()) {
1700             IAudioService audio = mAudioServiceFactory.getAudioService();
1701             Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]",
1702                     mute, audio == null);
1703             if (audio != null) {
1704                 try {
1705                     // We use the audio service directly here so that we can specify
1706                     // the current user. Telecom runs in the system_server process which
1707                     // may run as a separate user from the foreground user. If we
1708                     // used AudioManager directly, we would change mute for the system's
1709                     // user and not the current foreground, which we want to avoid.
1710                     audio.setMicrophoneMute(mute, mContext.getOpPackageName(),
1711                             getCurrentUserId(), mContext.getAttributionTag());
1712                 } catch (RemoteException e) {
1713                     Log.e(this, e, "Remote exception while toggling mute.");
1714                 }
1715                 // TODO: Check microphone state after attempting to set to ensure that
1716                 // our state corroborates AudioManager's state.
1717             }
1718         }
1719     }
1720 
updateSystemMuteState()1721     private void updateSystemMuteState() {
1722         CallAudioState newCallAudioState = new CallAudioState(mIsMuted,
1723                 mCurrentCallAudioState.getRoute(),
1724                 mAvailableRoutes,
1725                 mCurrentCallAudioState.getActiveBluetoothDevice(),
1726                 mBluetoothRouteManager.getConnectedDevices());
1727         setSystemAudioState(newCallAudioState);
1728         updateInternalCallAudioState();
1729     }
1730 
1731     /**
1732      * Updates the CallAudioState object from current internal state. The result is used for
1733      * external communication only.
1734      */
updateInternalCallAudioState()1735     private void updateInternalCallAudioState() {
1736         IState currentState = getCurrentState();
1737         if (currentState == null) {
1738             Log.e(this, new IllegalStateException(), "Current state should never be null" +
1739                     " when updateInternalCallAudioState is called.");
1740             mCurrentCallAudioState = new CallAudioState(
1741                     mIsMuted, mCurrentCallAudioState.getRoute(), mAvailableRoutes,
1742                     mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
1743                     mBluetoothRouteManager.getConnectedDevices());
1744             return;
1745         }
1746         int currentRoute = mStateNameToRouteCode.get(currentState.getName());
1747         mCurrentCallAudioState = new CallAudioState(mIsMuted, currentRoute, mAvailableRoutes,
1748                 mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
1749                 mBluetoothRouteManager.getConnectedDevices());
1750     }
1751 
setSystemAudioState(CallAudioState newCallAudioState)1752     private void setSystemAudioState(CallAudioState newCallAudioState) {
1753         setSystemAudioState(newCallAudioState, false);
1754     }
1755 
resendSystemAudioState()1756     private void resendSystemAudioState() {
1757         setSystemAudioState(mLastKnownCallAudioState, true);
1758     }
1759 
setSystemAudioState(CallAudioState newCallAudioState, boolean force)1760     private void setSystemAudioState(CallAudioState newCallAudioState, boolean force) {
1761         synchronized (mLock) {
1762             Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
1763                     newCallAudioState);
1764             if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) {
1765                 mStatusBarNotifier.notifyMute(newCallAudioState.isMuted());
1766                 mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
1767                 updateAudioForForegroundCall(newCallAudioState);
1768                 mLastKnownCallAudioState = newCallAudioState;
1769             }
1770         }
1771     }
1772 
updateAudioForForegroundCall(CallAudioState newCallAudioState)1773     private void updateAudioForForegroundCall(CallAudioState newCallAudioState) {
1774         Call call = mCallsManager.getForegroundCall();
1775         if (call != null && call.getConnectionService() != null) {
1776             call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
1777         }
1778     }
1779 
calculateSupportedRoutes()1780     private int calculateSupportedRoutes() {
1781         int routeMask = CallAudioState.ROUTE_SPEAKER;
1782 
1783         if (mWiredHeadsetManager.isPluggedIn()) {
1784             routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
1785         } else if (mDoesDeviceSupportEarpieceRoute){
1786             routeMask |= CallAudioState.ROUTE_EARPIECE;
1787         }
1788 
1789         if (mBluetoothRouteManager.isBluetoothAvailable()) {
1790             routeMask |=  CallAudioState.ROUTE_BLUETOOTH;
1791         }
1792 
1793         return routeMask;
1794     }
1795 
sendInternalMessage(int messageCode)1796     private void sendInternalMessage(int messageCode) {
1797         sendInternalMessage(messageCode, 0);
1798     }
1799 
sendInternalMessage(int messageCode, int arg1)1800     private void sendInternalMessage(int messageCode, int arg1) {
1801         // Internal messages are messages which the state machine sends to itself in the
1802         // course of processing externally-sourced messages. We want to send these messages at
1803         // the front of the queue in order to make actions appear atomic to the user and to
1804         // prevent scenarios such as these:
1805         // 1. State machine handler thread is suspended for some reason.
1806         // 2. Headset gets connected (sends CONNECT_HEADSET).
1807         // 3. User switches to speakerphone in the UI (sends SWITCH_SPEAKER).
1808         // 4. State machine handler is un-suspended.
1809         // 5. State machine handler processes the CONNECT_HEADSET message and sends
1810         //    SWITCH_HEADSET at end of queue.
1811         // 6. State machine handler processes SWITCH_SPEAKER.
1812         // 7. State machine handler processes SWITCH_HEADSET.
1813         Session subsession = Log.createSubsession();
1814         if(subsession != null) {
1815             SomeArgs args = SomeArgs.obtain();
1816             args.arg1 = subsession;
1817             sendMessageAtFrontOfQueue(messageCode, arg1, 0, args);
1818         } else {
1819             sendMessageAtFrontOfQueue(messageCode, arg1);
1820         }
1821     }
1822 
getInitialAudioState()1823     private CallAudioState getInitialAudioState() {
1824         int supportedRouteMask = calculateSupportedRoutes() & getCurrentCallSupportedRoutes();
1825         final int route;
1826 
1827         if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0
1828                 && mBluetoothRouteManager.hasBtActiveDevice()) {
1829             route = ROUTE_BLUETOOTH;
1830         } else if ((supportedRouteMask & ROUTE_WIRED_HEADSET) != 0) {
1831             route = ROUTE_WIRED_HEADSET;
1832         } else if ((supportedRouteMask & ROUTE_EARPIECE) != 0) {
1833             route = ROUTE_EARPIECE;
1834         } else {
1835             route = ROUTE_SPEAKER;
1836         }
1837 
1838         return new CallAudioState(false, route, supportedRouteMask, null,
1839                 mBluetoothRouteManager.getConnectedDevices());
1840     }
1841 
getCurrentUserId()1842     private int getCurrentUserId() {
1843         final long ident = Binder.clearCallingIdentity();
1844         try {
1845             UserInfo currentUser = ActivityManager.getService().getCurrentUser();
1846             return currentUser.id;
1847         } catch (RemoteException e) {
1848             // Activity manager not running, nothing we can do assume user 0.
1849         } finally {
1850             Binder.restoreCallingIdentity(ident);
1851         }
1852         return UserHandle.USER_OWNER;
1853     }
1854 
isInActiveState()1855     public boolean isInActiveState() {
1856         AudioState currentState = (AudioState) getCurrentState();
1857         if (currentState == null) {
1858             Log.w(this, "Current state is null, assuming inactive state");
1859             return false;
1860         }
1861         return currentState.isActive();
1862     }
1863 
checkForEarpieceSupport()1864     private boolean checkForEarpieceSupport() {
1865         AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
1866         for (AudioDeviceInfo device: deviceList) {
1867             if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
1868                 return true;
1869             }
1870         }
1871         // No earpiece found
1872         return false;
1873     }
1874 
calculateBaselineRouteMessage(boolean isExplicitUserRequest, boolean includeBluetooth)1875     private int calculateBaselineRouteMessage(boolean isExplicitUserRequest,
1876             boolean includeBluetooth) {
1877         boolean isSkipEarpiece = false;
1878         if (!isExplicitUserRequest) {
1879             synchronized (mLock) {
1880                 // Check video calls to skip earpiece since the baseline for video
1881                 // calls should be the speakerphone route
1882                 isSkipEarpiece = mCallsManager.hasVideoCall();
1883             }
1884         }
1885         if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0
1886                 && !mHasUserExplicitlyLeftBluetooth
1887                 && includeBluetooth) {
1888             return isExplicitUserRequest ? USER_SWITCH_BLUETOOTH : SWITCH_BLUETOOTH;
1889         } else if ((mAvailableRoutes & ROUTE_EARPIECE) != 0 && !isSkipEarpiece) {
1890             return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE;
1891         } else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
1892             return isExplicitUserRequest ? USER_SWITCH_HEADSET : SWITCH_HEADSET;
1893         } else {
1894             return isExplicitUserRequest ? USER_SWITCH_SPEAKER : SWITCH_SPEAKER;
1895         }
1896     }
1897 
reinitialize()1898     private void reinitialize() {
1899         CallAudioState initState = getInitialAudioState();
1900         mDeviceSupportedRoutes = initState.getSupportedRouteMask();
1901         mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
1902         mIsMuted = initState.isMuted();
1903         setSpeakerphoneOn(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
1904         setMuteOn(mIsMuted);
1905         mWasOnSpeaker = false;
1906         mHasUserExplicitlyLeftBluetooth = false;
1907         mLastKnownCallAudioState = initState;
1908         transitionTo(mRouteCodeToQuiescentState.get(initState.getRoute()));
1909     }
1910 
updateRouteForForegroundCall()1911     private void updateRouteForForegroundCall() {
1912         mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
1913 
1914         CallAudioState currentState = getCurrentCallAudioState();
1915 
1916         // Move to baseline route in the case the current route is no longer available.
1917         if ((mAvailableRoutes & currentState.getRoute()) == 0) {
1918             sendInternalMessage(calculateBaselineRouteMessage(false, true));
1919         }
1920     }
1921 
getCurrentCallSupportedRoutes()1922     private int getCurrentCallSupportedRoutes() {
1923         int supportedRoutes = CallAudioState.ROUTE_ALL;
1924 
1925         if (mCallsManager.getForegroundCall() != null) {
1926             supportedRoutes &= mCallsManager.getForegroundCall().getSupportedAudioRoutes();
1927         }
1928 
1929         return supportedRoutes;
1930     }
1931 
modifyRoutes(int base, int remove, int add, boolean considerCurrentCall)1932     private int modifyRoutes(int base, int remove, int add, boolean considerCurrentCall) {
1933         base &= ~remove;
1934 
1935         if (considerCurrentCall) {
1936             add &= getCurrentCallSupportedRoutes();
1937         }
1938 
1939         base |= add;
1940 
1941         return base;
1942     }
1943 }
1944