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