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