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             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
205                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
206             if (mMostRecentMode == AudioManager.MODE_IN_CALL) {
207                 // Preserving behavior from the old CallAudioManager.
208                 Log.i(LOG_TAG, "Transition from IN_CALL -> RINGTONE."
209                         + "  Resetting to NORMAL first.");
210                 mAudioManager.setMode(AudioManager.MODE_NORMAL);
211             }
212             mAudioManager.setMode(AudioManager.MODE_RINGTONE);
213 
214             mCallAudioManager.stopCallWaiting();
215             mCallAudioManager.startRinging();
216             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
217         }
218 
219         @Override
exit()220         public void exit() {
221             // Audio mode and audio stream will be set by the next state.
222             mCallAudioManager.stopRinging();
223         }
224 
225         @Override
processMessage(Message msg)226         public boolean processMessage(Message msg) {
227             if (super.processMessage(msg) == HANDLED) {
228                 return HANDLED;
229             }
230             MessageArgs args = (MessageArgs) msg.obj;
231             switch (msg.what) {
232                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
233                     // Do nothing. Loss of an active call should not impact ringer.
234                     return HANDLED;
235                 case NO_MORE_HOLDING_CALLS:
236                     // Do nothing and keep ringing.
237                     return HANDLED;
238                 case NO_MORE_RINGING_CALLS:
239                     // If there are active or holding calls, switch to the appropriate focus.
240                     // Otherwise abandon focus.
241                     if (args.hasActiveOrDialingCalls) {
242                         if (args.foregroundCallIsVoip) {
243                             transitionTo(mVoipCallFocusState);
244                         } else {
245                             transitionTo(mSimCallFocusState);
246                         }
247                     } else if (args.hasHoldingCalls || args.isTonePlaying) {
248                         transitionTo(mOtherFocusState);
249                     } else {
250                         transitionTo(mUnfocusedState);
251                     }
252                     return HANDLED;
253                 case NEW_ACTIVE_OR_DIALING_CALL:
254                     // If a call becomes active suddenly, give it priority over ringing.
255                     transitionTo(args.foregroundCallIsVoip
256                             ? mVoipCallFocusState : mSimCallFocusState);
257                     return HANDLED;
258                 case NEW_RINGING_CALL:
259                     Log.w(LOG_TAG, "Unexpected behavior! New ringing call appeared while in " +
260                             "ringing state.");
261                     return HANDLED;
262                 case NEW_HOLDING_CALL:
263                     // This really shouldn't happen, but transition to the focused state anyway.
264                     Log.w(LOG_TAG, "Call was surprisingly put into hold while ringing." +
265                             " Args are: " + args.toString());
266                     transitionTo(mOtherFocusState);
267                     return HANDLED;
268                 case MT_AUDIO_SPEEDUP_FOR_RINGING_CALL:
269                     // This happens when an IMS call is answered by the in-call UI. Special case
270                     // that we have to deal with for some reason.
271 
272                     // VOIP calls should never invoke this mechanism, so transition directly to
273                     // the sim call focus state.
274                     transitionTo(mSimCallFocusState);
275                     return HANDLED;
276                 default:
277                     // The forced focus switch commands are handled by BaseState.
278                     return NOT_HANDLED;
279             }
280         }
281     }
282 
283     private class SimCallFocusState extends BaseState {
284         @Override
enter()285         public void enter() {
286             Log.i(LOG_TAG, "Audio focus entering SIM CALL state");
287             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
288                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
289             mAudioManager.setMode(AudioManager.MODE_IN_CALL);
290             mMostRecentMode = AudioManager.MODE_IN_CALL;
291             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
292         }
293 
294         @Override
processMessage(Message msg)295         public boolean processMessage(Message msg) {
296             if (super.processMessage(msg) == HANDLED) {
297                 return HANDLED;
298             }
299             MessageArgs args = (MessageArgs) msg.obj;
300             switch (msg.what) {
301                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
302                     // Switch to either ringing, holding, or inactive
303                     transitionTo(destinationStateAfterNoMoreActiveCalls(args));
304                     return HANDLED;
305                 case NO_MORE_RINGING_CALLS:
306                     // Don't transition state, but stop any call-waiting tones that may have been
307                     // playing.
308                     if (args.isTonePlaying) {
309                         mCallAudioManager.stopCallWaiting();
310                     }
311                     // If a MT-audio-speedup call gets disconnected by the connection service
312                     // concurrently with the user answering it, we may get this message
313                     // indicating that a ringing call has disconnected while this state machine
314                     // is in the SimCallFocusState.
315                     if (!args.hasActiveOrDialingCalls) {
316                         transitionTo(destinationStateAfterNoMoreActiveCalls(args));
317                     }
318                     return HANDLED;
319                 case NO_MORE_HOLDING_CALLS:
320                     // Do nothing.
321                     return HANDLED;
322                 case NEW_ACTIVE_OR_DIALING_CALL:
323                     // Do nothing. Already active.
324                     return HANDLED;
325                 case NEW_RINGING_CALL:
326                     // Don't make a call ring over an active call, but do play a call waiting tone.
327                     mCallAudioManager.startCallWaiting();
328                     return HANDLED;
329                 case NEW_HOLDING_CALL:
330                     // Don't do anything now. Putting an active call on hold will be handled when
331                     // NO_MORE_ACTIVE_CALLS is processed.
332                     return HANDLED;
333                 case FOREGROUND_VOIP_MODE_CHANGE:
334                     if (args.foregroundCallIsVoip) {
335                         transitionTo(mVoipCallFocusState);
336                     }
337                     return HANDLED;
338                 default:
339                     // The forced focus switch commands are handled by BaseState.
340                     return NOT_HANDLED;
341             }
342         }
343     }
344 
345     private class VoipCallFocusState extends BaseState {
346         @Override
enter()347         public void enter() {
348             Log.i(LOG_TAG, "Audio focus entering VOIP CALL state");
349             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
350                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
351             mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
352             mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION;
353             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
354         }
355 
356         @Override
processMessage(Message msg)357         public boolean processMessage(Message msg) {
358             if (super.processMessage(msg) == HANDLED) {
359                 return HANDLED;
360             }
361             MessageArgs args = (MessageArgs) msg.obj;
362             switch (msg.what) {
363                 case NO_MORE_ACTIVE_OR_DIALING_CALLS:
364                     // Switch to either ringing, holding, or inactive
365                     transitionTo(destinationStateAfterNoMoreActiveCalls(args));
366                     return HANDLED;
367                 case NO_MORE_RINGING_CALLS:
368                     // Don't transition state, but stop any call-waiting tones that may have been
369                     // playing.
370                     if (args.isTonePlaying) {
371                         mCallAudioManager.stopCallWaiting();
372                     }
373                     return HANDLED;
374                 case NO_MORE_HOLDING_CALLS:
375                     // Do nothing.
376                     return HANDLED;
377                 case NEW_ACTIVE_OR_DIALING_CALL:
378                     // Do nothing. Already active.
379                     return HANDLED;
380                 case NEW_RINGING_CALL:
381                     // Don't make a call ring over an active call, but do play a call waiting tone.
382                     mCallAudioManager.startCallWaiting();
383                     return HANDLED;
384                 case NEW_HOLDING_CALL:
385                     // Don't do anything now. Putting an active call on hold will be handled when
386                     // NO_MORE_ACTIVE_CALLS is processed.
387                     return HANDLED;
388                 case FOREGROUND_VOIP_MODE_CHANGE:
389                     if (!args.foregroundCallIsVoip) {
390                         transitionTo(mSimCallFocusState);
391                     }
392                     return HANDLED;
393                 default:
394                     // The forced focus switch commands are handled by BaseState.
395                     return NOT_HANDLED;
396             }
397         }
398     }
399 
400     /**
401      * This class is used for calls on hold and end-of-call tones.
402      */
403     private class OtherFocusState extends BaseState {
404         @Override
enter()405         public void enter() {
406             Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state");
407             mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL,
408                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
409             mAudioManager.setMode(mMostRecentMode);
410             mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS);
411         }
412 
413         @Override
processMessage(Message msg)414         public boolean processMessage(Message msg) {
415             if (super.processMessage(msg) == HANDLED) {
416                 return HANDLED;
417             }
418             MessageArgs args = (MessageArgs) msg.obj;
419             switch (msg.what) {
420                 case NO_MORE_HOLDING_CALLS:
421                     if (args.hasActiveOrDialingCalls) {
422                         transitionTo(args.foregroundCallIsVoip
423                                 ? mVoipCallFocusState : mSimCallFocusState);
424                     } else if (args.hasRingingCalls) {
425                         transitionTo(mRingingFocusState);
426                     } else if (!args.isTonePlaying) {
427                         transitionTo(mUnfocusedState);
428                     }
429                     // Do nothing if a tone is playing.
430                     return HANDLED;
431                 case NEW_ACTIVE_OR_DIALING_CALL:
432                     transitionTo(args.foregroundCallIsVoip
433                             ? mVoipCallFocusState : mSimCallFocusState);
434                     return HANDLED;
435                 case NEW_RINGING_CALL:
436                     // Apparently this is current behavior. Should this be the case?
437                     transitionTo(mRingingFocusState);
438                     return HANDLED;
439                 case NEW_HOLDING_CALL:
440                     // Do nothing.
441                     return HANDLED;
442                 case NO_MORE_RINGING_CALLS:
443                     // If there are no more ringing calls in this state, then stop any call-waiting
444                     // tones that may be playing.
445                     mCallAudioManager.stopCallWaiting();
446                     return HANDLED;
447                 case TONE_STOPPED_PLAYING:
448                     transitionTo(destinationStateAfterNoMoreActiveCalls(args));
449                 default:
450                     return NOT_HANDLED;
451             }
452         }
453     }
454 
455     private static final String LOG_TAG = CallAudioModeStateMachine.class.getSimpleName();
456 
457     private final BaseState mUnfocusedState = new UnfocusedState();
458     private final BaseState mRingingFocusState = new RingingFocusState();
459     private final BaseState mSimCallFocusState = new SimCallFocusState();
460     private final BaseState mVoipCallFocusState = new VoipCallFocusState();
461     private final BaseState mOtherFocusState = new OtherFocusState();
462 
463     private final AudioManager mAudioManager;
464     private CallAudioManager mCallAudioManager;
465 
466     private int mMostRecentMode;
467     private boolean mIsInitialized = false;
468 
CallAudioModeStateMachine(AudioManager audioManager)469     public CallAudioModeStateMachine(AudioManager audioManager) {
470         super(CallAudioModeStateMachine.class.getSimpleName());
471         mAudioManager = audioManager;
472         mMostRecentMode = AudioManager.MODE_NORMAL;
473 
474         addState(mUnfocusedState);
475         addState(mRingingFocusState);
476         addState(mSimCallFocusState);
477         addState(mVoipCallFocusState);
478         addState(mOtherFocusState);
479         setInitialState(mUnfocusedState);
480         start();
481         sendMessage(INITIALIZE, new MessageArgs());
482     }
483 
setCallAudioManager(CallAudioManager callAudioManager)484     public void setCallAudioManager(CallAudioManager callAudioManager) {
485         mCallAudioManager = callAudioManager;
486     }
487 
getCurrentStateName()488     public String getCurrentStateName() {
489         IState currentState = getCurrentState();
490         return currentState == null ? "no state" : currentState.getName();
491     }
492 
sendMessageWithArgs(int messageCode, MessageArgs args)493     public void sendMessageWithArgs(int messageCode, MessageArgs args) {
494         sendMessage(messageCode, args);
495     }
496 
497     @Override
onPreHandleMessage(Message msg)498     protected void onPreHandleMessage(Message msg) {
499         if (msg.obj != null && msg.obj instanceof MessageArgs) {
500             Log.continueSession(((MessageArgs) msg.obj).session, "CAMSM.pM_" + msg.what);
501             Log.i(LOG_TAG, "Message received: %s.", MESSAGE_CODE_TO_NAME.get(msg.what));
502         } else if (msg.what == RUN_RUNNABLE && msg.obj instanceof Runnable) {
503             Log.i(LOG_TAG, "Running runnable for testing");
504         } else {
505                 Log.w(LOG_TAG, "Message sent must be of type nonnull MessageArgs, but got " +
506                         (msg.obj == null ? "null" : msg.obj.getClass().getSimpleName()));
507                 Log.w(LOG_TAG, "The message was of code %d = %s",
508                         msg.what, MESSAGE_CODE_TO_NAME.get(msg.what));
509         }
510     }
511 
512     @Override
onPostHandleMessage(Message msg)513     protected void onPostHandleMessage(Message msg) {
514         Log.endSession();
515     }
516 
destinationStateAfterNoMoreActiveCalls(MessageArgs args)517     private BaseState destinationStateAfterNoMoreActiveCalls(MessageArgs args) {
518         if (args.hasHoldingCalls) {
519             return mOtherFocusState;
520         } else if (args.hasRingingCalls) {
521             return mRingingFocusState;
522         } else if (args.isTonePlaying) {
523             return mOtherFocusState;
524         } else {
525             return mUnfocusedState;
526         }
527     }
528 }