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