• 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 import android.media.AudioManager;
20 import android.os.Looper;
21 import android.os.Message;
22 import android.telecom.Log;
23 import android.telecom.Logging.Runnable;
24 import android.telecom.Logging.Session;
25 import android.util.LocalLog;
26 import android.util.SparseArray;
27 
28 import com.android.internal.util.IState;
29 import com.android.internal.util.IndentingPrintWriter;
30 import com.android.internal.util.State;
31 import com.android.internal.util.StateMachine;
32 
33 public class CallAudioModeStateMachine extends StateMachine {
34     /**
35      * Captures the most recent CallAudioModeStateMachine state transitions and the corresponding
36      * changes to the {@link AudioManager#setMode}.
37      */
38     private LocalLog mLocalLog = new LocalLog(20);
39     public static class Factory {
create(SystemStateHelper systemStateHelper, AudioManager am)40         public CallAudioModeStateMachine create(SystemStateHelper systemStateHelper,
41                 AudioManager am) {
42             return new CallAudioModeStateMachine(systemStateHelper, am);
43         }
44     }
45 
46     public static class MessageArgs {
47         public boolean hasActiveOrDialingCalls;
48         public boolean hasRingingCalls;
49         public boolean hasHoldingCalls;
50         public boolean hasAudioProcessingCalls;
51         public boolean isTonePlaying;
52         public boolean foregroundCallIsVoip;
53         public Session session;
54 
MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls, boolean hasHoldingCalls, boolean hasAudioProcessingCalls, boolean isTonePlaying, boolean foregroundCallIsVoip, Session session)55         private MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls,
56                 boolean hasHoldingCalls, boolean hasAudioProcessingCalls, boolean isTonePlaying,
57                 boolean foregroundCallIsVoip, Session session) {
58             this.hasActiveOrDialingCalls = hasActiveOrDialingCalls;
59             this.hasRingingCalls = hasRingingCalls;
60             this.hasHoldingCalls = hasHoldingCalls;
61             this.hasAudioProcessingCalls = hasAudioProcessingCalls;
62             this.isTonePlaying = isTonePlaying;
63             this.foregroundCallIsVoip = foregroundCallIsVoip;
64             this.session = session;
65         }
66 
67         @Override
toString()68         public String toString() {
69             return "MessageArgs{" +
70                     "hasActiveCalls=" + hasActiveOrDialingCalls +
71                     ", hasRingingCalls=" + hasRingingCalls +
72                     ", hasHoldingCalls=" + hasHoldingCalls +
73                     ", hasAudioProcessingCalls=" + hasAudioProcessingCalls +
74                     ", isTonePlaying=" + isTonePlaying +
75                     ", foregroundCallIsVoip=" + foregroundCallIsVoip +
76                     ", session=" + session +
77                     '}';
78         }
79 
80         public static class Builder {
81             private boolean mHasActiveOrDialingCalls;
82             private boolean mHasRingingCalls;
83             private boolean mHasHoldingCalls;
84             private boolean mHasAudioProcessingCalls;
85             private boolean mIsTonePlaying;
86             private boolean mForegroundCallIsVoip;
87             private Session mSession;
88 
setHasActiveOrDialingCalls(boolean hasActiveOrDialingCalls)89             public Builder setHasActiveOrDialingCalls(boolean hasActiveOrDialingCalls) {
90                 mHasActiveOrDialingCalls = hasActiveOrDialingCalls;
91                 return this;
92             }
93 
setHasRingingCalls(boolean hasRingingCalls)94             public Builder setHasRingingCalls(boolean hasRingingCalls) {
95                 mHasRingingCalls = hasRingingCalls;
96                 return this;
97             }
98 
setHasHoldingCalls(boolean hasHoldingCalls)99             public Builder setHasHoldingCalls(boolean hasHoldingCalls) {
100                 mHasHoldingCalls = hasHoldingCalls;
101                 return this;
102             }
103 
setHasAudioProcessingCalls(boolean hasAudioProcessingCalls)104             public Builder setHasAudioProcessingCalls(boolean hasAudioProcessingCalls) {
105                 mHasAudioProcessingCalls = hasAudioProcessingCalls;
106                 return this;
107             }
108 
setIsTonePlaying(boolean isTonePlaying)109             public Builder setIsTonePlaying(boolean isTonePlaying) {
110                 mIsTonePlaying = isTonePlaying;
111                 return this;
112             }
113 
setForegroundCallIsVoip(boolean foregroundCallIsVoip)114             public Builder setForegroundCallIsVoip(boolean foregroundCallIsVoip) {
115                 mForegroundCallIsVoip = foregroundCallIsVoip;
116                 return this;
117             }
118 
setSession(Session session)119             public Builder setSession(Session session) {
120                 mSession = session;
121                 return this;
122             }
123 
build()124             public MessageArgs build() {
125                 return new MessageArgs(mHasActiveOrDialingCalls, mHasRingingCalls, mHasHoldingCalls,
126                         mHasAudioProcessingCalls, mIsTonePlaying, mForegroundCallIsVoip, mSession);
127             }
128         }
129     }
130 
131     // TODO: remove this and replace when the new audio mode gets pushed to AOSP.
132     public static final int NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING = 4;
133 
134     public static final int INITIALIZE = 1;
135     // These ENTER_*_FOCUS commands are for testing.
136     public static final int ENTER_CALL_FOCUS_FOR_TESTING = 2;
137     public static final int ENTER_COMMS_FOCUS_FOR_TESTING = 3;
138     public static final int ENTER_RING_FOCUS_FOR_TESTING = 4;
139     public static final int ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING = 5;
140     public static final int ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING = 6;
141     public static final int ABANDON_FOCUS_FOR_TESTING = 7;
142 
143     public static final int NO_MORE_ACTIVE_OR_DIALING_CALLS = 1001;
144     public static final int NO_MORE_RINGING_CALLS = 1002;
145     public static final int NO_MORE_HOLDING_CALLS = 1003;
146     public static final int NO_MORE_AUDIO_PROCESSING_CALLS = 1004;
147 
148     public static final int NEW_ACTIVE_OR_DIALING_CALL = 2001;
149     public static final int NEW_RINGING_CALL = 2002;
150     public static final int NEW_HOLDING_CALL = 2003;
151     public static final int NEW_AUDIO_PROCESSING_CALL = 2004;
152 
153     public static final int TONE_STARTED_PLAYING = 3001;
154     public static final int TONE_STOPPED_PLAYING = 3002;
155 
156     public static final int FOREGROUND_VOIP_MODE_CHANGE = 4001;
157 
158     public static final int RINGER_MODE_CHANGE = 5001;
159 
160     // Used to indicate that Telecom is done doing things to the AudioManager and that it's safe
161     // to release focus for other apps to take over.
162     public static final int AUDIO_OPERATIONS_COMPLETE = 6001;
163 
164     public static final int RUN_RUNNABLE = 9001;
165 
166     private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
167         put(ENTER_CALL_FOCUS_FOR_TESTING, "ENTER_CALL_FOCUS_FOR_TESTING");
168         put(ENTER_COMMS_FOCUS_FOR_TESTING, "ENTER_COMMS_FOCUS_FOR_TESTING");
169         put(ENTER_RING_FOCUS_FOR_TESTING, "ENTER_RING_FOCUS_FOR_TESTING");
170         put(ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING, "ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING");
171         put(ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, "ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING");
172         put(ABANDON_FOCUS_FOR_TESTING, "ABANDON_FOCUS_FOR_TESTING");
173         put(NO_MORE_ACTIVE_OR_DIALING_CALLS, "NO_MORE_ACTIVE_OR_DIALING_CALLS");
174         put(NO_MORE_RINGING_CALLS, "NO_MORE_RINGING_CALLS");
175         put(NO_MORE_HOLDING_CALLS, "NO_MORE_HOLDING_CALLS");
176         put(NO_MORE_AUDIO_PROCESSING_CALLS, "NO_MORE_AUDIO_PROCESSING_CALLS");
177         put(NEW_ACTIVE_OR_DIALING_CALL, "NEW_ACTIVE_OR_DIALING_CALL");
178         put(NEW_RINGING_CALL, "NEW_RINGING_CALL");
179         put(NEW_HOLDING_CALL, "NEW_HOLDING_CALL");
180         put(NEW_AUDIO_PROCESSING_CALL, "NEW_AUDIO_PROCESSING_CALL");
181         put(TONE_STARTED_PLAYING, "TONE_STARTED_PLAYING");
182         put(TONE_STOPPED_PLAYING, "TONE_STOPPED_PLAYING");
183         put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
184         put(RINGER_MODE_CHANGE, "RINGER_MODE_CHANGE");
185         put(AUDIO_OPERATIONS_COMPLETE, "AUDIO_OPERATIONS_COMPLETE");
186 
187         put(RUN_RUNNABLE, "RUN_RUNNABLE");
188     }};
189 
190     public static final String TONE_HOLD_STATE_NAME = OtherFocusState.class.getSimpleName();
191     public static final String UNFOCUSED_STATE_NAME = UnfocusedState.class.getSimpleName();
192     public static final String AUDIO_PROCESSING_STATE_NAME =
193             AudioProcessingFocusState.class.getSimpleName();
194     public static final String CALL_STATE_NAME = SimCallFocusState.class.getSimpleName();
195     public static final String RING_STATE_NAME = RingingFocusState.class.getSimpleName();
196     public static final String COMMS_STATE_NAME = VoipCallFocusState.class.getSimpleName();
197 
198     private class BaseState extends State {
199         @Override
processMessage(Message msg)200         public boolean processMessage(Message msg) {
201             switch (msg.what) {
202                 case ENTER_CALL_FOCUS_FOR_TESTING:
203                     transitionTo(mSimCallFocusState);
204                     return HANDLED;
205                 case ENTER_COMMS_FOCUS_FOR_TESTING:
206                     transitionTo(mVoipCallFocusState);
207                     return HANDLED;
208                 case ENTER_RING_FOCUS_FOR_TESTING:
209                     transitionTo(mRingingFocusState);
210                     return HANDLED;
211                 case ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING:
212                     transitionTo(mOtherFocusState);
213                     return HANDLED;
214                 case ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING:
215                     transitionTo(mAudioProcessingFocusState);
216                     return HANDLED;
217                 case ABANDON_FOCUS_FOR_TESTING:
218                     transitionTo(mUnfocusedState);
219                     return HANDLED;
220                 case INITIALIZE:
221                     mIsInitialized = true;
222                     return HANDLED;
223                 case RUN_RUNNABLE:
224                     java.lang.Runnable r = (java.lang.Runnable) msg.obj;
225                     r.run();
226                     return HANDLED;
227                 default:
228                     return NOT_HANDLED;
229             }
230         }
231     }
232 
233     private class UnfocusedState extends BaseState {
234         @Override
enter()235         public void enter() {
236             Log.i(LOG_TAG, "Audio focus entering UNFOCUSED state");
237             mLocalLog.log("Enter UNFOCUSED");
238             if (mIsInitialized) {
239                 mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
240                 mAudioManager.setMode(AudioManager.MODE_NORMAL);
241                 mLocalLog.log("Mode MODE_NORMAL");
242                 mMostRecentMode = AudioManager.MODE_NORMAL;
243                 // Don't release focus here -- wait until we get a signal that any other audio
244                 // operations triggered by this are done before releasing focus.
245             }
246         }
247 
248         @Override
processMessage(Message msg)249         public boolean processMessage(Message msg) {
250             if (super.processMessage(msg) == HANDLED) {
251                 return HANDLED;
252             }
253             MessageArgs args = (MessageArgs) msg.obj;
254             switch (msg.what) {
255                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
256                     // Do nothing.
257                     return HANDLED;
258                 case NO_MORE_RINGING_CALLS:
259                     // Do nothing.
260                     return HANDLED;
261                 case NO_MORE_HOLDING_CALLS:
262                     // Do nothing.
263                     return HANDLED;
264                 case NO_MORE_AUDIO_PROCESSING_CALLS:
265                     // Do nothing.
266                     return HANDLED;
267                 case NEW_ACTIVE_OR_DIALING_CALL:
268                     transitionTo(args.foregroundCallIsVoip
269                             ? mVoipCallFocusState : mSimCallFocusState);
270                     return HANDLED;
271                 case NEW_RINGING_CALL:
272                     transitionTo(mRingingFocusState);
273                     return HANDLED;
274                 case NEW_AUDIO_PROCESSING_CALL:
275                     transitionTo(mAudioProcessingFocusState);
276                     return HANDLED;
277                 case NEW_HOLDING_CALL:
278                     // This really shouldn't happen, but transition to the focused state anyway.
279                     Log.w(LOG_TAG, "Call was surprisingly put into hold from an unknown state." +
280                             " Args are: \n" + args.toString());
281                     transitionTo(mOtherFocusState);
282                     return HANDLED;
283                 case TONE_STARTED_PLAYING:
284                     // This shouldn't happen either, but perform the action anyway.
285                     Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
286                             + args.toString());
287                     return HANDLED;
288                 case AUDIO_OPERATIONS_COMPLETE:
289                     Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");
290                     mAudioManager.abandonAudioFocusForCall();
291                     return HANDLED;
292                 default:
293                     // The forced focus switch commands are handled by BaseState.
294                     return NOT_HANDLED;
295             }
296         }
297     }
298 
299     private class AudioProcessingFocusState extends BaseState {
300         @Override
enter()301         public void enter() {
302             Log.i(LOG_TAG, "Audio focus entering AUDIO_PROCESSING state");
303             mLocalLog.log("Enter AUDIO_PROCESSING");
304             if (mIsInitialized) {
305                 mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
306                 mAudioManager.setMode(NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING);
307                 mLocalLog.log("Mode MODE_CALL_SCREENING");
308                 mMostRecentMode = NEW_AUDIO_MODE_FOR_AUDIO_PROCESSING;
309             }
310         }
311 
312         @Override
processMessage(Message msg)313         public boolean processMessage(Message msg) {
314             if (super.processMessage(msg) == HANDLED) {
315                 return HANDLED;
316             }
317             MessageArgs args = (MessageArgs) msg.obj;
318             switch (msg.what) {
319                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
320                     // Do nothing.
321                     return HANDLED;
322                 case NO_MORE_RINGING_CALLS:
323                     // Do nothing.
324                     return HANDLED;
325                 case NO_MORE_HOLDING_CALLS:
326                     // Do nothing.
327                     return HANDLED;
328                 case NO_MORE_AUDIO_PROCESSING_CALLS:
329                     BaseState destState = calculateProperStateFromArgs(args);
330                     if (destState == this) {
331                         Log.w(LOG_TAG, "Got spurious NO_MORE_AUDIO_PROCESSING_CALLS");
332                     }
333                     transitionTo(destState);
334                     return HANDLED;
335                 case NEW_ACTIVE_OR_DIALING_CALL:
336                     transitionTo(args.foregroundCallIsVoip
337                             ? mVoipCallFocusState : mSimCallFocusState);
338                     return HANDLED;
339                 case NEW_RINGING_CALL:
340                     transitionTo(mRingingFocusState);
341                     return HANDLED;
342                 case NEW_HOLDING_CALL:
343                     // This really shouldn't happen, but recalculate from args and do the transition
344                     Log.w(LOG_TAG, "Call was surprisingly put into hold from an unknown state." +
345                             " Args are: \n" + args.toString());
346                     transitionTo(mOtherFocusState);
347                     return HANDLED;
348                 case NEW_AUDIO_PROCESSING_CALL:
349                     // Can happen as a duplicate message
350                     return HANDLED;
351                 case TONE_STARTED_PLAYING:
352                     // This shouldn't happen either, but perform the action anyway.
353                     Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
354                             + args.toString());
355                     return HANDLED;
356                 case AUDIO_OPERATIONS_COMPLETE:
357                     Log.i(LOG_TAG, "Abandoning audio focus: now AUDIO_PROCESSING");
358                     mAudioManager.abandonAudioFocusForCall();
359                     return HANDLED;
360                 default:
361                     // The forced focus switch commands are handled by BaseState.
362                     return NOT_HANDLED;
363             }
364         }
365     }
366 
367     private class RingingFocusState extends BaseState {
368         // Keeps track of whether we're ringing with audio focus or if we've just entered the state
369         // without acquiring focus because of a silent ringtone or something.
370         private boolean mHasFocus = false;
371 
tryStartRinging()372         private void tryStartRinging() {
373             if (mHasFocus && mCallAudioManager.isRingtonePlaying()) {
374                 Log.i(LOG_TAG, "RingingFocusState#tryStartRinging -- audio focus previously"
375                         + " acquired and ringtone already playing -- skipping.");
376                 return;
377             }
378 
379             if (mCallAudioManager.startRinging()) {
380                 mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
381                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
382                 // Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode -- this
383                 // trips up the audio system.
384                 if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
385                     mAudioManager.setMode(AudioManager.MODE_RINGTONE);
386                     mLocalLog.log("Mode MODE_RINGTONE");
387                 }
388                 mCallAudioManager.setCallAudioRouteFocusState(
389                         CallAudioRouteStateMachine.RINGING_FOCUS);
390                 mHasFocus = true;
391             } else {
392                 Log.i(LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
393             }
394         }
395 
396         @Override
enter()397         public void enter() {
398             Log.i(LOG_TAG, "Audio focus entering RINGING state");
399             mLocalLog.log("Enter RINGING");
400             tryStartRinging();
401             mCallAudioManager.stopCallWaiting();
402         }
403 
404         @Override
exit()405         public void exit() {
406             // Audio mode and audio stream will be set by the next state.
407             mCallAudioManager.stopRinging();
408             mHasFocus = false;
409         }
410 
411         @Override
processMessage(Message msg)412         public boolean processMessage(Message msg) {
413             if (super.processMessage(msg) == HANDLED) {
414                 return HANDLED;
415             }
416             MessageArgs args = (MessageArgs) msg.obj;
417             switch (msg.what) {
418                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
419                     // Do nothing. Loss of an active call should not impact ringer.
420                     return HANDLED;
421                 case NO_MORE_HOLDING_CALLS:
422                     // Do nothing and keep ringing.
423                     return HANDLED;
424                 case NO_MORE_RINGING_CALLS:
425                     BaseState destState = calculateProperStateFromArgs(args);
426                     if (destState == this) {
427                         Log.w(LOG_TAG, "Got spurious NO_MORE_RINGING_CALLS");
428                     }
429                     transitionTo(destState);
430                     return HANDLED;
431                 case NEW_ACTIVE_OR_DIALING_CALL:
432                     // If a call becomes active suddenly, give it priority over ringing.
433                     transitionTo(args.foregroundCallIsVoip
434                             ? mVoipCallFocusState : mSimCallFocusState);
435                     return HANDLED;
436                 case NEW_AUDIO_PROCESSING_CALL:
437                     // If we don't have any more ringing calls, transition to audio processing.
438                     if (!args.hasRingingCalls) {
439                         transitionTo(mAudioProcessingFocusState);
440                     } else {
441                         Log.w(LOG_TAG, "Got a audio processing call while there's still a call "
442                                 + "ringing");
443                     }
444                 case NEW_RINGING_CALL:
445                     // Can happen as a duplicate message
446                     return HANDLED;
447                 case NEW_HOLDING_CALL:
448                     // This really shouldn't happen, but transition to the focused state anyway.
449                     Log.w(LOG_TAG, "Call was surprisingly put into hold while ringing." +
450                             " Args are: " + args.toString());
451                     transitionTo(mOtherFocusState);
452                     return HANDLED;
453                 case RINGER_MODE_CHANGE: {
454                     Log.i(LOG_TAG, "RINGING state, received RINGER_MODE_CHANGE");
455                     tryStartRinging();
456                     return HANDLED;
457                 }
458                 case AUDIO_OPERATIONS_COMPLETE:
459                     Log.w(LOG_TAG, "Should not be seeing AUDIO_OPERATIONS_COMPLETE in a focused"
460                             + " state");
461                     return HANDLED;
462                 default:
463                     // The forced focus switch commands are handled by BaseState.
464                     return NOT_HANDLED;
465             }
466         }
467     }
468 
469     private class SimCallFocusState extends BaseState {
470         @Override
enter()471         public void enter() {
472             Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
473             mLocalLog.log("Enter SIM_CALL");
474             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
475                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
476             mAudioManager.setMode(AudioManager.MODE_IN_CALL);
477             mLocalLog.log("Mode MODE_IN_CALL");
478             mMostRecentMode = AudioManager.MODE_IN_CALL;
479             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
480         }
481 
482         @Override
processMessage(Message msg)483         public boolean processMessage(Message msg) {
484             if (super.processMessage(msg) == HANDLED) {
485                 return HANDLED;
486             }
487             MessageArgs args = (MessageArgs) msg.obj;
488             switch (msg.what) {
489                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
490                     // Switch to either ringing, holding, or inactive
491                     transitionTo(calculateProperStateFromArgs(args));
492                     return HANDLED;
493                 case NO_MORE_RINGING_CALLS:
494                     // Don't transition state, but stop any call-waiting tones that may have been
495                     // playing.
496                     if (args.isTonePlaying) {
497                         mCallAudioManager.stopCallWaiting();
498                     }
499                     // If a MT-audio-speedup call gets disconnected by the connection service
500                     // concurrently with the user answering it, we may get this message
501                     // indicating that a ringing call has disconnected while this state machine
502                     // is in the SimCallFocusState.
503                     if (!args.hasActiveOrDialingCalls) {
504                         transitionTo(calculateProperStateFromArgs(args));
505                     }
506                     return HANDLED;
507                 case NO_MORE_HOLDING_CALLS:
508                     if (args.foregroundCallIsVoip) {
509                         transitionTo(mVoipCallFocusState);
510                     }
511                     return HANDLED;
512                 case NEW_ACTIVE_OR_DIALING_CALL:
513                     if (args.foregroundCallIsVoip) {
514                         transitionTo(mVoipCallFocusState);
515                     }
516                     return HANDLED;
517                 case NEW_RINGING_CALL:
518                     // Don't make a call ring over an active call, but do play a call waiting tone.
519                     mCallAudioManager.startCallWaiting("call already active");
520                     return HANDLED;
521                 case NEW_HOLDING_CALL:
522                     // Just check the voip mode. Putting an active call on hold will be handled when
523                     // NO_MORE_ACTIVE_CALLS is processed.
524                     if (args.foregroundCallIsVoip) {
525                         transitionTo(mVoipCallFocusState);
526                     }
527                     return HANDLED;
528                 case NEW_AUDIO_PROCESSING_CALL:
529                     // If we don't have any more active calls, transition to audio processing.
530                     if (!args.hasActiveOrDialingCalls) {
531                         transitionTo(mAudioProcessingFocusState);
532                     } else {
533                         Log.w(LOG_TAG, "Got a audio processing call while there's still a call "
534                                 + "active");
535                     }
536                 case FOREGROUND_VOIP_MODE_CHANGE:
537                     if (args.foregroundCallIsVoip) {
538                         transitionTo(mVoipCallFocusState);
539                     }
540                     return HANDLED;
541                 case AUDIO_OPERATIONS_COMPLETE:
542                     Log.w(LOG_TAG, "Should not be seeing AUDIO_OPERATIONS_COMPLETE in a focused"
543                             + " state");
544                     return HANDLED;
545                 default:
546                     // The forced focus switch commands are handled by BaseState.
547                     return NOT_HANDLED;
548             }
549         }
550     }
551 
552     private class VoipCallFocusState extends BaseState {
553         @Override
enter()554         public void enter() {
555             Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
556             mLocalLog.log("Enter VOIP_CALL");
557             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
558                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
559             mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
560             mLocalLog.log("Mode MODE_IN_COMMUNICATION");
561             mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
562             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
563         }
564 
565         @Override
processMessage(Message msg)566         public boolean processMessage(Message msg) {
567             if (super.processMessage(msg) == HANDLED) {
568                 return HANDLED;
569             }
570             MessageArgs args = (MessageArgs) msg.obj;
571             switch (msg.what) {
572                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
573                     // Switch to either ringing, holding, or inactive
574                     transitionTo(calculateProperStateFromArgs(args));
575                     return HANDLED;
576                 case NO_MORE_RINGING_CALLS:
577                     // Don't transition state, but stop any call-waiting tones that may have been
578                     // playing.
579                     if (args.isTonePlaying) {
580                         mCallAudioManager.stopCallWaiting();
581                     }
582                     return HANDLED;
583                 case NO_MORE_HOLDING_CALLS:
584                     if (!args.foregroundCallIsVoip) {
585                         transitionTo(mSimCallFocusState);
586                     }
587                     return HANDLED;
588                 case NEW_ACTIVE_OR_DIALING_CALL:
589                     if (!args.foregroundCallIsVoip) {
590                         transitionTo(mSimCallFocusState);
591                     }
592                     return HANDLED;
593                 case NEW_RINGING_CALL:
594                     // Don't make a call ring over an active call, but do play a call waiting tone.
595                     mCallAudioManager.startCallWaiting("call already active");
596                     return HANDLED;
597                 case NEW_HOLDING_CALL:
598                     // Just check the voip mode. Putting an active call on hold will be handled when
599                     // NO_MORE_ACTIVE_CALLS is processed.
600                     if (!args.foregroundCallIsVoip) {
601                         transitionTo(mSimCallFocusState);
602                     }
603                     return HANDLED;
604                 case NEW_AUDIO_PROCESSING_CALL:
605                     // If we don't have any more active calls, transition to audio processing.
606                     if (!args.hasActiveOrDialingCalls) {
607                         transitionTo(mAudioProcessingFocusState);
608                     } else {
609                         Log.w(LOG_TAG, "Got a audio processing call while there's still a call "
610                                 + "active");
611                     }
612                 case FOREGROUND_VOIP_MODE_CHANGE:
613                     if (!args.foregroundCallIsVoip) {
614                         transitionTo(mSimCallFocusState);
615                     }
616                     return HANDLED;
617                 case AUDIO_OPERATIONS_COMPLETE:
618                     Log.w(LOG_TAG, "Should not be seeing AUDIO_OPERATIONS_COMPLETE in a focused"
619                             + " state");
620                     return HANDLED;
621                 default:
622                     // The forced focus switch commands are handled by BaseState.
623                     return NOT_HANDLED;
624             }
625         }
626     }
627 
628     /**
629      * This class is used for calls on hold and end-of-call tones.
630      */
631     private class OtherFocusState extends BaseState {
632         @Override
enter()633         public void enter() {
634             Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
635             mLocalLog.log("Enter TONE/HOLDING");
636             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
637                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
638             mAudioManager.setMode(mMostRecentMode);
639             mLocalLog.log("Mode " + mMostRecentMode);
640             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
641         }
642 
643         @Override
processMessage(Message msg)644         public boolean processMessage(Message msg) {
645             if (super.processMessage(msg) == HANDLED) {
646                 return HANDLED;
647             }
648             MessageArgs args = (MessageArgs) msg.obj;
649             switch (msg.what) {
650                 case NO_MORE_HOLDING_CALLS:
651                     if (args.hasActiveOrDialingCalls) {
652                         transitionTo(args.foregroundCallIsVoip
653                                 ? mVoipCallFocusState : mSimCallFocusState);
654                     } else if (args.hasRingingCalls) {
655                         transitionTo(mRingingFocusState);
656                     } else if (!args.isTonePlaying) {
657                         transitionTo(mUnfocusedState);
658                     }
659                     // Do nothing if a tone is playing.
660                     return HANDLED;
661                 case NEW_ACTIVE_OR_DIALING_CALL:
662                     transitionTo(args.foregroundCallIsVoip
663                             ? mVoipCallFocusState : mSimCallFocusState);
664                     return HANDLED;
665                 case NEW_RINGING_CALL:
666                     // TODO: consider whether to move this into MessageArgs if more things start
667                     // to use it.
668                     if (args.hasHoldingCalls && mSystemStateHelper.isDeviceAtEar()) {
669                         mCallAudioManager.startCallWaiting(
670                                 "Device is at ear with held call");
671                     } else {
672                         transitionTo(mRingingFocusState);
673                     }
674                     return HANDLED;
675                 case NEW_HOLDING_CALL:
676                     // Do nothing.
677                     return HANDLED;
678                 case NO_MORE_RINGING_CALLS:
679                     // If there are no more ringing calls in this state, then stop any call-waiting
680                     // tones that may be playing.
681                     mCallAudioManager.stopCallWaiting();
682                     return HANDLED;
683                 case TONE_STOPPED_PLAYING:
684                     transitionTo(calculateProperStateFromArgs(args));
685                     return HANDLED;
686                 case AUDIO_OPERATIONS_COMPLETE:
687                     Log.w(LOG_TAG, "Should not be seeing AUDIO_OPERATIONS_COMPLETE in a focused"
688                             + " state");
689                     return HANDLED;
690                 default:
691                     return NOT_HANDLED;
692             }
693         }
694     }
695 
696     private static final String LOG_TAG = CallAudioModeStateMachine.class.getSimpleName();
697 
698     private final BaseState mUnfocusedState = new UnfocusedState();
699     private final BaseState mRingingFocusState = new RingingFocusState();
700     private final BaseState mSimCallFocusState = new SimCallFocusState();
701     private final BaseState mVoipCallFocusState = new VoipCallFocusState();
702     private final BaseState mAudioProcessingFocusState = new AudioProcessingFocusState();
703     private final BaseState mOtherFocusState = new OtherFocusState();
704 
705     private final AudioManager mAudioManager;
706     private final SystemStateHelper mSystemStateHelper;
707     private CallAudioManager mCallAudioManager;
708 
709     private int mMostRecentMode;
710     private boolean mIsInitialized = false;
711 
CallAudioModeStateMachine(SystemStateHelper systemStateHelper, AudioManager audioManager)712     public CallAudioModeStateMachine(SystemStateHelper systemStateHelper,
713             AudioManager audioManager) {
714         super(CallAudioModeStateMachine.class.getSimpleName());
715         mAudioManager = audioManager;
716         mSystemStateHelper = systemStateHelper;
717         mMostRecentMode = AudioManager.MODE_NORMAL;
718 
719         createStates();
720     }
721 
722     /**
723      * Used for testing
724      */
CallAudioModeStateMachine(SystemStateHelper systemStateHelper, AudioManager audioManager, Looper looper)725     public CallAudioModeStateMachine(SystemStateHelper systemStateHelper,
726             AudioManager audioManager, Looper looper) {
727         super(CallAudioModeStateMachine.class.getSimpleName(), looper);
728         mAudioManager = audioManager;
729         mSystemStateHelper = systemStateHelper;
730         mMostRecentMode = AudioManager.MODE_NORMAL;
731 
732         createStates();
733     }
734 
createStates()735     private void createStates() {
736         addState(mUnfocusedState);
737         addState(mRingingFocusState);
738         addState(mSimCallFocusState);
739         addState(mVoipCallFocusState);
740         addState(mAudioProcessingFocusState);
741         addState(mOtherFocusState);
742         setInitialState(mUnfocusedState);
743         start();
744         sendMessage(INITIALIZE, new MessageArgs.Builder()
745                 .setHasActiveOrDialingCalls(false)
746                 .setHasRingingCalls(false)
747                 .setHasHoldingCalls(false)
748                 .setIsTonePlaying(false)
749                 .setForegroundCallIsVoip(false)
750                 .setSession(Log.createSubsession())
751                 .build());
752     }
753 
setCallAudioManager(CallAudioManager callAudioManager)754     public void setCallAudioManager(CallAudioManager callAudioManager) {
755         mCallAudioManager = callAudioManager;
756     }
757 
getCurrentStateName()758     public String getCurrentStateName() {
759         IState currentState = getCurrentState();
760         return currentState == null ? "no state" : currentState.getName();
761     }
762 
sendMessageWithArgs(int messageCode, MessageArgs args)763     public void sendMessageWithArgs(int messageCode, MessageArgs args) {
764         sendMessage(messageCode, args);
765     }
766 
767     @Override
onPreHandleMessage(Message msg)768     protected void onPreHandleMessage(Message msg) {
769         if (msg.obj != null && msg.obj instanceof MessageArgs) {
770             Log.continueSession(((MessageArgs) msg.obj).session, "CAMSM.pM_" + msg.what);
771             Log.i(LOG_TAG, "Message received: %s.", MESSAGE_CODE_TO_NAME.get(msg.what));
772         } else if (msg.what == RUN_RUNNABLE && msg.obj instanceof Runnable) {
773             Log.i(LOG_TAG, "Running runnable for testing");
774         } else {
775                 Log.w(LOG_TAG, "Message sent must be of type nonnull MessageArgs, but got " +
776                         (msg.obj == null ? "null" : msg.obj.getClass().getSimpleName()));
777                 Log.w(LOG_TAG, "The message was of code %d = %s",
778                         msg.what, MESSAGE_CODE_TO_NAME.get(msg.what));
779         }
780     }
781 
dumpPendingMessages(IndentingPrintWriter pw)782     public void dumpPendingMessages(IndentingPrintWriter pw) {
783         getHandler().getLooper().dump(pw::println, "");
784     }
785 
dump(IndentingPrintWriter pw)786     public void dump(IndentingPrintWriter pw) {
787         pw.println("History:");
788         mLocalLog.dump(pw);
789         pw.println("Pending Msg:");
790         dumpPendingMessages(pw);
791     }
792 
793     @Override
onPostHandleMessage(Message msg)794     protected void onPostHandleMessage(Message msg) {
795         Log.endSession();
796     }
797 
calculateProperStateFromArgs(MessageArgs args)798     private BaseState calculateProperStateFromArgs(MessageArgs args) {
799         // If there are active, audio-processing, holding, or ringing calls,
800         // switch to the appropriate focus.
801         // Otherwise abandon focus.
802 
803         // The order matters here. If there are active calls, holding focus for them takes priority.
804         // After that, we want to prioritize holding calls over ringing calls so that when a
805         // call-waiting call gets answered, there's no transition in and out of the ringing focus
806         // state. After that, we want tones since we actually hold focus during them, then the
807         // audio processing state because that will release focus.
808         if (args.hasActiveOrDialingCalls) {
809             if (args.foregroundCallIsVoip) {
810                 return mVoipCallFocusState;
811             } else {
812                 return mSimCallFocusState;
813             }
814         } else if (args.hasHoldingCalls) {
815             return mOtherFocusState;
816         } else if (args.hasRingingCalls) {
817             return mRingingFocusState;
818         } else if (args.isTonePlaying) {
819             return mOtherFocusState;
820         } else if (args.hasAudioProcessingCalls) {
821             return mAudioProcessingFocusState;
822         }
823         return mUnfocusedState;
824     }
825 
826 }
827