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