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