• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.server.telecom;
18 
19 import android.media.AudioManager;
20 import android.os.Message;
21 import android.telecom.Log;
22 import android.telecom.Logging.Runnable;
23 import android.telecom.Logging.Session;
24 import android.util.SparseArray;
25 
26 import com.android.internal.util.IState;
27 import com.android.internal.util.IndentingPrintWriter;
28 import com.android.internal.util.State;
29 import com.android.internal.util.StateMachine;
30 
31 public class CallAudioModeStateMachine extends StateMachine {
32     public static class MessageArgs {
33         public boolean hasActiveOrDialingCalls;
34         public boolean hasRingingCalls;
35         public boolean hasHoldingCalls;
36         public boolean isTonePlaying;
37         public boolean foregroundCallIsVoip;
38         public Session session;
39 
MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls, boolean hasHoldingCalls, boolean isTonePlaying, boolean foregroundCallIsVoip, Session session)40         public MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls,
41                 boolean hasHoldingCalls, boolean isTonePlaying, boolean foregroundCallIsVoip,
42                 Session session) {
43             this.hasActiveOrDialingCalls = hasActiveOrDialingCalls;
44             this.hasRingingCalls = hasRingingCalls;
45             this.hasHoldingCalls = hasHoldingCalls;
46             this.isTonePlaying = isTonePlaying;
47             this.foregroundCallIsVoip = foregroundCallIsVoip;
48             this.session = session;
49         }
50 
MessageArgs()51         public MessageArgs() {
52             this.session = Log.createSubsession();
53         }
54 
55         @Override
toString()56         public String toString() {
57             return "MessageArgs{" +
58                     "hasActiveCalls=" + hasActiveOrDialingCalls +
59                     ", hasRingingCalls=" + hasRingingCalls +
60                     ", hasHoldingCalls=" + hasHoldingCalls +
61                     ", isTonePlaying=" + isTonePlaying +
62                     ", foregroundCallIsVoip=" + foregroundCallIsVoip +
63                     ", session=" + session +
64                     '}';
65         }
66     }
67 
68     public static final int INITIALIZE = 1;
69     // These ENTER_*_FOCUS commands are for testing.
70     public static final int ENTER_CALL_FOCUS_FOR_TESTING = 2;
71     public static final int ENTER_COMMS_FOCUS_FOR_TESTING = 3;
72     public static final int ENTER_RING_FOCUS_FOR_TESTING = 4;
73     public static final int ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING = 5;
74     public static final int ABANDON_FOCUS_FOR_TESTING = 6;
75 
76     public static final int NO_MORE_ACTIVE_OR_DIALING_CALLS = 1001;
77     public static final int NO_MORE_RINGING_CALLS = 1002;
78     public static final int NO_MORE_HOLDING_CALLS = 1003;
79 
80     public static final int NEW_ACTIVE_OR_DIALING_CALL = 2001;
81     public static final int NEW_RINGING_CALL = 2002;
82     public static final int NEW_HOLDING_CALL = 2003;
83     public static final int MT_AUDIO_SPEEDUP_FOR_RINGING_CALL = 2004;
84 
85     public static final int TONE_STARTED_PLAYING = 3001;
86     public static final int TONE_STOPPED_PLAYING = 3002;
87 
88     public static final int FOREGROUND_VOIP_MODE_CHANGE = 4001;
89 
90     public static final int 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
processMessage(Message msg)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
enter()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
processMessage(Message msg)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 {
tryStartRinging()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
enter()221         public void enter() {
222             Log.i(LOG_TAG, "Audio focus entering RINGING state");
223             tryStartRinging();
224             mCallAudioManager.stopCallWaiting();
225         }
226 
227         @Override
exit()228         public void exit() {
229             // Audio mode and audio stream will be set by the next state.
230             mCallAudioManager.stopRinging();
231         }
232 
233         @Override
processMessage(Message msg)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
enter()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
processMessage(Message msg)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
enter()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
processMessage(Message msg)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
enter()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
processMessage(Message msg)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 
CallAudioModeStateMachine(AudioManager audioManager)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 
setCallAudioManager(CallAudioManager callAudioManager)498     public void setCallAudioManager(CallAudioManager callAudioManager) {
499         mCallAudioManager = callAudioManager;
500     }
501 
getCurrentStateName()502     public String getCurrentStateName() {
503         IState currentState = getCurrentState();
504         return currentState == null ? "no state" : currentState.getName();
505     }
506 
sendMessageWithArgs(int messageCode, MessageArgs args)507     public void sendMessageWithArgs(int messageCode, MessageArgs args) {
508         sendMessage(messageCode, args);
509     }
510 
511     @Override
onPreHandleMessage(Message msg)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 
dumpPendingMessages(IndentingPrintWriter pw)526     public void dumpPendingMessages(IndentingPrintWriter pw) {
527         getHandler().getLooper().dump(pw::println, "");
528     }
529 
530     @Override
onPostHandleMessage(Message msg)531     protected void onPostHandleMessage(Message msg) {
532         Log.endSession();
533     }
534 
destinationStateAfterNoMoreActiveCalls(MessageArgs args)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