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