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