CallsManager.java revision 1ccab206c39d0fafde7475bd1ff23fd346a17339
1/* 2 * Copyright (C) 2013 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.content.Context; 20import android.net.Uri; 21import android.os.Bundle; 22import android.os.Parcelable; 23import android.provider.CallLog.Calls; 24import android.telecom.AudioState; 25import android.telecom.CallState; 26import android.telecom.DisconnectCause; 27import android.telecom.GatewayInfo; 28import android.telecom.ParcelableConference; 29import android.telecom.PhoneAccountHandle; 30import android.telecom.PhoneCapabilities; 31import android.telephony.TelephonyManager; 32 33import com.android.internal.util.ArrayUtils; 34import com.android.internal.util.IndentingPrintWriter; 35import com.google.common.collect.ImmutableCollection; 36import com.google.common.collect.ImmutableList; 37 38import java.util.Arrays; 39import java.util.Collections; 40import java.util.List; 41import java.util.Objects; 42import java.util.Set; 43import java.util.concurrent.ConcurrentHashMap; 44 45/** 46 * Singleton. 47 * 48 * NOTE: by design most APIs are package private, use the relevant adapter/s to allow 49 * access from other packages specifically refraining from passing the CallsManager instance 50 * beyond the com.android.server.telecom package boundary. 51 */ 52public final class CallsManager extends Call.ListenerBase { 53 54 // TODO: Consider renaming this CallsManagerPlugin. 55 interface CallsManagerListener { 56 void onCallAdded(Call call); 57 void onCallRemoved(Call call); 58 void onCallStateChanged(Call call, int oldState, int newState); 59 void onConnectionServiceChanged( 60 Call call, 61 ConnectionServiceWrapper oldService, 62 ConnectionServiceWrapper newService); 63 void onIncomingCallAnswered(Call call); 64 void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage); 65 void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall); 66 void onAudioStateChanged(AudioState oldAudioState, AudioState newAudioState); 67 void onRingbackRequested(Call call, boolean ringback); 68 void onIsConferencedChanged(Call call); 69 void onIsVoipAudioModeChanged(Call call); 70 void onVideoStateChanged(Call call); 71 } 72 73 /** 74 * Singleton instance of the {@link CallsManager}, initialized from {@link TelecomService}. 75 */ 76 private static CallsManager INSTANCE = null; 77 78 private static final String TAG = "CallsManager"; 79 80 private static final int MAXIMUM_LIVE_CALLS = 1; 81 private static final int MAXIMUM_HOLD_CALLS = 1; 82 private static final int MAXIMUM_RINGING_CALLS = 1; 83 private static final int MAXIMUM_OUTGOING_CALLS = 1; 84 85 private static final int[] LIVE_CALL_STATES = 86 {CallState.CONNECTING, CallState.PRE_DIAL_WAIT, CallState.DIALING, CallState.ACTIVE}; 87 88 private static final int[] OUTGOING_CALL_STATES = 89 {CallState.CONNECTING, CallState.PRE_DIAL_WAIT, CallState.DIALING}; 90 91 /** 92 * The main call repository. Keeps an instance of all live calls. New incoming and outgoing 93 * calls are added to the map and removed when the calls move to the disconnected state. 94 * 95 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is 96 * load factor before resizing, 1 means we only expect a single thread to 97 * access the map so make only a single shard 98 */ 99 private final Set<Call> mCalls = Collections.newSetFromMap( 100 new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1)); 101 102 private final ConnectionServiceRepository mConnectionServiceRepository; 103 private final DtmfLocalTonePlayer mDtmfLocalTonePlayer; 104 private final InCallController mInCallController; 105 private final CallAudioManager mCallAudioManager; 106 private final Ringer mRinger; 107 // For this set initial table size to 16 because we add 13 listeners in 108 // the CallsManager constructor. 109 private final Set<CallsManagerListener> mListeners = Collections.newSetFromMap( 110 new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1)); 111 private final HeadsetMediaButton mHeadsetMediaButton; 112 private final WiredHeadsetManager mWiredHeadsetManager; 113 private final TtyManager mTtyManager; 114 private final ProximitySensorManager mProximitySensorManager; 115 private final PhoneStateBroadcaster mPhoneStateBroadcaster; 116 private final CallLogManager mCallLogManager; 117 private final Context mContext; 118 private final PhoneAccountRegistrar mPhoneAccountRegistrar; 119 private final MissedCallNotifier mMissedCallNotifier; 120 121 /** 122 * The call the user is currently interacting with. This is the call that should have audio 123 * focus and be visible in the in-call UI. 124 */ 125 private Call mForegroundCall; 126 127 /** Singleton accessor. */ 128 static CallsManager getInstance() { 129 return INSTANCE; 130 } 131 132 /** 133 * Sets the static singleton instance. 134 * 135 * @param instance The instance to set. 136 */ 137 static void initialize(CallsManager instance) { 138 INSTANCE = instance; 139 } 140 141 /** 142 * Initializes the required Telecom components. 143 */ 144 CallsManager(Context context, MissedCallNotifier missedCallNotifier, 145 PhoneAccountRegistrar phoneAccountRegistrar) { 146 mContext = context; 147 mPhoneAccountRegistrar = phoneAccountRegistrar; 148 mMissedCallNotifier = missedCallNotifier; 149 StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this); 150 mWiredHeadsetManager = new WiredHeadsetManager(context); 151 mCallAudioManager = new CallAudioManager(context, statusBarNotifier, mWiredHeadsetManager); 152 InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager); 153 mRinger = new Ringer(mCallAudioManager, this, playerFactory, context); 154 mHeadsetMediaButton = new HeadsetMediaButton(context, this); 155 mTtyManager = new TtyManager(context, mWiredHeadsetManager); 156 mProximitySensorManager = new ProximitySensorManager(context); 157 mPhoneStateBroadcaster = new PhoneStateBroadcaster(); 158 mCallLogManager = new CallLogManager(context); 159 mInCallController = new InCallController(context); 160 mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context); 161 mConnectionServiceRepository = new ConnectionServiceRepository(mPhoneAccountRegistrar, 162 context); 163 164 mListeners.add(statusBarNotifier); 165 mListeners.add(mCallLogManager); 166 mListeners.add(mPhoneStateBroadcaster); 167 mListeners.add(mInCallController); 168 mListeners.add(mRinger); 169 mListeners.add(new RingbackPlayer(this, playerFactory)); 170 mListeners.add(new InCallToneMonitor(playerFactory, this)); 171 mListeners.add(mCallAudioManager); 172 mListeners.add(missedCallNotifier); 173 mListeners.add(mDtmfLocalTonePlayer); 174 mListeners.add(mHeadsetMediaButton); 175 mListeners.add(RespondViaSmsManager.getInstance()); 176 mListeners.add(mProximitySensorManager); 177 } 178 179 @Override 180 public void onSuccessfulOutgoingCall(Call call, int callState) { 181 Log.v(this, "onSuccessfulOutgoingCall, %s", call); 182 183 setCallState(call, callState); 184 if (!mCalls.contains(call)) { 185 // Call was not added previously in startOutgoingCall due to it being a potential MMI 186 // code, so add it now. 187 addCall(call); 188 } 189 190 // The call's ConnectionService has been updated. 191 for (CallsManagerListener listener : mListeners) { 192 listener.onConnectionServiceChanged(call, null, call.getConnectionService()); 193 } 194 195 markCallAsDialing(call); 196 } 197 198 @Override 199 public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) { 200 Log.v(this, "onFailedOutgoingCall, call: %s", call); 201 202 // TODO: Replace disconnect cause with more specific disconnect causes. 203 markCallAsDisconnected(call, disconnectCause); 204 } 205 206 @Override 207 public void onSuccessfulIncomingCall(Call incomingCall) { 208 Log.d(this, "onSuccessfulIncomingCall"); 209 setCallState(incomingCall, CallState.RINGING); 210 211 if (hasMaximumRingingCalls()) { 212 incomingCall.reject(false, null); 213 // since the call was not added to the list of calls, we have to call the missed 214 // call notifier and the call logger manually. 215 mMissedCallNotifier.showMissedCallNotification(incomingCall); 216 mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE); 217 } else { 218 addCall(incomingCall); 219 } 220 } 221 222 @Override 223 public void onFailedIncomingCall(Call call) { 224 setCallState(call, CallState.DISCONNECTED); 225 call.removeListener(this); 226 } 227 228 @Override 229 public void onRingbackRequested(Call call, boolean ringback) { 230 for (CallsManagerListener listener : mListeners) { 231 listener.onRingbackRequested(call, ringback); 232 } 233 } 234 235 @Override 236 public void onPostDialWait(Call call, String remaining) { 237 mInCallController.onPostDialWait(call, remaining); 238 } 239 240 @Override 241 public void onParentChanged(Call call) { 242 for (CallsManagerListener listener : mListeners) { 243 listener.onIsConferencedChanged(call); 244 } 245 } 246 247 @Override 248 public void onChildrenChanged(Call call) { 249 for (CallsManagerListener listener : mListeners) { 250 listener.onIsConferencedChanged(call); 251 } 252 } 253 254 @Override 255 public void onIsVoipAudioModeChanged(Call call) { 256 for (CallsManagerListener listener : mListeners) { 257 listener.onIsVoipAudioModeChanged(call); 258 } 259 } 260 261 @Override 262 public void onVideoStateChanged(Call call) { 263 for (CallsManagerListener listener : mListeners) { 264 listener.onVideoStateChanged(call); 265 } 266 } 267 268 ImmutableCollection<Call> getCalls() { 269 return ImmutableList.copyOf(mCalls); 270 } 271 272 Call getForegroundCall() { 273 return mForegroundCall; 274 } 275 276 Ringer getRinger() { 277 return mRinger; 278 } 279 280 InCallController getInCallController() { 281 return mInCallController; 282 } 283 284 boolean hasEmergencyCall() { 285 for (Call call : mCalls) { 286 if (call.isEmergencyCall()) { 287 return true; 288 } 289 } 290 return false; 291 } 292 293 AudioState getAudioState() { 294 return mCallAudioManager.getAudioState(); 295 } 296 297 boolean isTtySupported() { 298 return mTtyManager.isTtySupported(); 299 } 300 301 int getCurrentTtyMode() { 302 return mTtyManager.getCurrentTtyMode(); 303 } 304 305 /** 306 * Starts the process to attach the call to a connection service. 307 * 308 * @param phoneAccountHandle The phone account which contains the component name of the 309 * connection service to use for this call. 310 * @param extras The optional extras Bundle passed with the intent used for the incoming call. 311 */ 312 void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) { 313 Log.d(this, "processIncomingCallIntent"); 314 Uri handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER); 315 Call call = new Call( 316 mContext, 317 mConnectionServiceRepository, 318 handle, 319 null /* gatewayInfo */, 320 null /* connectionManagerPhoneAccount */, 321 phoneAccountHandle, 322 true /* isIncoming */, 323 false /* isConference */); 324 325 call.setExtras(extras); 326 // TODO: Move this to be a part of addCall() 327 call.addListener(this); 328 call.startCreateConnection(mPhoneAccountRegistrar); 329 } 330 331 /** 332 * Kicks off the first steps to creating an outgoing call so that InCallUI can launch. 333 * 334 * @param handle Handle to connect the call with. 335 * @param phoneAccountHandle The phone account which contains the component name of the 336 * connection service to use for this call. 337 * @param extras The optional extras Bundle passed with the intent used for the incoming call. 338 */ 339 Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) { 340 // Create a call with original handle. The handle may be changed when the call is attached 341 // to a connection service, but in most cases will remain the same. 342 Call call = new Call( 343 mContext, 344 mConnectionServiceRepository, 345 handle, 346 null /* gatewayInfo */, 347 null /* connectionManagerPhoneAccount */, 348 null /* phoneAccountHandle */, 349 false /* isIncoming */, 350 false /* isConference */); 351 352 List<PhoneAccountHandle> accounts = 353 mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme()); 354 355 // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call 356 // as if a phoneAccount was not specified (does the default behavior instead). 357 // Note: We will not attempt to dial with a requested phoneAccount if it is disabled. 358 if (phoneAccountHandle != null) { 359 if (!accounts.contains(phoneAccountHandle)) { 360 phoneAccountHandle = null; 361 } 362 } 363 364 if (phoneAccountHandle == null) { 365 // No preset account, check if default exists that supports the URI scheme for the 366 // handle. 367 PhoneAccountHandle defaultAccountHandle = 368 mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount( 369 handle.getScheme()); 370 if (defaultAccountHandle != null) { 371 phoneAccountHandle = defaultAccountHandle; 372 } 373 } 374 375 call.setTargetPhoneAccount(phoneAccountHandle); 376 377 boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext, 378 call.getHandle()); 379 380 // Do not support any more live calls. Our options are to move a call to hold, disconnect 381 // a call, or cancel this call altogether. 382 if (!makeRoomForOutgoingCall(call, isEmergencyCall)) { 383 // just cancel at this point. 384 return null; 385 } 386 387 if (phoneAccountHandle == null && accounts.size() > 1 && !isEmergencyCall) { 388 // This is the state where the user is expected to select an account 389 call.setState(CallState.PRE_DIAL_WAIT); 390 extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS, accounts); 391 } else { 392 call.setState(CallState.CONNECTING); 393 } 394 395 call.setExtras(extras); 396 397 if (!isPotentialMMICode(handle)) { 398 addCall(call); 399 } else { 400 call.addListener(this); 401 } 402 403 return call; 404 } 405 406 /** 407 * Attempts to issue/connect the specified call. 408 * 409 * @param handle Handle to connect the call with. 410 * @param gatewayInfo Optional gateway information that can be used to route the call to the 411 * actual dialed handle via a gateway provider. May be null. 412 * @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects. 413 * @param videoState The desired video state for the outgoing call. 414 */ 415 void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn, 416 int videoState) { 417 if (call == null) { 418 // don't do anything if the call no longer exists 419 Log.i(this, "Canceling unknown call."); 420 return; 421 } 422 423 final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress(); 424 425 if (gatewayInfo == null) { 426 Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle)); 427 } else { 428 Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s", 429 Log.pii(uriHandle), Log.pii(handle)); 430 } 431 432 call.setHandle(uriHandle); 433 call.setGatewayInfo(gatewayInfo); 434 call.setStartWithSpeakerphoneOn(speakerphoneOn); 435 call.setVideoState(videoState); 436 437 boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext, 438 call.getHandle()); 439 if (isEmergencyCall) { 440 // Emergency -- CreateConnectionProcessor will choose accounts automatically 441 call.setTargetPhoneAccount(null); 442 } 443 444 if (call.getTargetPhoneAccount() != null || isEmergencyCall) { 445 // If the account has been set, proceed to place the outgoing call. 446 // Otherwise the connection will be initiated when the account is set by the user. 447 call.startCreateConnection(mPhoneAccountRegistrar); 448 } 449 } 450 451 /** 452 * Attempts to start a conference call for the specified call. 453 * 454 * @param call The call to conference. 455 * @param otherCall The other call to conference with. 456 */ 457 void conference(Call call, Call otherCall) { 458 call.conferenceWith(otherCall); 459 } 460 461 /** 462 * Instructs Telecom to answer the specified call. Intended to be invoked by the in-call 463 * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by 464 * the user opting to answer said call. 465 * 466 * @param call The call to answer. 467 * @param videoState The video state in which to answer the call. 468 */ 469 void answerCall(Call call, int videoState) { 470 if (!mCalls.contains(call)) { 471 Log.i(this, "Request to answer a non-existent call %s", call); 472 } else { 473 // If the foreground call is not the ringing call and it is currently isActive() or 474 // STATE_DIALING, put it on hold before answering the call. 475 if (mForegroundCall != null && mForegroundCall != call && 476 (mForegroundCall.isActive() || 477 mForegroundCall.getState() == CallState.DIALING)) { 478 if (0 == (mForegroundCall.getCallCapabilities() & PhoneCapabilities.HOLD)) { 479 // This call does not support hold. If it is from a different connection 480 // service, then disconnect it, otherwise allow the connection service to 481 // figure out the right states. 482 if (mForegroundCall.getConnectionService() != call.getConnectionService()) { 483 mForegroundCall.disconnect(); 484 } 485 } else { 486 Log.v(this, "Holding active/dialing call %s before answering incoming call %s.", 487 mForegroundCall, call); 488 mForegroundCall.hold(); 489 } 490 // TODO: Wait until we get confirmation of the active call being 491 // on-hold before answering the new call. 492 // TODO: Import logic from CallManager.acceptCall() 493 } 494 495 for (CallsManagerListener listener : mListeners) { 496 listener.onIncomingCallAnswered(call); 497 } 498 499 // We do not update the UI until we get confirmation of the answer() through 500 // {@link #markCallAsActive}. 501 call.answer(videoState); 502 } 503 } 504 505 /** 506 * Instructs Telecom to reject the specified call. Intended to be invoked by the in-call 507 * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by 508 * the user opting to reject said call. 509 */ 510 void rejectCall(Call call, boolean rejectWithMessage, String textMessage) { 511 if (!mCalls.contains(call)) { 512 Log.i(this, "Request to reject a non-existent call %s", call); 513 } else { 514 for (CallsManagerListener listener : mListeners) { 515 listener.onIncomingCallRejected(call, rejectWithMessage, textMessage); 516 } 517 call.reject(rejectWithMessage, textMessage); 518 } 519 } 520 521 /** 522 * Instructs Telecom to play the specified DTMF tone within the specified call. 523 * 524 * @param digit The DTMF digit to play. 525 */ 526 void playDtmfTone(Call call, char digit) { 527 if (!mCalls.contains(call)) { 528 Log.i(this, "Request to play DTMF in a non-existent call %s", call); 529 } else { 530 call.playDtmfTone(digit); 531 mDtmfLocalTonePlayer.playTone(call, digit); 532 } 533 } 534 535 /** 536 * Instructs Telecom to stop the currently playing DTMF tone, if any. 537 */ 538 void stopDtmfTone(Call call) { 539 if (!mCalls.contains(call)) { 540 Log.i(this, "Request to stop DTMF in a non-existent call %s", call); 541 } else { 542 call.stopDtmfTone(); 543 mDtmfLocalTonePlayer.stopTone(call); 544 } 545 } 546 547 /** 548 * Instructs Telecom to continue (or not) the current post-dial DTMF string, if any. 549 */ 550 void postDialContinue(Call call, boolean proceed) { 551 if (!mCalls.contains(call)) { 552 Log.i(this, "Request to continue post-dial string in a non-existent call %s", call); 553 } else { 554 call.postDialContinue(proceed); 555 } 556 } 557 558 /** 559 * Instructs Telecom to disconnect the specified call. Intended to be invoked by the 560 * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by 561 * the user hitting the end-call button. 562 */ 563 void disconnectCall(Call call) { 564 Log.v(this, "disconnectCall %s", call); 565 566 if (!mCalls.contains(call)) { 567 Log.w(this, "Unknown call (%s) asked to disconnect", call); 568 } else { 569 call.disconnect(); 570 } 571 } 572 573 /** 574 * Instructs Telecom to disconnect all calls. 575 */ 576 void disconnectAllCalls() { 577 Log.v(this, "disconnectAllCalls"); 578 579 for (Call call : mCalls) { 580 disconnectCall(call); 581 } 582 } 583 584 585 /** 586 * Instructs Telecom to put the specified call on hold. Intended to be invoked by the 587 * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by 588 * the user hitting the hold button during an active call. 589 */ 590 void holdCall(Call call) { 591 if (!mCalls.contains(call)) { 592 Log.w(this, "Unknown call (%s) asked to be put on hold", call); 593 } else { 594 Log.d(this, "Putting call on hold: (%s)", call); 595 call.hold(); 596 } 597 } 598 599 /** 600 * Instructs Telecom to release the specified call from hold. Intended to be invoked by 601 * the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered 602 * by the user hitting the hold button during a held call. 603 */ 604 void unholdCall(Call call) { 605 if (!mCalls.contains(call)) { 606 Log.w(this, "Unknown call (%s) asked to be removed from hold", call); 607 } else { 608 Log.d(this, "unholding call: (%s)", call); 609 for (Call c : mCalls) { 610 if (c != null && c.isAlive() && c != call) { 611 c.hold(); 612 } 613 } 614 call.unhold(); 615 } 616 } 617 618 /** Called by the in-call UI to change the mute state. */ 619 void mute(boolean shouldMute) { 620 mCallAudioManager.mute(shouldMute); 621 } 622 623 /** 624 * Called by the in-call UI to change the audio route, for example to change from earpiece to 625 * speaker phone. 626 */ 627 void setAudioRoute(int route) { 628 mCallAudioManager.setAudioRoute(route); 629 } 630 631 /** Called by the in-call UI to turn the proximity sensor on. */ 632 void turnOnProximitySensor() { 633 mProximitySensorManager.turnOn(); 634 } 635 636 /** 637 * Called by the in-call UI to turn the proximity sensor off. 638 * @param screenOnImmediately If true, the screen will be turned on immediately. Otherwise, 639 * the screen will be kept off until the proximity sensor goes negative. 640 */ 641 void turnOffProximitySensor(boolean screenOnImmediately) { 642 mProximitySensorManager.turnOff(screenOnImmediately); 643 } 644 645 void phoneAccountSelected(Call call, PhoneAccountHandle account) { 646 if (!mCalls.contains(call)) { 647 Log.i(this, "Attempted to add account to unknown call %s", call); 648 } else { 649 // TODO: There is an odd race condition here. Since NewOutgoingCallIntentBroadcaster and 650 // the PRE_DIAL_WAIT sequence run in parallel, if the user selects an account before the 651 // NEW_OUTGOING_CALL sequence finishes, we'll start the call immediately without 652 // respecting a rewritten number or a canceled number. This is unlikely since 653 // NEW_OUTGOING_CALL sequence, in practice, runs a lot faster than the user selecting 654 // a phone account from the in-call UI. 655 call.setTargetPhoneAccount(account); 656 657 // Note: emergency calls never go through account selection dialog so they never 658 // arrive here. 659 if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) { 660 call.startCreateConnection(mPhoneAccountRegistrar); 661 } else { 662 call.disconnect(); 663 } 664 } 665 } 666 667 /** Called when the audio state changes. */ 668 void onAudioStateChanged(AudioState oldAudioState, AudioState newAudioState) { 669 Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState); 670 for (CallsManagerListener listener : mListeners) { 671 listener.onAudioStateChanged(oldAudioState, newAudioState); 672 } 673 } 674 675 void markCallAsRinging(Call call) { 676 setCallState(call, CallState.RINGING); 677 } 678 679 void markCallAsDialing(Call call) { 680 setCallState(call, CallState.DIALING); 681 } 682 683 void markCallAsActive(Call call) { 684 if (call.getConnectTimeMillis() == 0) { 685 call.setConnectTimeMillis(System.currentTimeMillis()); 686 } 687 setCallState(call, CallState.ACTIVE); 688 689 if (call.getStartWithSpeakerphoneOn()) { 690 setAudioRoute(AudioState.ROUTE_SPEAKER); 691 } 692 } 693 694 void markCallAsOnHold(Call call) { 695 setCallState(call, CallState.ON_HOLD); 696 } 697 698 /** 699 * Marks the specified call as STATE_DISCONNECTED and notifies the in-call app. If this was the 700 * last live call, then also disconnect from the in-call controller. 701 * 702 * @param disconnectCause The disconnect cause, see {@link android.telecomm.DisconnectCause}. 703 */ 704 void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) { 705 call.setDisconnectCause(disconnectCause); 706 setCallState(call, CallState.DISCONNECTED); 707 removeCall(call); 708 } 709 710 /** 711 * Removes an existing disconnected call, and notifies the in-call app. 712 */ 713 void markCallAsRemoved(Call call) { 714 removeCall(call); 715 } 716 717 /** 718 * Cleans up any calls currently associated with the specified connection service when the 719 * service binder disconnects unexpectedly. 720 * 721 * @param service The connection service that disconnected. 722 */ 723 void handleConnectionServiceDeath(ConnectionServiceWrapper service) { 724 if (service != null) { 725 for (Call call : mCalls) { 726 if (call.getConnectionService() == service) { 727 markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR)); 728 } 729 } 730 } 731 } 732 733 boolean hasAnyCalls() { 734 return !mCalls.isEmpty(); 735 } 736 737 boolean hasActiveOrHoldingCall() { 738 return getFirstCallWithState(CallState.ACTIVE, CallState.ON_HOLD) != null; 739 } 740 741 boolean hasRingingCall() { 742 return getFirstCallWithState(CallState.RINGING) != null; 743 } 744 745 boolean onMediaButton(int type) { 746 if (hasAnyCalls()) { 747 if (HeadsetMediaButton.SHORT_PRESS == type) { 748 Call ringingCall = getFirstCallWithState(CallState.RINGING); 749 if (ringingCall == null) { 750 mCallAudioManager.toggleMute(); 751 return true; 752 } else { 753 ringingCall.answer(ringingCall.getVideoState()); 754 return true; 755 } 756 } else if (HeadsetMediaButton.LONG_PRESS == type) { 757 Log.d(this, "handleHeadsetHook: longpress -> hangup"); 758 Call callToHangup = getFirstCallWithState( 759 CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD); 760 if (callToHangup != null) { 761 callToHangup.disconnect(); 762 return true; 763 } 764 } 765 } 766 return false; 767 } 768 769 /** 770 * Checks to see if the specified call is the only high-level call and if so, enable the 771 * "Add-call" button. We allow you to add a second call but not a third or beyond. 772 * 773 * @param call The call to test for add-call. 774 * @return Whether the add-call feature should be enabled for the call. 775 */ 776 protected boolean isAddCallCapable(Call call) { 777 if (call.getParentCall() != null) { 778 // Never true for child calls. 779 return false; 780 } 781 782 // Loop through all the other calls and there exists a top level (has no parent) call 783 // that is not the specified call, return false. 784 for (Call otherCall : mCalls) { 785 if (call != otherCall && otherCall.getParentCall() == null) { 786 return false; 787 } 788 } 789 return true; 790 } 791 792 Call getFirstCallWithState(int... states) { 793 return getFirstCallWithState(null, states); 794 } 795 796 /** 797 * Returns the first call that it finds with the given states. The states are treated as having 798 * priority order so that any call with the first state will be returned before any call with 799 * states listed later in the parameter list. 800 * 801 * @param callToSkip Call that this method should skip while searching 802 */ 803 Call getFirstCallWithState(Call callToSkip, int... states) { 804 for (int currentState : states) { 805 // check the foreground first 806 if (mForegroundCall != null && mForegroundCall.getState() == currentState) { 807 return mForegroundCall; 808 } 809 810 for (Call call : mCalls) { 811 if (Objects.equals(callToSkip, call)) { 812 continue; 813 } 814 815 // Only operate on top-level calls 816 if (call.getParentCall() != null) { 817 continue; 818 } 819 820 if (currentState == call.getState()) { 821 return call; 822 } 823 } 824 } 825 return null; 826 } 827 828 Call createConferenceCall( 829 PhoneAccountHandle phoneAccount, 830 ParcelableConference parcelableConference) { 831 Call call = new Call( 832 mContext, 833 mConnectionServiceRepository, 834 null /* handle */, 835 null /* gatewayInfo */, 836 null /* connectionManagerPhoneAccount */, 837 phoneAccount, 838 false /* isIncoming */, 839 true /* isConference */); 840 841 setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState())); 842 if (call.getState() == CallState.ACTIVE) { 843 call.setConnectTimeMillis(System.currentTimeMillis()); 844 } 845 call.setCallCapabilities(parcelableConference.getCapabilities()); 846 847 // TODO: Move this to be a part of addCall() 848 call.addListener(this); 849 addCall(call); 850 return call; 851 } 852 853 /** 854 * @return the call state currently tracked by {@link PhoneStateBroadcaster} 855 */ 856 int getCallState() { 857 return mPhoneStateBroadcaster.getCallState(); 858 } 859 860 /** 861 * Retrieves the {@link PhoneAccountRegistrar}. 862 * 863 * @return The {@link PhoneAccountRegistrar}. 864 */ 865 PhoneAccountRegistrar getPhoneAccountRegistrar() { 866 return mPhoneAccountRegistrar; 867 } 868 869 /** 870 * Retrieves the {@link MissedCallNotifier} 871 * @return The {@link MissedCallNotifier}. 872 */ 873 MissedCallNotifier getMissedCallNotifier() { 874 return mMissedCallNotifier; 875 } 876 877 /** 878 * Adds the specified call to the main list of live calls. 879 * 880 * @param call The call to add. 881 */ 882 private void addCall(Call call) { 883 Log.v(this, "addCall(%s)", call); 884 885 call.addListener(this); 886 mCalls.add(call); 887 888 // TODO: Update mForegroundCall prior to invoking 889 // onCallAdded for calls which immediately take the foreground (like the first call). 890 for (CallsManagerListener listener : mListeners) { 891 listener.onCallAdded(call); 892 } 893 updateForegroundCall(); 894 } 895 896 private void removeCall(Call call) { 897 Log.v(this, "removeCall(%s)", call); 898 899 call.setParentCall(null); // need to clean up parent relationship before destroying. 900 call.removeListener(this); 901 call.clearConnectionService(); 902 903 boolean shouldNotify = false; 904 if (mCalls.contains(call)) { 905 mCalls.remove(call); 906 shouldNotify = true; 907 } 908 909 // Only broadcast changes for calls that are being tracked. 910 if (shouldNotify) { 911 for (CallsManagerListener listener : mListeners) { 912 listener.onCallRemoved(call); 913 } 914 updateForegroundCall(); 915 } 916 } 917 918 /** 919 * Sets the specified state on the specified call. 920 * 921 * @param call The call. 922 * @param newState The new state of the call. 923 */ 924 private void setCallState(Call call, int newState) { 925 if (call == null) { 926 return; 927 } 928 int oldState = call.getState(); 929 Log.i(this, "setCallState %s -> %s, call: %s", CallState.toString(oldState), 930 CallState.toString(newState), call); 931 if (newState != oldState) { 932 // Unfortunately, in the telephony world the radio is king. So if the call notifies 933 // us that the call is in a particular state, we allow it even if it doesn't make 934 // sense (e.g., STATE_ACTIVE -> STATE_RINGING). 935 // TODO: Consider putting a stop to the above and turning CallState 936 // into a well-defined state machine. 937 // TODO: Define expected state transitions here, and log when an 938 // unexpected transition occurs. 939 call.setState(newState); 940 941 // Only broadcast state change for calls that are being tracked. 942 if (mCalls.contains(call)) { 943 for (CallsManagerListener listener : mListeners) { 944 listener.onCallStateChanged(call, oldState, newState); 945 } 946 updateForegroundCall(); 947 } 948 } 949 } 950 951 /** 952 * Checks which call should be visible to the user and have audio focus. 953 */ 954 private void updateForegroundCall() { 955 Call newForegroundCall = null; 956 for (Call call : mCalls) { 957 // TODO: Foreground-ness needs to be explicitly set. No call, regardless 958 // of its state will be foreground by default and instead the connection service should 959 // be notified when its calls enter and exit foreground state. Foreground will mean that 960 // the call should play audio and listen to microphone if it wants. 961 962 // Only top-level calls can be in foreground 963 if (call.getParentCall() != null) { 964 continue; 965 } 966 967 // Active calls have priority. 968 if (call.isActive()) { 969 newForegroundCall = call; 970 break; 971 } 972 973 if (call.isAlive() || call.getState() == CallState.RINGING) { 974 newForegroundCall = call; 975 // Don't break in case there's an active call that has priority. 976 } 977 } 978 979 if (newForegroundCall != mForegroundCall) { 980 Log.v(this, "Updating foreground call, %s -> %s.", mForegroundCall, newForegroundCall); 981 Call oldForegroundCall = mForegroundCall; 982 mForegroundCall = newForegroundCall; 983 if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) { 984 mForegroundCall.unhold(); 985 } 986 for (CallsManagerListener listener : mListeners) { 987 listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall); 988 } 989 } 990 } 991 992 private boolean isPotentialMMICode(Uri handle) { 993 return (handle != null && handle.getSchemeSpecificPart() != null 994 && handle.getSchemeSpecificPart().contains("#")); 995 } 996 997 private int getNumCallsWithState(int... states) { 998 int count = 0; 999 for (int state : states) { 1000 for (Call call : mCalls) { 1001 if (call.getState() == state) { 1002 count++; 1003 } 1004 } 1005 } 1006 return count; 1007 } 1008 1009 private boolean hasMaximumLiveCalls() { 1010 return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(LIVE_CALL_STATES); 1011 } 1012 1013 private boolean hasMaximumHoldingCalls() { 1014 return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(CallState.ON_HOLD); 1015 } 1016 1017 private boolean hasMaximumRingingCalls() { 1018 return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(CallState.RINGING); 1019 } 1020 1021 private boolean hasMaximumOutgoingCalls() { 1022 return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(OUTGOING_CALL_STATES); 1023 } 1024 1025 private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) { 1026 if (hasMaximumLiveCalls()) { 1027 // NOTE: If the amount of live calls changes beyond 1, this logic will probably 1028 // have to change. 1029 Call liveCall = getFirstCallWithState(call, LIVE_CALL_STATES); 1030 1031 if (hasMaximumHoldingCalls()) { 1032 // There is no more room for any more calls, unless it's an emergency. 1033 if (isEmergency) { 1034 // Kill the current active call, this is easier then trying to disconnect a 1035 // holding call and hold an active call. 1036 liveCall.disconnect(); 1037 return true; 1038 } 1039 return false; // No more room! 1040 } 1041 1042 // We have room for at least one more holding call at this point. 1043 1044 // First thing, if we are trying to make a call with the same phone account as the live 1045 // call, then allow it so that the connection service can make its own decision about 1046 // how to handle the new call relative to the current one. 1047 if (Objects.equals(liveCall.getTargetPhoneAccount(), call.getTargetPhoneAccount())) { 1048 return true; 1049 } else if (call.getTargetPhoneAccount() == null) { 1050 // Without a phone account, we can't say reliably that the call will fail. 1051 // If the user chooses the same phone account as the live call, then it's 1052 // still possible that the call can be made (like with CDMA calls not supporting 1053 // hold but they still support adding a call by going immediately into conference 1054 // mode). Return true here and we'll run this code again after user chooses an 1055 // account. 1056 return true; 1057 } 1058 1059 // Try to hold the live call before attempting the new outgoing call. 1060 if (liveCall.can(PhoneCapabilities.HOLD)) { 1061 liveCall.hold(); 1062 return true; 1063 } 1064 1065 // The live call cannot be held so we're out of luck here. There's no room. 1066 return false; 1067 } 1068 return true; 1069 } 1070 1071 /** 1072 * Dumps the state of the {@link CallsManager}. 1073 * 1074 * @param pw The {@code IndentingPrintWriter} to write the state to. 1075 */ 1076 public void dump(IndentingPrintWriter pw) { 1077 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 1078 pw.increaseIndent(); 1079 if (mCalls != null) { 1080 pw.println("mCalls: "); 1081 pw.increaseIndent(); 1082 for (Call call : mCalls) { 1083 pw.println(call); 1084 } 1085 pw.decreaseIndent(); 1086 } 1087 pw.decreaseIndent(); 1088 } 1089} 1090