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