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