• 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.Message;
21 import android.telecom.Log;
22 import android.telecom.Logging.Runnable;
23 import android.telecom.Logging.Session;
24 import android.util.SparseArray;
25 
26 import com.android.internal.util.IState;
27 import com.android.internal.util.IndentingPrintWriter;
28 import com.android.internal.util.State;
29 import com.android.internal.util.StateMachine;
30 
31 public class CallAudioModeStateMachine extends StateMachine {
32     public static class MessageArgs {
33         public boolean hasActiveOrDialingCalls;
34         public boolean hasRingingCalls;
35         public boolean hasHoldingCalls;
36         public boolean isTonePlaying;
37         public boolean foregroundCallIsVoip;
38         public Session session;
39 
MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls, boolean hasHoldingCalls, boolean isTonePlaying, boolean foregroundCallIsVoip, Session session)40         public MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls,
41                 boolean hasHoldingCalls, boolean isTonePlaying, boolean foregroundCallIsVoip,
42                 Session session) {
43             this.hasActiveOrDialingCalls = hasActiveOrDialingCalls;
44             this.hasRingingCalls = hasRingingCalls;
45             this.hasHoldingCalls = hasHoldingCalls;
46             this.isTonePlaying = isTonePlaying;
47             this.foregroundCallIsVoip = foregroundCallIsVoip;
48             this.session = session;
49         }
50 
MessageArgs()51         public MessageArgs() {
52             this.session = Log.createSubsession();
53         }
54 
55         @Override
toString()56         public String toString() {
57             return "MessageArgs{" +
58                     "hasActiveCalls=" + hasActiveOrDialingCalls +
59                     ", hasRingingCalls=" + hasRingingCalls +
60                     ", hasHoldingCalls=" + hasHoldingCalls +
61                     ", isTonePlaying=" + isTonePlaying +
62                     ", foregroundCallIsVoip=" + foregroundCallIsVoip +
63                     ", session=" + session +
64                     '}';
65         }
66     }
67 
68     public static final int INITIALIZE = 1;
69     // These ENTER_*_FOCUS commands are for testing.
70     public static final int ENTER_CALL_FOCUS_FOR_TESTING = 2;
71     public static final int ENTER_COMMS_FOCUS_FOR_TESTING = 3;
72     public static final int ENTER_RING_FOCUS_FOR_TESTING = 4;
73     public static final int ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING = 5;
74     public static final int ABANDON_FOCUS_FOR_TESTING = 6;
75 
76     public static final int NO_MORE_ACTIVE_OR_DIALING_CALLS = 1001;
77     public static final int NO_MORE_RINGING_CALLS = 1002;
78     public static final int NO_MORE_HOLDING_CALLS = 1003;
79 
80     public static final int NEW_ACTIVE_OR_DIALING_CALL = 2001;
81     public static final int NEW_RINGING_CALL = 2002;
82     public static final int NEW_HOLDING_CALL = 2003;
83     public static final int MT_AUDIO_SPEEDUP_FOR_RINGING_CALL = 2004;
84 
85     public static final int TONE_STARTED_PLAYING = 3001;
86     public static final int TONE_STOPPED_PLAYING = 3002;
87 
88     public static final int FOREGROUND_VOIP_MODE_CHANGE = 4001;
89 
90     public static final int RUN_RUNNABLE = 9001;
91 
92     private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
93         put(ENTER_CALL_FOCUS_FOR_TESTING, "ENTER_CALL_FOCUS_FOR_TESTING");
94         put(ENTER_COMMS_FOCUS_FOR_TESTING, "ENTER_COMMS_FOCUS_FOR_TESTING");
95         put(ENTER_RING_FOCUS_FOR_TESTING, "ENTER_RING_FOCUS_FOR_TESTING");
96         put(ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING, "ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING");
97         put(ABANDON_FOCUS_FOR_TESTING, "ABANDON_FOCUS_FOR_TESTING");
98         put(NO_MORE_ACTIVE_OR_DIALING_CALLS, "NO_MORE_ACTIVE_OR_DIALING_CALLS");
99         put(NO_MORE_RINGING_CALLS, "NO_MORE_RINGING_CALLS");
100         put(NO_MORE_HOLDING_CALLS, "NO_MORE_HOLDING_CALLS");
101         put(NEW_ACTIVE_OR_DIALING_CALL, "NEW_ACTIVE_OR_DIALING_CALL");
102         put(NEW_RINGING_CALL, "NEW_RINGING_CALL");
103         put(NEW_HOLDING_CALL, "NEW_HOLDING_CALL");
104         put(MT_AUDIO_SPEEDUP_FOR_RINGING_CALL, "MT_AUDIO_SPEEDUP_FOR_RINGING_CALL");
105         put(TONE_STARTED_PLAYING, "TONE_STARTED_PLAYING");
106         put(TONE_STOPPED_PLAYING, "TONE_STOPPED_PLAYING");
107         put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
108 
109         put(RUN_RUNNABLE, "RUN_RUNNABLE");
110     }};
111 
112     public static final String TONE_HOLD_STATE_NAME = OtherFocusState.class.getSimpleName();
113     public static final String UNFOCUSED_STATE_NAME = UnfocusedState.class.getSimpleName();
114     public static final String CALL_STATE_NAME = SimCallFocusState.class.getSimpleName();
115     public static final String RING_STATE_NAME = RingingFocusState.class.getSimpleName();
116     public static final String COMMS_STATE_NAME = VoipCallFocusState.class.getSimpleName();
117 
118     private class BaseState extends State {
119         @Override
processMessage(Message msg)120         public boolean processMessage(Message msg) {
121             switch (msg.what) {
122                 case ENTER_CALL_FOCUS_FOR_TESTING:
123                     transitionTo(mSimCallFocusState);
124                     return HANDLED;
125                 case ENTER_COMMS_FOCUS_FOR_TESTING:
126                     transitionTo(mVoipCallFocusState);
127                     return HANDLED;
128                 case ENTER_RING_FOCUS_FOR_TESTING:
129                     transitionTo(mRingingFocusState);
130                     return HANDLED;
131                 case ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING:
132                     transitionTo(mOtherFocusState);
133                     return HANDLED;
134                 case ABANDON_FOCUS_FOR_TESTING:
135                     transitionTo(mUnfocusedState);
136                     return HANDLED;
137                 case INITIALIZE:
138                     mIsInitialized = true;
139                     return HANDLED;
140                 case RUN_RUNNABLE:
141                     java.lang.Runnable r = (java.lang.Runnable) msg.obj;
142                     r.run();
143                     return HANDLED;
144                 default:
145                     return NOT_HANDLED;
146             }
147         }
148     }
149 
150     private class UnfocusedState extends BaseState {
151         @Override
enter()152         public void enter() {
153             if (mIsInitialized) {
154                 Log.i(LOG_TAG, "Abandoning audio focus: now UNFOCUSED");
155                 mAudioManager.abandonAudioFocusForCall();
156                 mAudioManager.setMode(AudioManager.MODE_NORMAL);
157 
158                 mMostRecentMode = AudioManager.MODE_NORMAL;
159                 mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.NO_FOCUS);
160             }
161         }
162 
163         @Override
processMessage(Message msg)164         public boolean processMessage(Message msg) {
165             if (super.processMessage(msg) == HANDLED) {
166                 return HANDLED;
167             }
168             MessageArgs args = (MessageArgs) msg.obj;
169             switch (msg.what) {
170                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
171                     // Do nothing.
172                     return HANDLED;
173                 case NO_MORE_RINGING_CALLS:
174                     // Do nothing.
175                     return HANDLED;
176                 case NO_MORE_HOLDING_CALLS:
177                     // Do nothing.
178                     return HANDLED;
179                 case NEW_ACTIVE_OR_DIALING_CALL:
180                     transitionTo(args.foregroundCallIsVoip
181                             ? mVoipCallFocusState : mSimCallFocusState);
182                     return HANDLED;
183                 case NEW_RINGING_CALL:
184                     transitionTo(mRingingFocusState);
185                     return HANDLED;
186                 case NEW_HOLDING_CALL:
187                     // This really shouldn't happen, but transition to the focused state anyway.
188                     Log.w(LOG_TAG, "Call was surprisingly put into hold from an unknown state." +
189                             " Args are: \n" + args.toString());
190                     transitionTo(mOtherFocusState);
191                     return HANDLED;
192                 case TONE_STARTED_PLAYING:
193                     // This shouldn't happen either, but perform the action anyway.
194                     Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
195                             + args.toString());
196                     return HANDLED;
197                 default:
198                     // The forced focus switch commands are handled by BaseState.
199                     return NOT_HANDLED;
200             }
201         }
202     }
203 
204     private class RingingFocusState extends BaseState {
205         @Override
enter()206         public void enter() {
207             Log.i(LOG_TAG, "Audio focus entering RINGING state");
208             if (mCallAudioManager.startRinging()) {
209                 mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
210                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
211                 mAudioManager.setMode(AudioManager.MODE_RINGTONE);
212                 mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.RINGING_FOCUS);
213             } else {
214                 Log.i(LOG_TAG, "Entering RINGING but not acquiring focus -- silent ringtone");
215             }
216 
217             mCallAudioManager.stopCallWaiting();
218         }
219 
220         @Override
exit()221         public void exit() {
222             // Audio mode and audio stream will be set by the next state.
223             mCallAudioManager.stopRinging();
224         }
225 
226         @Override
processMessage(Message msg)227         public boolean processMessage(Message msg) {
228             if (super.processMessage(msg) == HANDLED) {
229                 return HANDLED;
230             }
231             MessageArgs args = (MessageArgs) msg.obj;
232             switch (msg.what) {
233                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
234                     // Do nothing. Loss of an active call should not impact ringer.
235                     return HANDLED;
236                 case NO_MORE_HOLDING_CALLS:
237                     // Do nothing and keep ringing.
238                     return HANDLED;
239                 case NO_MORE_RINGING_CALLS:
240                     // If there are active or holding calls, switch to the appropriate focus.
241                     // Otherwise abandon focus.
242                     if (args.hasActiveOrDialingCalls) {
243                         if (args.foregroundCallIsVoip) {
244                             transitionTo(mVoipCallFocusState);
245                         } else {
246                             transitionTo(mSimCallFocusState);
247                         }
248                     } else if (args.hasHoldingCalls || args.isTonePlaying) {
249                         transitionTo(mOtherFocusState);
250                     } else {
251                         transitionTo(mUnfocusedState);
252                     }
253                     return HANDLED;
254                 case NEW_ACTIVE_OR_DIALING_CALL:
255                     // If a call becomes active suddenly, give it priority over ringing.
256                     transitionTo(args.foregroundCallIsVoip
257                             ? mVoipCallFocusState : mSimCallFocusState);
258                     return HANDLED;
259                 case NEW_RINGING_CALL:
260                     Log.w(LOG_TAG, "Unexpected behavior! New ringing call appeared while in " +
261                             "ringing state.");
262                     return HANDLED;
263                 case NEW_HOLDING_CALL:
264                     // This really shouldn't happen, but transition to the focused state anyway.
265                     Log.w(LOG_TAG, "Call was surprisingly put into hold while ringing." +
266                             " Args are: " + args.toString());
267                     transitionTo(mOtherFocusState);
268                     return HANDLED;
269                 case MT_AUDIO_SPEEDUP_FOR_RINGING_CALL:
270                     // This happens when an IMS call is answered by the in-call UI. Special case
271                     // that we have to deal with for some reason.
272 
273                     // The IMS audio routing may be via modem or via RTP stream. In case via RTP
274                     // stream, the state machine should transit to mVoipCallFocusState.
275                     transitionTo(args.foregroundCallIsVoip
276                             ? mVoipCallFocusState : mSimCallFocusState);
277                     return HANDLED;
278                 default:
279                     // The forced focus switch commands are handled by BaseState.
280                     return NOT_HANDLED;
281             }
282         }
283     }
284 
285     private class SimCallFocusState extends BaseState {
286         @Override
enter()287         public void enter() {
288             Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
289             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
290                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
291             mAudioManager.setMode(AudioManager.MODE_IN_CALL);
292             mMostRecentMode = AudioManager.MODE_IN_CALL;
293             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
294         }
295 
296         @Override
processMessage(Message msg)297         public boolean processMessage(Message msg) {
298             if (super.processMessage(msg) == HANDLED) {
299                 return HANDLED;
300             }
301             MessageArgs args = (MessageArgs) msg.obj;
302             switch (msg.what) {
303                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
304                     // Switch to either ringing, holding, or inactive
305                     transitionTo(destinationStateAfterNoMoreActiveCalls(args));
306                     return HANDLED;
307                 case NO_MORE_RINGING_CALLS:
308                     // Don't transition state, but stop any call-waiting tones that may have been
309                     // playing.
310                     if (args.isTonePlaying) {
311                         mCallAudioManager.stopCallWaiting();
312                     }
313                     // If a MT-audio-speedup call gets disconnected by the connection service
314                     // concurrently with the user answering it, we may get this message
315                     // indicating that a ringing call has disconnected while this state machine
316                     // is in the SimCallFocusState.
317                     if (!args.hasActiveOrDialingCalls) {
318                         transitionTo(destinationStateAfterNoMoreActiveCalls(args));
319                     }
320                     return HANDLED;
321                 case NO_MORE_HOLDING_CALLS:
322                     // Do nothing.
323                     return HANDLED;
324                 case NEW_ACTIVE_OR_DIALING_CALL:
325                     // Do nothing. Already active.
326                     return HANDLED;
327                 case NEW_RINGING_CALL:
328                     // Don't make a call ring over an active call, but do play a call waiting tone.
329                     mCallAudioManager.startCallWaiting();
330                     return HANDLED;
331                 case NEW_HOLDING_CALL:
332                     // Don't do anything now. Putting an active call on hold will be handled when
333                     // NO_MORE_ACTIVE_CALLS is processed.
334                     return HANDLED;
335                 case FOREGROUND_VOIP_MODE_CHANGE:
336                     if (args.foregroundCallIsVoip) {
337                         transitionTo(mVoipCallFocusState);
338                     }
339                     return HANDLED;
340                 default:
341                     // The forced focus switch commands are handled by BaseState.
342                     return NOT_HANDLED;
343             }
344         }
345     }
346 
347     private class VoipCallFocusState extends BaseState {
348         @Override
enter()349         public void enter() {
350             Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
351             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
352                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
353             mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
354             mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
355             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
356         }
357 
358         @Override
processMessage(Message msg)359         public boolean processMessage(Message msg) {
360             if (super.processMessage(msg) == HANDLED) {
361                 return HANDLED;
362             }
363             MessageArgs args = (MessageArgs) msg.obj;
364             switch (msg.what) {
365                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
366                     // Switch to either ringing, holding, or inactive
367                     transitionTo(destinationStateAfterNoMoreActiveCalls(args));
368                     return HANDLED;
369                 case NO_MORE_RINGING_CALLS:
370                     // Don't transition state, but stop any call-waiting tones that may have been
371                     // playing.
372                     if (args.isTonePlaying) {
373                         mCallAudioManager.stopCallWaiting();
374                     }
375                     return HANDLED;
376                 case NO_MORE_HOLDING_CALLS:
377                     // Do nothing.
378                     return HANDLED;
379                 case NEW_ACTIVE_OR_DIALING_CALL:
380                     // Do nothing. Already active.
381                     return HANDLED;
382                 case NEW_RINGING_CALL:
383                     // Don't make a call ring over an active call, but do play a call waiting tone.
384                     mCallAudioManager.startCallWaiting();
385                     return HANDLED;
386                 case NEW_HOLDING_CALL:
387                     // Don't do anything now. Putting an active call on hold will be handled when
388                     // NO_MORE_ACTIVE_CALLS is processed.
389                     return HANDLED;
390                 case FOREGROUND_VOIP_MODE_CHANGE:
391                     if (!args.foregroundCallIsVoip) {
392                         transitionTo(mSimCallFocusState);
393                     }
394                     return HANDLED;
395                 default:
396                     // The forced focus switch commands are handled by BaseState.
397                     return NOT_HANDLED;
398             }
399         }
400     }
401 
402     /**
403      * This class is used for calls on hold and end-of-call tones.
404      */
405     private class OtherFocusState extends BaseState {
406         @Override
enter()407         public void enter() {
408             Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
409             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
410                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
411             mAudioManager.setMode(mMostRecentMode);
412             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
413         }
414 
415         @Override
processMessage(Message msg)416         public boolean processMessage(Message msg) {
417             if (super.processMessage(msg) == HANDLED) {
418                 return HANDLED;
419             }
420             MessageArgs args = (MessageArgs) msg.obj;
421             switch (msg.what) {
422                 case NO_MORE_HOLDING_CALLS:
423                     if (args.hasActiveOrDialingCalls) {
424                         transitionTo(args.foregroundCallIsVoip
425                                 ? mVoipCallFocusState : mSimCallFocusState);
426                     } else if (args.hasRingingCalls) {
427                         transitionTo(mRingingFocusState);
428                     } else if (!args.isTonePlaying) {
429                         transitionTo(mUnfocusedState);
430                     }
431                     // Do nothing if a tone is playing.
432                     return HANDLED;
433                 case NEW_ACTIVE_OR_DIALING_CALL:
434                     transitionTo(args.foregroundCallIsVoip
435                             ? mVoipCallFocusState : mSimCallFocusState);
436                     return HANDLED;
437                 case NEW_RINGING_CALL:
438                     // Apparently this is current behavior. Should this be the case?
439                     transitionTo(mRingingFocusState);
440                     return HANDLED;
441                 case NEW_HOLDING_CALL:
442                     // Do nothing.
443                     return HANDLED;
444                 case NO_MORE_RINGING_CALLS:
445                     // If there are no more ringing calls in this state, then stop any call-waiting
446                     // tones that may be playing.
447                     mCallAudioManager.stopCallWaiting();
448                     return HANDLED;
449                 case TONE_STOPPED_PLAYING:
450                     transitionTo(destinationStateAfterNoMoreActiveCalls(args));
451                 default:
452                     return NOT_HANDLED;
453             }
454         }
455     }
456 
457     private static final String LOG_TAG = CallAudioModeStateMachine.class.getSimpleName();
458 
459     private final BaseState mUnfocusedState = new UnfocusedState();
460     private final BaseState mRingingFocusState = new RingingFocusState();
461     private final BaseState mSimCallFocusState = new SimCallFocusState();
462     private final BaseState mVoipCallFocusState = new VoipCallFocusState();
463     private final BaseState mOtherFocusState = new OtherFocusState();
464 
465     private final AudioManager mAudioManager;
466     private CallAudioManager mCallAudioManager;
467 
468     private int mMostRecentMode;
469     private boolean mIsInitialized = false;
470 
CallAudioModeStateMachine(AudioManager audioManager)471     public CallAudioModeStateMachine(AudioManager audioManager) {
472         super(CallAudioModeStateMachine.class.getSimpleName());
473         mAudioManager = audioManager;
474         mMostRecentMode = AudioManager.MODE_NORMAL;
475 
476         addState(mUnfocusedState);
477         addState(mRingingFocusState);
478         addState(mSimCallFocusState);
479         addState(mVoipCallFocusState);
480         addState(mOtherFocusState);
481         setInitialState(mUnfocusedState);
482         start();
483         sendMessage(INITIALIZE, new MessageArgs());
484     }
485 
setCallAudioManager(CallAudioManager callAudioManager)486     public void setCallAudioManager(CallAudioManager callAudioManager) {
487         mCallAudioManager = callAudioManager;
488     }
489 
getCurrentStateName()490     public String getCurrentStateName() {
491         IState currentState = getCurrentState();
492         return currentState == null ? "no state" : currentState.getName();
493     }
494 
sendMessageWithArgs(int messageCode, MessageArgs args)495     public void sendMessageWithArgs(int messageCode, MessageArgs args) {
496         sendMessage(messageCode, args);
497     }
498 
499     @Override
onPreHandleMessage(Message msg)500     protected void onPreHandleMessage(Message msg) {
501         if (msg.obj != null && msg.obj instanceof MessageArgs) {
502             Log.continueSession(((MessageArgs) msg.obj).session, "CAMSM.pM_" + msg.what);
503             Log.i(LOG_TAG, "Message received: %s.", MESSAGE_CODE_TO_NAME.get(msg.what));
504         } else if (msg.what == RUN_RUNNABLE && msg.obj instanceof Runnable) {
505             Log.i(LOG_TAG, "Running runnable for testing");
506         } else {
507                 Log.w(LOG_TAG, "Message sent must be of type nonnull MessageArgs, but got " +
508                         (msg.obj == null ? "null" : msg.obj.getClass().getSimpleName()));
509                 Log.w(LOG_TAG, "The message was of code %d = %s",
510                         msg.what, MESSAGE_CODE_TO_NAME.get(msg.what));
511         }
512     }
513 
dumpPendingMessages(IndentingPrintWriter pw)514     public void dumpPendingMessages(IndentingPrintWriter pw) {
515         getHandler().getLooper().dump(pw::println, "");
516     }
517 
518     @Override
onPostHandleMessage(Message msg)519     protected void onPostHandleMessage(Message msg) {
520         Log.endSession();
521     }
522 
destinationStateAfterNoMoreActiveCalls(MessageArgs args)523     private BaseState destinationStateAfterNoMoreActiveCalls(MessageArgs args) {
524         if (args.hasHoldingCalls) {
525             return mOtherFocusState;
526         } else if (args.hasRingingCalls) {
527             return mRingingFocusState;
528         } else if (args.isTonePlaying) {
529             return mOtherFocusState;
530         } else {
531             return mUnfocusedState;
532         }
533     }
534 }
535