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