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 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 processMessage(Message msg)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 enter()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 processMessage(Message msg)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 enter()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 if (mMostRecentMode == AudioManager.MODE_IN_CALL) { 212 // Preserving behavior from the old CallAudioManager. 213 Log.i(LOG_TAG, "Transition from IN_CALL -> RINGTONE." 214 + " Resetting to NORMAL first."); 215 mAudioManager.setMode(AudioManager.MODE_NORMAL); 216 } 217 mAudioManager.setMode(AudioManager.MODE_RINGTONE); 218 mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.RINGING_FOCUS); 219 } else { 220 Log.i(LOG_TAG, "Entering RINGING but not acquiring focus -- silent ringtone"); 221 } 222 223 mCallAudioManager.stopCallWaiting(); 224 } 225 226 @Override exit()227 public void exit() { 228 // Audio mode and audio stream will be set by the next state. 229 mCallAudioManager.stopRinging(); 230 } 231 232 @Override processMessage(Message msg)233 public boolean processMessage(Message msg) { 234 if (super.processMessage(msg) == HANDLED) { 235 return HANDLED; 236 } 237 MessageArgs args = (MessageArgs) msg.obj; 238 switch (msg.what) { 239 case NO_MORE_ACTIVE_OR_DIALING_CALLS: 240 // Do nothing. Loss of an active call should not impact ringer. 241 return HANDLED; 242 case NO_MORE_HOLDING_CALLS: 243 // Do nothing and keep ringing. 244 return HANDLED; 245 case NO_MORE_RINGING_CALLS: 246 // If there are active or holding calls, switch to the appropriate focus. 247 // Otherwise abandon focus. 248 if (args.hasActiveOrDialingCalls) { 249 if (args.foregroundCallIsVoip) { 250 transitionTo(mVoipCallFocusState); 251 } else { 252 transitionTo(mSimCallFocusState); 253 } 254 } else if (args.hasHoldingCalls || args.isTonePlaying) { 255 transitionTo(mOtherFocusState); 256 } else { 257 transitionTo(mUnfocusedState); 258 } 259 return HANDLED; 260 case NEW_ACTIVE_OR_DIALING_CALL: 261 // If a call becomes active suddenly, give it priority over ringing. 262 transitionTo(args.foregroundCallIsVoip 263 ? mVoipCallFocusState : mSimCallFocusState); 264 return HANDLED; 265 case NEW_RINGING_CALL: 266 Log.w(LOG_TAG, "Unexpected behavior! New ringing call appeared while in " + 267 "ringing state."); 268 return HANDLED; 269 case NEW_HOLDING_CALL: 270 // This really shouldn't happen, but transition to the focused state anyway. 271 Log.w(LOG_TAG, "Call was surprisingly put into hold while ringing." + 272 " Args are: " + args.toString()); 273 transitionTo(mOtherFocusState); 274 return HANDLED; 275 case MT_AUDIO_SPEEDUP_FOR_RINGING_CALL: 276 // This happens when an IMS call is answered by the in-call UI. Special case 277 // that we have to deal with for some reason. 278 279 // VOIP calls should never invoke this mechanism, so transition directly to 280 // the sim call focus state. 281 transitionTo(mSimCallFocusState); 282 return HANDLED; 283 default: 284 // The forced focus switch commands are handled by BaseState. 285 return NOT_HANDLED; 286 } 287 } 288 } 289 290 private class SimCallFocusState extends BaseState { 291 @Override enter()292 public void enter() { 293 Log.i(LOG_TAG, "Audio focus entering SIM CALL state"); 294 mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL, 295 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); 296 mAudioManager.setMode(AudioManager.MODE_IN_CALL); 297 mMostRecentMode = AudioManager.MODE_IN_CALL; 298 mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS); 299 } 300 301 @Override processMessage(Message msg)302 public boolean processMessage(Message msg) { 303 if (super.processMessage(msg) == HANDLED) { 304 return HANDLED; 305 } 306 MessageArgs args = (MessageArgs) msg.obj; 307 switch (msg.what) { 308 case NO_MORE_ACTIVE_OR_DIALING_CALLS: 309 // Switch to either ringing, holding, or inactive 310 transitionTo(destinationStateAfterNoMoreActiveCalls(args)); 311 return HANDLED; 312 case NO_MORE_RINGING_CALLS: 313 // Don't transition state, but stop any call-waiting tones that may have been 314 // playing. 315 if (args.isTonePlaying) { 316 mCallAudioManager.stopCallWaiting(); 317 } 318 // If a MT-audio-speedup call gets disconnected by the connection service 319 // concurrently with the user answering it, we may get this message 320 // indicating that a ringing call has disconnected while this state machine 321 // is in the SimCallFocusState. 322 if (!args.hasActiveOrDialingCalls) { 323 transitionTo(destinationStateAfterNoMoreActiveCalls(args)); 324 } 325 return HANDLED; 326 case NO_MORE_HOLDING_CALLS: 327 // Do nothing. 328 return HANDLED; 329 case NEW_ACTIVE_OR_DIALING_CALL: 330 // Do nothing. Already active. 331 return HANDLED; 332 case NEW_RINGING_CALL: 333 // Don't make a call ring over an active call, but do play a call waiting tone. 334 mCallAudioManager.startCallWaiting(); 335 return HANDLED; 336 case NEW_HOLDING_CALL: 337 // Don't do anything now. Putting an active call on hold will be handled when 338 // NO_MORE_ACTIVE_CALLS is processed. 339 return HANDLED; 340 case FOREGROUND_VOIP_MODE_CHANGE: 341 if (args.foregroundCallIsVoip) { 342 transitionTo(mVoipCallFocusState); 343 } 344 return HANDLED; 345 default: 346 // The forced focus switch commands are handled by BaseState. 347 return NOT_HANDLED; 348 } 349 } 350 } 351 352 private class VoipCallFocusState extends BaseState { 353 @Override enter()354 public void enter() { 355 Log.i(LOG_TAG, "Audio focus entering VOIP CALL state"); 356 mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL, 357 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); 358 mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION); 359 mMostRecentMode = AudioManager.MODE_IN_COMMUNICATION; 360 mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS); 361 } 362 363 @Override processMessage(Message msg)364 public boolean processMessage(Message msg) { 365 if (super.processMessage(msg) == HANDLED) { 366 return HANDLED; 367 } 368 MessageArgs args = (MessageArgs) msg.obj; 369 switch (msg.what) { 370 case NO_MORE_ACTIVE_OR_DIALING_CALLS: 371 // Switch to either ringing, holding, or inactive 372 transitionTo(destinationStateAfterNoMoreActiveCalls(args)); 373 return HANDLED; 374 case NO_MORE_RINGING_CALLS: 375 // Don't transition state, but stop any call-waiting tones that may have been 376 // playing. 377 if (args.isTonePlaying) { 378 mCallAudioManager.stopCallWaiting(); 379 } 380 return HANDLED; 381 case NO_MORE_HOLDING_CALLS: 382 // Do nothing. 383 return HANDLED; 384 case NEW_ACTIVE_OR_DIALING_CALL: 385 // Do nothing. Already active. 386 return HANDLED; 387 case NEW_RINGING_CALL: 388 // Don't make a call ring over an active call, but do play a call waiting tone. 389 mCallAudioManager.startCallWaiting(); 390 return HANDLED; 391 case NEW_HOLDING_CALL: 392 // Don't do anything now. Putting an active call on hold will be handled when 393 // NO_MORE_ACTIVE_CALLS is processed. 394 return HANDLED; 395 case FOREGROUND_VOIP_MODE_CHANGE: 396 if (!args.foregroundCallIsVoip) { 397 transitionTo(mSimCallFocusState); 398 } 399 return HANDLED; 400 default: 401 // The forced focus switch commands are handled by BaseState. 402 return NOT_HANDLED; 403 } 404 } 405 } 406 407 /** 408 * This class is used for calls on hold and end-of-call tones. 409 */ 410 private class OtherFocusState extends BaseState { 411 @Override enter()412 public void enter() { 413 Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state"); 414 mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL, 415 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); 416 mAudioManager.setMode(mMostRecentMode); 417 mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS); 418 } 419 420 @Override processMessage(Message msg)421 public boolean processMessage(Message msg) { 422 if (super.processMessage(msg) == HANDLED) { 423 return HANDLED; 424 } 425 MessageArgs args = (MessageArgs) msg.obj; 426 switch (msg.what) { 427 case NO_MORE_HOLDING_CALLS: 428 if (args.hasActiveOrDialingCalls) { 429 transitionTo(args.foregroundCallIsVoip 430 ? mVoipCallFocusState : mSimCallFocusState); 431 } else if (args.hasRingingCalls) { 432 transitionTo(mRingingFocusState); 433 } else if (!args.isTonePlaying) { 434 transitionTo(mUnfocusedState); 435 } 436 // Do nothing if a tone is playing. 437 return HANDLED; 438 case NEW_ACTIVE_OR_DIALING_CALL: 439 transitionTo(args.foregroundCallIsVoip 440 ? mVoipCallFocusState : mSimCallFocusState); 441 return HANDLED; 442 case NEW_RINGING_CALL: 443 // Apparently this is current behavior. Should this be the case? 444 transitionTo(mRingingFocusState); 445 return HANDLED; 446 case NEW_HOLDING_CALL: 447 // Do nothing. 448 return HANDLED; 449 case NO_MORE_RINGING_CALLS: 450 // If there are no more ringing calls in this state, then stop any call-waiting 451 // tones that may be playing. 452 mCallAudioManager.stopCallWaiting(); 453 return HANDLED; 454 case TONE_STOPPED_PLAYING: 455 transitionTo(destinationStateAfterNoMoreActiveCalls(args)); 456 default: 457 return NOT_HANDLED; 458 } 459 } 460 } 461 462 private static final String LOG_TAG = CallAudioModeStateMachine.class.getSimpleName(); 463 464 private final BaseState mUnfocusedState = new UnfocusedState(); 465 private final BaseState mRingingFocusState = new RingingFocusState(); 466 private final BaseState mSimCallFocusState = new SimCallFocusState(); 467 private final BaseState mVoipCallFocusState = new VoipCallFocusState(); 468 private final BaseState mOtherFocusState = new OtherFocusState(); 469 470 private final AudioManager mAudioManager; 471 private CallAudioManager mCallAudioManager; 472 473 private int mMostRecentMode; 474 private boolean mIsInitialized = false; 475 CallAudioModeStateMachine(AudioManager audioManager)476 public CallAudioModeStateMachine(AudioManager audioManager) { 477 super(CallAudioModeStateMachine.class.getSimpleName()); 478 mAudioManager = audioManager; 479 mMostRecentMode = AudioManager.MODE_NORMAL; 480 481 addState(mUnfocusedState); 482 addState(mRingingFocusState); 483 addState(mSimCallFocusState); 484 addState(mVoipCallFocusState); 485 addState(mOtherFocusState); 486 setInitialState(mUnfocusedState); 487 start(); 488 sendMessage(INITIALIZE, new MessageArgs()); 489 } 490 setCallAudioManager(CallAudioManager callAudioManager)491 public void setCallAudioManager(CallAudioManager callAudioManager) { 492 mCallAudioManager = callAudioManager; 493 } 494 getCurrentStateName()495 public String getCurrentStateName() { 496 IState currentState = getCurrentState(); 497 return currentState == null ? "no state" : currentState.getName(); 498 } 499 sendMessageWithArgs(int messageCode, MessageArgs args)500 public void sendMessageWithArgs(int messageCode, MessageArgs args) { 501 sendMessage(messageCode, args); 502 } 503 504 @Override onPreHandleMessage(Message msg)505 protected void onPreHandleMessage(Message msg) { 506 if (msg.obj != null && msg.obj instanceof MessageArgs) { 507 Log.continueSession(((MessageArgs) msg.obj).session, "CAMSM.pM_" + msg.what); 508 Log.i(LOG_TAG, "Message received: %s.", MESSAGE_CODE_TO_NAME.get(msg.what)); 509 } else if (msg.what == RUN_RUNNABLE && msg.obj instanceof Runnable) { 510 Log.i(LOG_TAG, "Running runnable for testing"); 511 } else { 512 Log.w(LOG_TAG, "Message sent must be of type nonnull MessageArgs, but got " + 513 (msg.obj == null ? "null" : msg.obj.getClass().getSimpleName())); 514 Log.w(LOG_TAG, "The message was of code %d = %s", 515 msg.what, MESSAGE_CODE_TO_NAME.get(msg.what)); 516 } 517 } 518 dumpPendingMessages(IndentingPrintWriter pw)519 public void dumpPendingMessages(IndentingPrintWriter pw) { 520 getHandler().getLooper().dump(pw::println, ""); 521 } 522 523 @Override onPostHandleMessage(Message msg)524 protected void onPostHandleMessage(Message msg) { 525 Log.endSession(); 526 } 527 destinationStateAfterNoMoreActiveCalls(MessageArgs args)528 private BaseState destinationStateAfterNoMoreActiveCalls(MessageArgs args) { 529 if (args.hasHoldingCalls) { 530 return mOtherFocusState; 531 } else if (args.hasRingingCalls) { 532 return mRingingFocusState; 533 } else if (args.isTonePlaying) { 534 return mOtherFocusState; 535 } else { 536 return mUnfocusedState; 537 } 538 } 539 } 540