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