CallAudioModeStateMachine.java revision e271f2268db782a2c72e115bf67044be3baa1416
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
17package com.android.server.telecom;
18
19import android.media.AudioManager;
20import android.os.Message;
21import android.util.SparseArray;
22
23import com.android.internal.util.IState;
24import com.android.internal.util.State;
25import com.android.internal.util.StateMachine;
26
27public 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
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
47        public MessageArgs() {
48            this.session = Log.createSubsession();
49        }
50
51        @Override
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
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
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
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
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.RINGING_FOCUS);
217        }
218
219        @Override
220        public void exit() {
221            // Audio mode and audio stream will be set by the next state.
222            mCallAudioManager.stopRinging();
223        }
224
225        @Override
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
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.ACTIVE_FOCUS);
292        }
293
294        @Override
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
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.ACTIVE_FOCUS);
354        }
355
356        @Override
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
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.ACTIVE_FOCUS);
411        }
412
413        @Override
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
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
484    public void setCallAudioManager(CallAudioManager callAudioManager) {
485        mCallAudioManager = callAudioManager;
486    }
487
488    public String getCurrentStateName() {
489        IState currentState = getCurrentState();
490        return currentState == null ? "no state" : currentState.getName();
491    }
492
493    public void sendMessageWithArgs(int messageCode, MessageArgs args) {
494        sendMessage(messageCode, args);
495    }
496
497    @Override
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
513    protected void onPostHandleMessage(Message msg) {
514        Log.endSession();
515    }
516
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}