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