CallsManager.java revision e4cd9162be58042a99c6f5eadfa9b77d5686a8ea
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.Handler; 23import android.os.Looper; 24import android.os.SystemProperties; 25import android.os.Trace; 26import android.provider.CallLog.Calls; 27import android.telecom.CallAudioState; 28import android.telecom.Conference; 29import android.telecom.Connection; 30import android.telecom.DisconnectCause; 31import android.telecom.GatewayInfo; 32import android.telecom.ParcelableConference; 33import android.telecom.ParcelableConnection; 34import android.telecom.PhoneAccount; 35import android.telecom.PhoneAccountHandle; 36import android.telecom.TelecomManager; 37import android.telecom.VideoProfile; 38import android.telephony.PhoneNumberUtils; 39import android.telephony.TelephonyManager; 40import android.text.TextUtils; 41 42import com.android.internal.annotations.VisibleForTesting; 43import com.android.internal.telephony.PhoneConstants; 44import com.android.internal.telephony.TelephonyProperties; 45import com.android.internal.util.IndentingPrintWriter; 46 47import java.util.Collection; 48import java.util.Collections; 49import java.util.HashSet; 50import java.util.List; 51import java.util.Objects; 52import java.util.Set; 53import java.util.concurrent.ConcurrentHashMap; 54 55/** 56 * Singleton. 57 * 58 * NOTE: by design most APIs are package private, use the relevant adapter/s to allow 59 * access from other packages specifically refraining from passing the CallsManager instance 60 * beyond the com.android.server.telecom package boundary. 61 */ 62@VisibleForTesting 63public class CallsManager extends Call.ListenerBase implements VideoProviderProxy.Listener { 64 65 // TODO: Consider renaming this CallsManagerPlugin. 66 interface CallsManagerListener { 67 void onCallAdded(Call call); 68 void onCallRemoved(Call call); 69 void onCallStateChanged(Call call, int oldState, int newState); 70 void onConnectionServiceChanged( 71 Call call, 72 ConnectionServiceWrapper oldService, 73 ConnectionServiceWrapper newService); 74 void onIncomingCallAnswered(Call call); 75 void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage); 76 void onForegroundCallChanged(Call oldForegroundCall, Call newForegroundCall); 77 void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState); 78 void onRingbackRequested(Call call, boolean ringback); 79 void onIsConferencedChanged(Call call); 80 void onIsVoipAudioModeChanged(Call call); 81 void onVideoStateChanged(Call call); 82 void onCanAddCallChanged(boolean canAddCall); 83 void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile); 84 } 85 86 private static final String TAG = "CallsManager"; 87 88 private static final int MAXIMUM_LIVE_CALLS = 1; 89 private static final int MAXIMUM_HOLD_CALLS = 1; 90 private static final int MAXIMUM_RINGING_CALLS = 1; 91 private static final int MAXIMUM_OUTGOING_CALLS = 1; 92 private static final int MAXIMUM_TOP_LEVEL_CALLS = 2; 93 94 private static final int[] OUTGOING_CALL_STATES = 95 {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING}; 96 97 private static final int[] LIVE_CALL_STATES = 98 {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING, CallState.ACTIVE}; 99 100 /** 101 * The main call repository. Keeps an instance of all live calls. New incoming and outgoing 102 * calls are added to the map and removed when the calls move to the disconnected state. 103 * 104 * ConcurrentHashMap constructor params: 8 is initial table size, 0.9f is 105 * load factor before resizing, 1 means we only expect a single thread to 106 * access the map so make only a single shard 107 */ 108 private final Set<Call> mCalls = Collections.newSetFromMap( 109 new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1)); 110 111 private final ConnectionServiceRepository mConnectionServiceRepository; 112 private final DtmfLocalTonePlayer mDtmfLocalTonePlayer; 113 private final InCallController mInCallController; 114 private final CallAudioManager mCallAudioManager; 115 private RespondViaSmsManager mRespondViaSmsManager; 116 private final Ringer mRinger; 117 private final InCallWakeLockController mInCallWakeLockController; 118 // For this set initial table size to 16 because we add 13 listeners in 119 // the CallsManager constructor. 120 private final Set<CallsManagerListener> mListeners = Collections.newSetFromMap( 121 new ConcurrentHashMap<CallsManagerListener, Boolean>(16, 0.9f, 1)); 122 private final HeadsetMediaButton mHeadsetMediaButton; 123 private final WiredHeadsetManager mWiredHeadsetManager; 124 private final DockManager mDockManager; 125 private final TtyManager mTtyManager; 126 private final ProximitySensorManager mProximitySensorManager; 127 private final PhoneStateBroadcaster mPhoneStateBroadcaster; 128 private final CallLogManager mCallLogManager; 129 private final Context mContext; 130 private final TelecomSystem.SyncRoot mLock; 131 private final ContactsAsyncHelper mContactsAsyncHelper; 132 private final CallerInfoAsyncQueryFactory mCallerInfoAsyncQueryFactory; 133 private final PhoneAccountRegistrar mPhoneAccountRegistrar; 134 private final MissedCallNotifier mMissedCallNotifier; 135 private final Set<Call> mLocallyDisconnectingCalls = new HashSet<>(); 136 private final Set<Call> mPendingCallsToDisconnect = new HashSet<>(); 137 /* Handler tied to thread in which CallManager was initialized. */ 138 private final Handler mHandler = new Handler(Looper.getMainLooper()); 139 140 private boolean mCanAddCall = true; 141 142 /** 143 * The call the user is currently interacting with. This is the call that should have audio 144 * focus and be visible in the in-call UI. 145 */ 146 private Call mForegroundCall; 147 148 private Runnable mStopTone; 149 150 /** 151 * Initializes the required Telecom components. 152 */ 153 CallsManager( 154 Context context, 155 TelecomSystem.SyncRoot lock, 156 ContactsAsyncHelper contactsAsyncHelper, 157 CallerInfoAsyncQueryFactory callerInfoAsyncQueryFactory, 158 MissedCallNotifier missedCallNotifier, 159 PhoneAccountRegistrar phoneAccountRegistrar, 160 HeadsetMediaButtonFactory headsetMediaButtonFactory, 161 ProximitySensorManagerFactory proximitySensorManagerFactory, 162 InCallWakeLockControllerFactory inCallWakeLockControllerFactory) { 163 mContext = context; 164 mLock = lock; 165 mContactsAsyncHelper = contactsAsyncHelper; 166 mCallerInfoAsyncQueryFactory = callerInfoAsyncQueryFactory; 167 mPhoneAccountRegistrar = phoneAccountRegistrar; 168 mMissedCallNotifier = missedCallNotifier; 169 StatusBarNotifier statusBarNotifier = new StatusBarNotifier(context, this); 170 mWiredHeadsetManager = new WiredHeadsetManager(context); 171 mDockManager = new DockManager(context); 172 mCallAudioManager = new CallAudioManager( 173 context, mLock, statusBarNotifier, mWiredHeadsetManager, mDockManager, this); 174 InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager, lock); 175 mRinger = new Ringer(mCallAudioManager, this, playerFactory, context); 176 mHeadsetMediaButton = headsetMediaButtonFactory.create(context, this, mLock); 177 mTtyManager = new TtyManager(context, mWiredHeadsetManager); 178 mProximitySensorManager = proximitySensorManagerFactory.create(context, this); 179 mPhoneStateBroadcaster = new PhoneStateBroadcaster(this); 180 mCallLogManager = new CallLogManager(context); 181 mInCallController = new InCallController(context, mLock, this); 182 mDtmfLocalTonePlayer = new DtmfLocalTonePlayer(context); 183 mConnectionServiceRepository = 184 new ConnectionServiceRepository(mPhoneAccountRegistrar, mContext, mLock, this); 185 mInCallWakeLockController = inCallWakeLockControllerFactory.create(context, this); 186 187 mListeners.add(statusBarNotifier); 188 mListeners.add(mCallLogManager); 189 mListeners.add(mPhoneStateBroadcaster); 190 mListeners.add(mInCallController); 191 mListeners.add(mRinger); 192 mListeners.add(new RingbackPlayer(this, playerFactory)); 193 mListeners.add(new InCallToneMonitor(playerFactory, this)); 194 mListeners.add(mCallAudioManager); 195 mListeners.add(missedCallNotifier); 196 mListeners.add(mDtmfLocalTonePlayer); 197 mListeners.add(mHeadsetMediaButton); 198 mListeners.add(mProximitySensorManager); 199 200 mMissedCallNotifier.updateOnStartup( 201 mLock, this, mContactsAsyncHelper, mCallerInfoAsyncQueryFactory); 202 } 203 204 public void setRespondViaSmsManager(RespondViaSmsManager respondViaSmsManager) { 205 if (mRespondViaSmsManager != null) { 206 mListeners.remove(mRespondViaSmsManager); 207 } 208 mRespondViaSmsManager = respondViaSmsManager; 209 mListeners.add(respondViaSmsManager); 210 } 211 212 public RespondViaSmsManager getRespondViaSmsManager() { 213 return mRespondViaSmsManager; 214 } 215 216 @Override 217 public void onSuccessfulOutgoingCall(Call call, int callState) { 218 Log.v(this, "onSuccessfulOutgoingCall, %s", call); 219 220 setCallState(call, callState, "successful outgoing call"); 221 if (!mCalls.contains(call)) { 222 // Call was not added previously in startOutgoingCall due to it being a potential MMI 223 // code, so add it now. 224 addCall(call); 225 } 226 227 // The call's ConnectionService has been updated. 228 for (CallsManagerListener listener : mListeners) { 229 listener.onConnectionServiceChanged(call, null, call.getConnectionService()); 230 } 231 232 markCallAsDialing(call); 233 } 234 235 @Override 236 public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) { 237 Log.v(this, "onFailedOutgoingCall, call: %s", call); 238 239 markCallAsRemoved(call); 240 } 241 242 @Override 243 public void onSuccessfulIncomingCall(Call incomingCall) { 244 Log.d(this, "onSuccessfulIncomingCall"); 245 setCallState(incomingCall, CallState.RINGING, "successful incoming call"); 246 247 if (hasMaximumRingingCalls()) { 248 incomingCall.reject(false, null); 249 // since the call was not added to the list of calls, we have to call the missed 250 // call notifier and the call logger manually. 251 mMissedCallNotifier.showMissedCallNotification(incomingCall); 252 mCallLogManager.logCall(incomingCall, Calls.MISSED_TYPE); 253 } else { 254 addCall(incomingCall); 255 } 256 } 257 258 @Override 259 public void onFailedIncomingCall(Call call) { 260 setCallState(call, CallState.DISCONNECTED, "failed incoming call"); 261 call.removeListener(this); 262 } 263 264 @Override 265 public void onSuccessfulUnknownCall(Call call, int callState) { 266 setCallState(call, callState, "successful unknown call"); 267 Log.i(this, "onSuccessfulUnknownCall for call %s", call); 268 addCall(call); 269 } 270 271 @Override 272 public void onFailedUnknownCall(Call call) { 273 Log.i(this, "onFailedUnknownCall for call %s", call); 274 setCallState(call, CallState.DISCONNECTED, "failed unknown call"); 275 call.removeListener(this); 276 } 277 278 @Override 279 public void onRingbackRequested(Call call, boolean ringback) { 280 for (CallsManagerListener listener : mListeners) { 281 listener.onRingbackRequested(call, ringback); 282 } 283 } 284 285 @Override 286 public void onPostDialWait(Call call, String remaining) { 287 mInCallController.onPostDialWait(call, remaining); 288 } 289 290 @Override 291 public void onPostDialChar(final Call call, char nextChar) { 292 if (PhoneNumberUtils.is12Key(nextChar)) { 293 // Play tone if it is one of the dialpad digits, canceling out the previously queued 294 // up stopTone runnable since playing a new tone automatically stops the previous tone. 295 if (mStopTone != null) { 296 mHandler.removeCallbacks(mStopTone); 297 } 298 299 mDtmfLocalTonePlayer.playTone(call, nextChar); 300 301 // TODO: Create a LockedRunnable class that does the synchronization automatically. 302 mStopTone = new Runnable() { 303 @Override 304 public void run() { 305 synchronized (mLock) { 306 // Set a timeout to stop the tone in case there isn't another tone to follow. 307 mDtmfLocalTonePlayer.stopTone(call); 308 } 309 } 310 }; 311 mHandler.postDelayed( 312 mStopTone, 313 Timeouts.getDelayBetweenDtmfTonesMillis(mContext.getContentResolver())); 314 } else if (nextChar == 0 || nextChar == TelecomManager.DTMF_CHARACTER_WAIT || 315 nextChar == TelecomManager.DTMF_CHARACTER_PAUSE) { 316 // Stop the tone if a tone is playing, removing any other stopTone callbacks since 317 // the previous tone is being stopped anyway. 318 if (mStopTone != null) { 319 mHandler.removeCallbacks(mStopTone); 320 } 321 mDtmfLocalTonePlayer.stopTone(call); 322 } else { 323 Log.w(this, "onPostDialChar: invalid value %d", nextChar); 324 } 325 } 326 327 @Override 328 public void onParentChanged(Call call) { 329 // parent-child relationship affects which call should be foreground, so do an update. 330 updateCallsManagerState(); 331 for (CallsManagerListener listener : mListeners) { 332 listener.onIsConferencedChanged(call); 333 } 334 } 335 336 @Override 337 public void onChildrenChanged(Call call) { 338 // parent-child relationship affects which call should be foreground, so do an update. 339 updateCallsManagerState(); 340 for (CallsManagerListener listener : mListeners) { 341 listener.onIsConferencedChanged(call); 342 } 343 } 344 345 @Override 346 public void onIsVoipAudioModeChanged(Call call) { 347 for (CallsManagerListener listener : mListeners) { 348 listener.onIsVoipAudioModeChanged(call); 349 } 350 } 351 352 @Override 353 public void onVideoStateChanged(Call call) { 354 for (CallsManagerListener listener : mListeners) { 355 listener.onVideoStateChanged(call); 356 } 357 } 358 359 @Override 360 public boolean onCanceledViaNewOutgoingCallBroadcast(final Call call) { 361 mPendingCallsToDisconnect.add(call); 362 mHandler.postDelayed(new Runnable() { 363 @Override 364 public void run() { 365 synchronized (mLock) { 366 if (mPendingCallsToDisconnect.remove(call)) { 367 Log.i(this, "Delayed disconnection of call: %s", call); 368 call.disconnect(); 369 } 370 } 371 } 372 }, Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver())); 373 374 return true; 375 } 376 377 /** 378 * Handles changes to the {@link Connection.VideoProvider} for a call. Adds the 379 * {@link CallsManager} as a listener for the {@link VideoProviderProxy} which is created 380 * in {@link Call#setVideoProvider(IVideoProvider)}. This allows the {@link CallsManager} to 381 * respond to callbacks from the {@link VideoProviderProxy}. 382 * 383 * @param call The call. 384 */ 385 @Override 386 public void onVideoCallProviderChanged(Call call) { 387 VideoProviderProxy videoProviderProxy = call.getVideoProviderProxy(); 388 389 if (videoProviderProxy == null) { 390 return; 391 } 392 393 videoProviderProxy.addListener(this); 394 } 395 396 /** 397 * Handles session modification requests received via the {@link TelecomVideoCallCallback} for 398 * a call. Notifies listeners of the {@link CallsManager.CallsManagerListener} of the session 399 * modification request. 400 * 401 * @param call The call. 402 * @param videoProfile The {@link VideoProfile}. 403 */ 404 @Override 405 public void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile) { 406 int videoState = videoProfile != null ? videoProfile.getVideoState() : 407 VideoProfile.STATE_AUDIO_ONLY; 408 Log.v(TAG, "onSessionModifyRequestReceived : videoProfile = " + VideoProfile 409 .videoStateToString(videoState)); 410 411 for (CallsManagerListener listener : mListeners) { 412 listener.onSessionModifyRequestReceived(call, videoProfile); 413 } 414 } 415 416 Collection<Call> getCalls() { 417 return Collections.unmodifiableCollection(mCalls); 418 } 419 420 Call getForegroundCall() { 421 return mForegroundCall; 422 } 423 424 Ringer getRinger() { 425 return mRinger; 426 } 427 428 InCallController getInCallController() { 429 return mInCallController; 430 } 431 432 boolean hasEmergencyCall() { 433 for (Call call : mCalls) { 434 if (call.isEmergencyCall()) { 435 return true; 436 } 437 } 438 return false; 439 } 440 441 boolean hasVideoCall() { 442 for (Call call : mCalls) { 443 if (VideoProfile.isVideo(call.getVideoState())) { 444 return true; 445 } 446 } 447 return false; 448 } 449 450 CallAudioState getAudioState() { 451 return mCallAudioManager.getCallAudioState(); 452 } 453 454 boolean isTtySupported() { 455 return mTtyManager.isTtySupported(); 456 } 457 458 int getCurrentTtyMode() { 459 return mTtyManager.getCurrentTtyMode(); 460 } 461 462 void addListener(CallsManagerListener listener) { 463 mListeners.add(listener); 464 } 465 466 void removeListener(CallsManagerListener listener) { 467 mListeners.remove(listener); 468 } 469 470 /** 471 * Starts the process to attach the call to a connection service. 472 * 473 * @param phoneAccountHandle The phone account which contains the component name of the 474 * connection service to use for this call. 475 * @param extras The optional extras Bundle passed with the intent used for the incoming call. 476 */ 477 void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras) { 478 Log.d(this, "processIncomingCallIntent"); 479 Uri handle = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS); 480 if (handle == null) { 481 // Required for backwards compatibility 482 handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER); 483 } 484 Call call = new Call( 485 mContext, 486 this, 487 mLock, 488 mConnectionServiceRepository, 489 mContactsAsyncHelper, 490 mCallerInfoAsyncQueryFactory, 491 handle, 492 null /* gatewayInfo */, 493 null /* connectionManagerPhoneAccount */, 494 phoneAccountHandle, 495 true /* isIncoming */, 496 false /* isConference */); 497 498 call.setIntentExtras(extras); 499 // TODO: Move this to be a part of addCall() 500 call.addListener(this); 501 call.startCreateConnection(mPhoneAccountRegistrar); 502 } 503 504 void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) { 505 Uri handle = extras.getParcelable(TelecomManager.EXTRA_UNKNOWN_CALL_HANDLE); 506 Log.i(this, "addNewUnknownCall with handle: %s", Log.pii(handle)); 507 Call call = new Call( 508 mContext, 509 this, 510 mLock, 511 mConnectionServiceRepository, 512 mContactsAsyncHelper, 513 mCallerInfoAsyncQueryFactory, 514 handle, 515 null /* gatewayInfo */, 516 null /* connectionManagerPhoneAccount */, 517 phoneAccountHandle, 518 // Use onCreateIncomingConnection in TelephonyConnectionService, so that we attach 519 // to the existing connection instead of trying to create a new one. 520 true /* isIncoming */, 521 false /* isConference */); 522 call.setIsUnknown(true); 523 call.setIntentExtras(extras); 524 call.addListener(this); 525 call.startCreateConnection(mPhoneAccountRegistrar); 526 } 527 528 private boolean areHandlesEqual(Uri handle1, Uri handle2) { 529 if (handle1 == null || handle2 == null) { 530 return handle1 == handle2; 531 } 532 533 if (!TextUtils.equals(handle1.getScheme(), handle2.getScheme())) { 534 return false; 535 } 536 537 final String number1 = PhoneNumberUtils.normalizeNumber(handle1.getSchemeSpecificPart()); 538 final String number2 = PhoneNumberUtils.normalizeNumber(handle2.getSchemeSpecificPart()); 539 return TextUtils.equals(number1, number2); 540 } 541 542 private Call getNewOutgoingCall(Uri handle) { 543 // First check to see if we can reuse any of the calls that are waiting to disconnect. 544 // See {@link Call#abort} and {@link #onCanceledViaNewOutgoingCall} for more information. 545 Call reusedCall = null; 546 for (Call pendingCall : mPendingCallsToDisconnect) { 547 if (reusedCall == null && areHandlesEqual(pendingCall.getHandle(), handle)) { 548 mPendingCallsToDisconnect.remove(pendingCall); 549 Log.i(this, "Reusing disconnected call %s", pendingCall); 550 reusedCall = pendingCall; 551 } else { 552 Log.i(this, "Not reusing disconnected call %s", pendingCall); 553 pendingCall.disconnect(); 554 } 555 } 556 if (reusedCall != null) { 557 return reusedCall; 558 } 559 560 // Create a call with original handle. The handle may be changed when the call is attached 561 // to a connection service, but in most cases will remain the same. 562 return new Call( 563 mContext, 564 this, 565 mLock, 566 mConnectionServiceRepository, 567 mContactsAsyncHelper, 568 mCallerInfoAsyncQueryFactory, 569 handle, 570 null /* gatewayInfo */, 571 null /* connectionManagerPhoneAccount */, 572 null /* phoneAccountHandle */, 573 false /* isIncoming */, 574 false /* isConference */); 575 } 576 577 /** 578 * Kicks off the first steps to creating an outgoing call so that InCallUI can launch. 579 * 580 * @param handle Handle to connect the call with. 581 * @param phoneAccountHandle The phone account which contains the component name of the 582 * connection service to use for this call. 583 * @param extras The optional extras Bundle passed with the intent used for the incoming call. 584 */ 585 Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras) { 586 Call call = getNewOutgoingCall(handle); 587 588 List<PhoneAccountHandle> accounts = 589 mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false); 590 591 Log.v(this, "startOutgoingCall found accounts = " + accounts); 592 593 if (mForegroundCall != null && mForegroundCall.getTargetPhoneAccount() != null) { 594 // If there is an ongoing call, use the same phone account to place this new call. 595 phoneAccountHandle = mForegroundCall.getTargetPhoneAccount(); 596 } 597 598 // Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call 599 // as if a phoneAccount was not specified (does the default behavior instead). 600 // Note: We will not attempt to dial with a requested phoneAccount if it is disabled. 601 if (phoneAccountHandle != null) { 602 if (!accounts.contains(phoneAccountHandle)) { 603 phoneAccountHandle = null; 604 } 605 } 606 607 if (phoneAccountHandle == null) { 608 // No preset account, check if default exists that supports the URI scheme for the 609 // handle. 610 phoneAccountHandle = 611 mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(handle.getScheme()); 612 } 613 614 call.setTargetPhoneAccount(phoneAccountHandle); 615 616 boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext, 617 call.getHandle()); 618 boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle); 619 620 // Do not support any more live calls. Our options are to move a call to hold, disconnect 621 // a call, or cancel this call altogether. 622 if (!isPotentialInCallMMICode && !makeRoomForOutgoingCall(call, isEmergencyCall)) { 623 // just cancel at this point. 624 Log.i(this, "No remaining room for outgoing call: %s", call); 625 if (mCalls.contains(call)) { 626 // This call can already exist if it is a reused call, 627 // See {@link #getNewOutgoingCall}. 628 call.disconnect(); 629 } 630 return null; 631 } 632 633 boolean needsAccountSelection = phoneAccountHandle == null && accounts.size() > 1 && 634 !isEmergencyCall; 635 636 if (needsAccountSelection) { 637 // This is the state where the user is expected to select an account 638 call.setState(CallState.SELECT_PHONE_ACCOUNT, "needs account selection"); 639 // Create our own instance to modify (since extras may be Bundle.EMPTY) 640 extras = new Bundle(extras); 641 extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS, accounts); 642 } else { 643 call.setState( 644 CallState.CONNECTING, 645 phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString()); 646 } 647 648 call.setIntentExtras(extras); 649 650 // Do not add the call if it is a potential MMI code. 651 if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) { 652 call.addListener(this); 653 } else if (!mCalls.contains(call)) { 654 // We check if mCalls already contains the call because we could potentially be reusing 655 // a call which was previously added (See {@link #getNewOutgoingCall}). 656 addCall(call); 657 } 658 659 return call; 660 } 661 662 /** 663 * Attempts to issue/connect the specified call. 664 * 665 * @param handle Handle to connect the call with. 666 * @param gatewayInfo Optional gateway information that can be used to route the call to the 667 * actual dialed handle via a gateway provider. May be null. 668 * @param speakerphoneOn Whether or not to turn the speakerphone on once the call connects. 669 * @param videoState The desired video state for the outgoing call. 670 */ 671 void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn, 672 int videoState) { 673 if (call == null) { 674 // don't do anything if the call no longer exists 675 Log.i(this, "Canceling unknown call."); 676 return; 677 } 678 679 final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress(); 680 681 if (gatewayInfo == null) { 682 Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle)); 683 } else { 684 Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s", 685 Log.pii(uriHandle), Log.pii(handle)); 686 } 687 688 call.setHandle(uriHandle); 689 call.setGatewayInfo(gatewayInfo); 690 call.setVideoState(videoState); 691 692 if (speakerphoneOn) { 693 Log.i(this, "%s Starting with speakerphone as requested", call); 694 } else { 695 Log.i(this, "%s Starting with speakerphone because car is docked.", call); 696 } 697 call.setStartWithSpeakerphoneOn(speakerphoneOn || mDockManager.isDocked()); 698 699 boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext, 700 call.getHandle()); 701 if (isEmergencyCall) { 702 // Emergency -- CreateConnectionProcessor will choose accounts automatically 703 call.setTargetPhoneAccount(null); 704 } 705 706 if (call.getTargetPhoneAccount() != null || isEmergencyCall) { 707 // If the account has been set, proceed to place the outgoing call. 708 // Otherwise the connection will be initiated when the account is set by the user. 709 call.startCreateConnection(mPhoneAccountRegistrar); 710 } 711 } 712 713 /** 714 * Attempts to start a conference call for the specified call. 715 * 716 * @param call The call to conference. 717 * @param otherCall The other call to conference with. 718 */ 719 void conference(Call call, Call otherCall) { 720 call.conferenceWith(otherCall); 721 } 722 723 /** 724 * Instructs Telecom to answer the specified call. Intended to be invoked by the in-call 725 * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by 726 * the user opting to answer said call. 727 * 728 * @param call The call to answer. 729 * @param videoState The video state in which to answer the call. 730 */ 731 void answerCall(Call call, int videoState) { 732 if (!mCalls.contains(call)) { 733 Log.i(this, "Request to answer a non-existent call %s", call); 734 } else { 735 // If the foreground call is not the ringing call and it is currently isActive() or 736 // STATE_DIALING, put it on hold before answering the call. 737 if (mForegroundCall != null && mForegroundCall != call && 738 (mForegroundCall.isActive() || 739 mForegroundCall.getState() == CallState.DIALING)) { 740 if (0 == (mForegroundCall.getConnectionCapabilities() 741 & Connection.CAPABILITY_HOLD)) { 742 // This call does not support hold. If it is from a different connection 743 // service, then disconnect it, otherwise allow the connection service to 744 // figure out the right states. 745 if (mForegroundCall.getConnectionService() != call.getConnectionService()) { 746 mForegroundCall.disconnect(); 747 } 748 } else { 749 Call heldCall = getHeldCall(); 750 if (heldCall != null) { 751 Log.v(this, "Disconnecting held call %s before holding active call.", 752 heldCall); 753 heldCall.disconnect(); 754 } 755 756 Log.v(this, "Holding active/dialing call %s before answering incoming call %s.", 757 mForegroundCall, call); 758 mForegroundCall.hold(); 759 } 760 // TODO: Wait until we get confirmation of the active call being 761 // on-hold before answering the new call. 762 // TODO: Import logic from CallManager.acceptCall() 763 } 764 765 for (CallsManagerListener listener : mListeners) { 766 listener.onIncomingCallAnswered(call); 767 } 768 769 // We do not update the UI until we get confirmation of the answer() through 770 // {@link #markCallAsActive}. 771 call.answer(videoState); 772 if (VideoProfile.isVideo(videoState) && 773 !mWiredHeadsetManager.isPluggedIn() && 774 !mCallAudioManager.isBluetoothDeviceAvailable() && 775 isSpeakerEnabledForVideoCalls()) { 776 call.setStartWithSpeakerphoneOn(true); 777 } 778 } 779 } 780 781 private static boolean isSpeakerEnabledForVideoCalls() { 782 return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT, 783 PhoneConstants.AUDIO_OUTPUT_DEFAULT) == 784 PhoneConstants.AUDIO_OUTPUT_ENABLE_SPEAKER); 785 } 786 787 /** 788 * Instructs Telecom to reject the specified call. Intended to be invoked by the in-call 789 * app through {@link InCallAdapter} after Telecom notifies it of an incoming call followed by 790 * the user opting to reject said call. 791 */ 792 void rejectCall(Call call, boolean rejectWithMessage, String textMessage) { 793 if (!mCalls.contains(call)) { 794 Log.i(this, "Request to reject a non-existent call %s", call); 795 } else { 796 for (CallsManagerListener listener : mListeners) { 797 listener.onIncomingCallRejected(call, rejectWithMessage, textMessage); 798 } 799 call.reject(rejectWithMessage, textMessage); 800 } 801 } 802 803 /** 804 * Instructs Telecom to play the specified DTMF tone within the specified call. 805 * 806 * @param digit The DTMF digit to play. 807 */ 808 void playDtmfTone(Call call, char digit) { 809 if (!mCalls.contains(call)) { 810 Log.i(this, "Request to play DTMF in a non-existent call %s", call); 811 } else { 812 call.playDtmfTone(digit); 813 mDtmfLocalTonePlayer.playTone(call, digit); 814 } 815 } 816 817 /** 818 * Instructs Telecom to stop the currently playing DTMF tone, if any. 819 */ 820 void stopDtmfTone(Call call) { 821 if (!mCalls.contains(call)) { 822 Log.i(this, "Request to stop DTMF in a non-existent call %s", call); 823 } else { 824 call.stopDtmfTone(); 825 mDtmfLocalTonePlayer.stopTone(call); 826 } 827 } 828 829 /** 830 * Instructs Telecom to continue (or not) the current post-dial DTMF string, if any. 831 */ 832 void postDialContinue(Call call, boolean proceed) { 833 if (!mCalls.contains(call)) { 834 Log.i(this, "Request to continue post-dial string in a non-existent call %s", call); 835 } else { 836 call.postDialContinue(proceed); 837 } 838 } 839 840 /** 841 * Instructs Telecom to disconnect the specified call. Intended to be invoked by the 842 * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by 843 * the user hitting the end-call button. 844 */ 845 void disconnectCall(Call call) { 846 Log.v(this, "disconnectCall %s", call); 847 848 if (!mCalls.contains(call)) { 849 Log.w(this, "Unknown call (%s) asked to disconnect", call); 850 } else { 851 mLocallyDisconnectingCalls.add(call); 852 call.disconnect(); 853 } 854 } 855 856 /** 857 * Instructs Telecom to disconnect all calls. 858 */ 859 void disconnectAllCalls() { 860 Log.v(this, "disconnectAllCalls"); 861 862 for (Call call : mCalls) { 863 disconnectCall(call); 864 } 865 } 866 867 868 /** 869 * Instructs Telecom to put the specified call on hold. Intended to be invoked by the 870 * in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered by 871 * the user hitting the hold button during an active call. 872 */ 873 void holdCall(Call call) { 874 if (!mCalls.contains(call)) { 875 Log.w(this, "Unknown call (%s) asked to be put on hold", call); 876 } else { 877 Log.d(this, "Putting call on hold: (%s)", call); 878 call.hold(); 879 } 880 } 881 882 /** 883 * Instructs Telecom to release the specified call from hold. Intended to be invoked by 884 * the in-call app through {@link InCallAdapter} for an ongoing call. This is usually triggered 885 * by the user hitting the hold button during a held call. 886 */ 887 void unholdCall(Call call) { 888 if (!mCalls.contains(call)) { 889 Log.w(this, "Unknown call (%s) asked to be removed from hold", call); 890 } else { 891 Log.d(this, "unholding call: (%s)", call); 892 for (Call c : mCalls) { 893 // Only attempt to hold parent calls and not the individual children. 894 if (c != null && c.isAlive() && c != call && c.getParentCall() == null) { 895 c.hold(); 896 } 897 } 898 call.unhold(); 899 } 900 } 901 902 /** Called by the in-call UI to change the mute state. */ 903 void mute(boolean shouldMute) { 904 mCallAudioManager.mute(shouldMute); 905 } 906 907 /** 908 * Called by the in-call UI to change the audio route, for example to change from earpiece to 909 * speaker phone. 910 */ 911 void setAudioRoute(int route) { 912 mCallAudioManager.setAudioRoute(route); 913 } 914 915 /** Called by the in-call UI to turn the proximity sensor on. */ 916 void turnOnProximitySensor() { 917 mProximitySensorManager.turnOn(); 918 } 919 920 /** 921 * Called by the in-call UI to turn the proximity sensor off. 922 * @param screenOnImmediately If true, the screen will be turned on immediately. Otherwise, 923 * the screen will be kept off until the proximity sensor goes negative. 924 */ 925 void turnOffProximitySensor(boolean screenOnImmediately) { 926 mProximitySensorManager.turnOff(screenOnImmediately); 927 } 928 929 void phoneAccountSelected(Call call, PhoneAccountHandle account, boolean setDefault) { 930 if (!mCalls.contains(call)) { 931 Log.i(this, "Attempted to add account to unknown call %s", call); 932 } else { 933 // TODO: There is an odd race condition here. Since NewOutgoingCallIntentBroadcaster and 934 // the SELECT_PHONE_ACCOUNT sequence run in parallel, if the user selects an account before the 935 // NEW_OUTGOING_CALL sequence finishes, we'll start the call immediately without 936 // respecting a rewritten number or a canceled number. This is unlikely since 937 // NEW_OUTGOING_CALL sequence, in practice, runs a lot faster than the user selecting 938 // a phone account from the in-call UI. 939 call.setTargetPhoneAccount(account); 940 941 // Note: emergency calls never go through account selection dialog so they never 942 // arrive here. 943 if (makeRoomForOutgoingCall(call, false /* isEmergencyCall */)) { 944 call.startCreateConnection(mPhoneAccountRegistrar); 945 } else { 946 call.disconnect(); 947 } 948 949 if (setDefault) { 950 mPhoneAccountRegistrar.setUserSelectedOutgoingPhoneAccount(account); 951 } 952 } 953 } 954 955 /** Called when the audio state changes. */ 956 void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState) { 957 Log.v(this, "onAudioStateChanged, audioState: %s -> %s", oldAudioState, newAudioState); 958 for (CallsManagerListener listener : mListeners) { 959 listener.onCallAudioStateChanged(oldAudioState, newAudioState); 960 } 961 } 962 963 void markCallAsRinging(Call call) { 964 setCallState(call, CallState.RINGING, "ringing set explicitly"); 965 } 966 967 void markCallAsDialing(Call call) { 968 setCallState(call, CallState.DIALING, "dialing set explicitly"); 969 maybeMoveToSpeakerPhone(call); 970 } 971 972 void markCallAsActive(Call call) { 973 setCallState(call, CallState.ACTIVE, "active set explicitly"); 974 maybeMoveToSpeakerPhone(call); 975 } 976 977 void markCallAsOnHold(Call call) { 978 setCallState(call, CallState.ON_HOLD, "on-hold set explicitly"); 979 } 980 981 /** 982 * Marks the specified call as STATE_DISCONNECTED and notifies the in-call app. If this was the 983 * last live call, then also disconnect from the in-call controller. 984 * 985 * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}. 986 */ 987 void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) { 988 call.setDisconnectCause(disconnectCause); 989 setCallState(call, CallState.DISCONNECTED, "disconnected set explicitly"); 990 } 991 992 /** 993 * Removes an existing disconnected call, and notifies the in-call app. 994 */ 995 void markCallAsRemoved(Call call) { 996 removeCall(call); 997 if (mLocallyDisconnectingCalls.contains(call)) { 998 mLocallyDisconnectingCalls.remove(call); 999 if (mForegroundCall != null && mForegroundCall.getState() == CallState.ON_HOLD) { 1000 mForegroundCall.unhold(); 1001 } 1002 } 1003 } 1004 1005 /** 1006 * Cleans up any calls currently associated with the specified connection service when the 1007 * service binder disconnects unexpectedly. 1008 * 1009 * @param service The connection service that disconnected. 1010 */ 1011 void handleConnectionServiceDeath(ConnectionServiceWrapper service) { 1012 if (service != null) { 1013 for (Call call : mCalls) { 1014 if (call.getConnectionService() == service) { 1015 if (call.getState() != CallState.DISCONNECTED) { 1016 markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR)); 1017 } 1018 markCallAsRemoved(call); 1019 } 1020 } 1021 } 1022 } 1023 1024 boolean hasAnyCalls() { 1025 return !mCalls.isEmpty(); 1026 } 1027 1028 boolean hasActiveOrHoldingCall() { 1029 return getFirstCallWithState(CallState.ACTIVE, CallState.ON_HOLD) != null; 1030 } 1031 1032 boolean hasRingingCall() { 1033 return getFirstCallWithState(CallState.RINGING) != null; 1034 } 1035 1036 boolean onMediaButton(int type) { 1037 if (hasAnyCalls()) { 1038 if (HeadsetMediaButton.SHORT_PRESS == type) { 1039 Call ringingCall = getFirstCallWithState(CallState.RINGING); 1040 if (ringingCall == null) { 1041 mCallAudioManager.toggleMute(); 1042 return true; 1043 } else { 1044 ringingCall.answer(ringingCall.getVideoState()); 1045 return true; 1046 } 1047 } else if (HeadsetMediaButton.LONG_PRESS == type) { 1048 Log.d(this, "handleHeadsetHook: longpress -> hangup"); 1049 Call callToHangup = getFirstCallWithState( 1050 CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD); 1051 if (callToHangup != null) { 1052 callToHangup.disconnect(); 1053 return true; 1054 } 1055 } 1056 } 1057 return false; 1058 } 1059 1060 /** 1061 * Returns true if telecom supports adding another top-level call. 1062 */ 1063 boolean canAddCall() { 1064 if (getFirstCallWithState(OUTGOING_CALL_STATES) != null) { 1065 return false; 1066 } 1067 1068 int count = 0; 1069 for (Call call : mCalls) { 1070 if (call.isEmergencyCall()) { 1071 // We never support add call if one of the calls is an emergency call. 1072 return false; 1073 } else if (!call.getChildCalls().isEmpty() && !call.can(Connection.CAPABILITY_HOLD)) { 1074 // This is to deal with CDMA conference calls. CDMA conference calls do not 1075 // allow the addition of another call when it is already in a 3 way conference. 1076 // So, we detect that it is a CDMA conference call by checking if the call has 1077 // some children and it does not support the CAPABILILTY_HOLD 1078 // TODO: This maybe cleaner if the lower layers can explicitly signal to telecom 1079 // about this limitation (b/22880180). 1080 return false; 1081 } else if (call.getParentCall() == null) { 1082 count++; 1083 } 1084 1085 // We do not check states for canAddCall. We treat disconnected calls the same 1086 // and wait until they are removed instead. If we didn't count disconnected calls, 1087 // we could put InCallServices into a state where they are showing two calls but 1088 // also support add-call. Technically it's right, but overall looks better (UI-wise) 1089 // and acts better if we wait until the call is removed. 1090 if (count >= MAXIMUM_TOP_LEVEL_CALLS) { 1091 return false; 1092 } 1093 } 1094 return true; 1095 } 1096 1097 @VisibleForTesting 1098 public Call getRingingCall() { 1099 return getFirstCallWithState(CallState.RINGING); 1100 } 1101 1102 Call getActiveCall() { 1103 return getFirstCallWithState(CallState.ACTIVE); 1104 } 1105 1106 Call getDialingCall() { 1107 return getFirstCallWithState(CallState.DIALING); 1108 } 1109 1110 Call getHeldCall() { 1111 return getFirstCallWithState(CallState.ON_HOLD); 1112 } 1113 1114 int getNumHeldCalls() { 1115 int count = 0; 1116 for (Call call : mCalls) { 1117 if (call.getParentCall() == null && call.getState() == CallState.ON_HOLD) { 1118 count++; 1119 } 1120 } 1121 return count; 1122 } 1123 1124 Call getFirstCallWithState(int... states) { 1125 return getFirstCallWithState(null, states); 1126 } 1127 1128 /** 1129 * Returns the first call that it finds with the given states. The states are treated as having 1130 * priority order so that any call with the first state will be returned before any call with 1131 * states listed later in the parameter list. 1132 * 1133 * @param callToSkip Call that this method should skip while searching 1134 */ 1135 Call getFirstCallWithState(Call callToSkip, int... states) { 1136 for (int currentState : states) { 1137 // check the foreground first 1138 if (mForegroundCall != null && mForegroundCall.getState() == currentState) { 1139 return mForegroundCall; 1140 } 1141 1142 for (Call call : mCalls) { 1143 if (Objects.equals(callToSkip, call)) { 1144 continue; 1145 } 1146 1147 // Only operate on top-level calls 1148 if (call.getParentCall() != null) { 1149 continue; 1150 } 1151 1152 if (currentState == call.getState()) { 1153 return call; 1154 } 1155 } 1156 } 1157 return null; 1158 } 1159 1160 Call createConferenceCall( 1161 PhoneAccountHandle phoneAccount, 1162 ParcelableConference parcelableConference) { 1163 1164 // If the parceled conference specifies a connect time, use it; otherwise default to 0, 1165 // which is the default value for new Calls. 1166 long connectTime = 1167 parcelableConference.getConnectTimeMillis() == 1168 Conference.CONNECT_TIME_NOT_SPECIFIED ? 0 : 1169 parcelableConference.getConnectTimeMillis(); 1170 1171 Call call = new Call( 1172 mContext, 1173 this, 1174 mLock, 1175 mConnectionServiceRepository, 1176 mContactsAsyncHelper, 1177 mCallerInfoAsyncQueryFactory, 1178 null /* handle */, 1179 null /* gatewayInfo */, 1180 null /* connectionManagerPhoneAccount */, 1181 phoneAccount, 1182 false /* isIncoming */, 1183 true /* isConference */, 1184 connectTime); 1185 1186 setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()), 1187 "new conference call"); 1188 call.setConnectionCapabilities(parcelableConference.getConnectionCapabilities()); 1189 call.setVideoState(parcelableConference.getVideoState()); 1190 call.setVideoProvider(parcelableConference.getVideoProvider()); 1191 call.setStatusHints(parcelableConference.getStatusHints()); 1192 call.setExtras(parcelableConference.getExtras()); 1193 1194 // TODO: Move this to be a part of addCall() 1195 call.addListener(this); 1196 addCall(call); 1197 return call; 1198 } 1199 1200 /** 1201 * @return the call state currently tracked by {@link PhoneStateBroadcaster} 1202 */ 1203 int getCallState() { 1204 return mPhoneStateBroadcaster.getCallState(); 1205 } 1206 1207 /** 1208 * Retrieves the {@link PhoneAccountRegistrar}. 1209 * 1210 * @return The {@link PhoneAccountRegistrar}. 1211 */ 1212 PhoneAccountRegistrar getPhoneAccountRegistrar() { 1213 return mPhoneAccountRegistrar; 1214 } 1215 1216 /** 1217 * Retrieves the {@link MissedCallNotifier} 1218 * @return The {@link MissedCallNotifier}. 1219 */ 1220 MissedCallNotifier getMissedCallNotifier() { 1221 return mMissedCallNotifier; 1222 } 1223 1224 /** 1225 * Adds the specified call to the main list of live calls. 1226 * 1227 * @param call The call to add. 1228 */ 1229 private void addCall(Call call) { 1230 Trace.beginSection("addCall"); 1231 Log.v(this, "addCall(%s)", call); 1232 call.addListener(this); 1233 mCalls.add(call); 1234 1235 // TODO: Update mForegroundCall prior to invoking 1236 // onCallAdded for calls which immediately take the foreground (like the first call). 1237 for (CallsManagerListener listener : mListeners) { 1238 if (Log.SYSTRACE_DEBUG) { 1239 Trace.beginSection(listener.getClass().toString() + " addCall"); 1240 } 1241 listener.onCallAdded(call); 1242 if (Log.SYSTRACE_DEBUG) { 1243 Trace.endSection(); 1244 } 1245 } 1246 updateCallsManagerState(); 1247 Trace.endSection(); 1248 } 1249 1250 private void removeCall(Call call) { 1251 Trace.beginSection("removeCall"); 1252 Log.v(this, "removeCall(%s)", call); 1253 1254 call.setParentCall(null); // need to clean up parent relationship before destroying. 1255 call.removeListener(this); 1256 call.clearConnectionService(); 1257 1258 boolean shouldNotify = false; 1259 if (mCalls.contains(call)) { 1260 mCalls.remove(call); 1261 shouldNotify = true; 1262 } 1263 1264 call.destroy(); 1265 1266 // Only broadcast changes for calls that are being tracked. 1267 if (shouldNotify) { 1268 for (CallsManagerListener listener : mListeners) { 1269 if (Log.SYSTRACE_DEBUG) { 1270 Trace.beginSection(listener.getClass().toString() + " onCallRemoved"); 1271 } 1272 listener.onCallRemoved(call); 1273 if (Log.SYSTRACE_DEBUG) { 1274 Trace.endSection(); 1275 } 1276 } 1277 updateCallsManagerState(); 1278 } 1279 Trace.endSection(); 1280 } 1281 1282 /** 1283 * Sets the specified state on the specified call. 1284 * 1285 * @param call The call. 1286 * @param newState The new state of the call. 1287 */ 1288 private void setCallState(Call call, int newState, String tag) { 1289 if (call == null) { 1290 return; 1291 } 1292 int oldState = call.getState(); 1293 Log.i(this, "setCallState %s -> %s, call: %s", CallState.toString(oldState), 1294 CallState.toString(newState), call); 1295 if (newState != oldState) { 1296 // Unfortunately, in the telephony world the radio is king. So if the call notifies 1297 // us that the call is in a particular state, we allow it even if it doesn't make 1298 // sense (e.g., STATE_ACTIVE -> STATE_RINGING). 1299 // TODO: Consider putting a stop to the above and turning CallState 1300 // into a well-defined state machine. 1301 // TODO: Define expected state transitions here, and log when an 1302 // unexpected transition occurs. 1303 call.setState(newState, tag); 1304 1305 Trace.beginSection("onCallStateChanged"); 1306 // Only broadcast state change for calls that are being tracked. 1307 if (mCalls.contains(call)) { 1308 for (CallsManagerListener listener : mListeners) { 1309 if (Log.SYSTRACE_DEBUG) { 1310 Trace.beginSection(listener.getClass().toString() + " onCallStateChanged"); 1311 } 1312 listener.onCallStateChanged(call, oldState, newState); 1313 if (Log.SYSTRACE_DEBUG) { 1314 Trace.endSection(); 1315 } 1316 } 1317 updateCallsManagerState(); 1318 } 1319 Trace.endSection(); 1320 } 1321 } 1322 1323 /** 1324 * Checks which call should be visible to the user and have audio focus. 1325 */ 1326 private void updateForegroundCall() { 1327 Trace.beginSection("updateForegroundCall"); 1328 Call newForegroundCall = null; 1329 for (Call call : mCalls) { 1330 // TODO: Foreground-ness needs to be explicitly set. No call, regardless 1331 // of its state will be foreground by default and instead the connection service should 1332 // be notified when its calls enter and exit foreground state. Foreground will mean that 1333 // the call should play audio and listen to microphone if it wants. 1334 1335 // Only top-level calls can be in foreground 1336 if (call.getParentCall() != null) { 1337 continue; 1338 } 1339 1340 // Active calls have priority. 1341 if (call.isActive()) { 1342 newForegroundCall = call; 1343 break; 1344 } 1345 1346 if (call.isAlive() || call.getState() == CallState.RINGING) { 1347 newForegroundCall = call; 1348 // Don't break in case there's an active call that has priority. 1349 } 1350 } 1351 1352 if (newForegroundCall != mForegroundCall) { 1353 Log.v(this, "Updating foreground call, %s -> %s.", mForegroundCall, newForegroundCall); 1354 Call oldForegroundCall = mForegroundCall; 1355 mForegroundCall = newForegroundCall; 1356 1357 for (CallsManagerListener listener : mListeners) { 1358 if (Log.SYSTRACE_DEBUG) { 1359 Trace.beginSection(listener.getClass().toString() + " updateForegroundCall"); 1360 } 1361 listener.onForegroundCallChanged(oldForegroundCall, mForegroundCall); 1362 if (Log.SYSTRACE_DEBUG) { 1363 Trace.endSection(); 1364 } 1365 } 1366 } 1367 Trace.endSection(); 1368 } 1369 1370 private void updateCanAddCall() { 1371 boolean newCanAddCall = canAddCall(); 1372 if (newCanAddCall != mCanAddCall) { 1373 mCanAddCall = newCanAddCall; 1374 for (CallsManagerListener listener : mListeners) { 1375 if (Log.SYSTRACE_DEBUG) { 1376 Trace.beginSection(listener.getClass().toString() + " updateCanAddCall"); 1377 } 1378 listener.onCanAddCallChanged(mCanAddCall); 1379 if (Log.SYSTRACE_DEBUG) { 1380 Trace.endSection(); 1381 } 1382 } 1383 } 1384 } 1385 1386 private void updateCallsManagerState() { 1387 updateForegroundCall(); 1388 updateCanAddCall(); 1389 } 1390 1391 private boolean isPotentialMMICode(Uri handle) { 1392 return (handle != null && handle.getSchemeSpecificPart() != null 1393 && handle.getSchemeSpecificPart().contains("#")); 1394 } 1395 1396 /** 1397 * Determines if a dialed number is potentially an In-Call MMI code. In-Call MMI codes are 1398 * MMI codes which can be dialed when one or more calls are in progress. 1399 * <P> 1400 * Checks for numbers formatted similar to the MMI codes defined in: 1401 * {@link com.android.internal.telephony.gsm.GSMPhone#handleInCallMmiCommands(String)} 1402 * and 1403 * {@link com.android.internal.telephony.imsphone.ImsPhone#handleInCallMmiCommands(String)} 1404 * 1405 * @param handle The URI to call. 1406 * @return {@code True} if the URI represents a number which could be an in-call MMI code. 1407 */ 1408 private boolean isPotentialInCallMMICode(Uri handle) { 1409 if (handle != null && handle.getSchemeSpecificPart() != null && 1410 handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) { 1411 1412 String dialedNumber = handle.getSchemeSpecificPart(); 1413 return (dialedNumber.equals("0") || 1414 (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) || 1415 (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) || 1416 dialedNumber.equals("3") || 1417 dialedNumber.equals("4") || 1418 dialedNumber.equals("5")); 1419 } 1420 return false; 1421 } 1422 1423 private int getNumCallsWithState(int... states) { 1424 int count = 0; 1425 for (int state : states) { 1426 for (Call call : mCalls) { 1427 if (call.getParentCall() == null && call.getState() == state) { 1428 count++; 1429 } 1430 } 1431 } 1432 return count; 1433 } 1434 1435 private boolean hasMaximumLiveCalls() { 1436 return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(LIVE_CALL_STATES); 1437 } 1438 1439 private boolean hasMaximumHoldingCalls() { 1440 return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(CallState.ON_HOLD); 1441 } 1442 1443 private boolean hasMaximumRingingCalls() { 1444 return MAXIMUM_RINGING_CALLS <= getNumCallsWithState(CallState.RINGING); 1445 } 1446 1447 private boolean hasMaximumOutgoingCalls() { 1448 return MAXIMUM_OUTGOING_CALLS <= getNumCallsWithState(OUTGOING_CALL_STATES); 1449 } 1450 1451 private boolean makeRoomForOutgoingCall(Call call, boolean isEmergency) { 1452 if (hasMaximumLiveCalls()) { 1453 // NOTE: If the amount of live calls changes beyond 1, this logic will probably 1454 // have to change. 1455 Call liveCall = getFirstCallWithState(call, LIVE_CALL_STATES); 1456 Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " + 1457 liveCall); 1458 1459 if (call == liveCall) { 1460 // If the call is already the foreground call, then we are golden. 1461 // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT 1462 // state since the call was already populated into the list. 1463 return true; 1464 } 1465 1466 if (hasMaximumOutgoingCalls()) { 1467 Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES); 1468 if (isEmergency && !outgoingCall.isEmergencyCall()) { 1469 // Disconnect the current outgoing call if it's not an emergency call. If the 1470 // user tries to make two outgoing calls to different emergency call numbers, 1471 // we will try to connect the first outgoing call. 1472 outgoingCall.disconnect(); 1473 return true; 1474 } 1475 if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) { 1476 // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT} 1477 // state, just disconnect it since the user has explicitly started a new call. 1478 outgoingCall.disconnect(); 1479 return true; 1480 } 1481 return false; 1482 } 1483 1484 if (hasMaximumHoldingCalls()) { 1485 // There is no more room for any more calls, unless it's an emergency. 1486 if (isEmergency) { 1487 // Kill the current active call, this is easier then trying to disconnect a 1488 // holding call and hold an active call. 1489 liveCall.disconnect(); 1490 return true; 1491 } 1492 return false; // No more room! 1493 } 1494 1495 // We have room for at least one more holding call at this point. 1496 1497 // TODO: Remove once b/23035408 has been corrected. 1498 // If the live call is a conference, it will not have a target phone account set. This 1499 // means the check to see if the live call has the same target phone account as the new 1500 // call will not cause us to bail early. As a result, we'll end up holding the 1501 // ongoing conference call. However, the ConnectionService is already doing that. This 1502 // has caused problems with some carriers. As a workaround until b/23035408 is 1503 // corrected, we will try and get the target phone account for one of the conference's 1504 // children and use that instead. 1505 PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount(); 1506 if (liveCallPhoneAccount == null && liveCall.isConference() && 1507 !liveCall.getChildCalls().isEmpty()) { 1508 liveCallPhoneAccount = getFirstChildPhoneAccount(liveCall); 1509 Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " + 1510 liveCallPhoneAccount); 1511 } 1512 1513 // First thing, if we are trying to make a call with the same phone account as the live 1514 // call, then allow it so that the connection service can make its own decision about 1515 // how to handle the new call relative to the current one. 1516 if (Objects.equals(liveCallPhoneAccount, call.getTargetPhoneAccount())) { 1517 Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches."); 1518 return true; 1519 } else if (call.getTargetPhoneAccount() == null) { 1520 // Without a phone account, we can't say reliably that the call will fail. 1521 // If the user chooses the same phone account as the live call, then it's 1522 // still possible that the call can be made (like with CDMA calls not supporting 1523 // hold but they still support adding a call by going immediately into conference 1524 // mode). Return true here and we'll run this code again after user chooses an 1525 // account. 1526 return true; 1527 } 1528 1529 // Try to hold the live call before attempting the new outgoing call. 1530 if (liveCall.can(Connection.CAPABILITY_HOLD)) { 1531 Log.i(this, "makeRoomForOutgoingCall: holding live call."); 1532 liveCall.hold(); 1533 return true; 1534 } 1535 1536 // The live call cannot be held so we're out of luck here. There's no room. 1537 return false; 1538 } 1539 return true; 1540 } 1541 1542 /** 1543 * Given a call, find the first non-null phone account handle of its children. 1544 * 1545 * @param parentCall The parent call. 1546 * @return The first non-null phone account handle of the children, or {@code null} if none. 1547 */ 1548 private PhoneAccountHandle getFirstChildPhoneAccount(Call parentCall) { 1549 for (Call childCall : parentCall.getChildCalls()) { 1550 PhoneAccountHandle childPhoneAccount = childCall.getTargetPhoneAccount(); 1551 if (childPhoneAccount != null) { 1552 return childPhoneAccount; 1553 } 1554 } 1555 return null; 1556 } 1557 1558 /** 1559 * Checks to see if the call should be on speakerphone and if so, set it. 1560 */ 1561 private void maybeMoveToSpeakerPhone(Call call) { 1562 if (call.getStartWithSpeakerphoneOn()) { 1563 setAudioRoute(CallAudioState.ROUTE_SPEAKER); 1564 call.setStartWithSpeakerphoneOn(false); 1565 } 1566 } 1567 1568 /** 1569 * Creates a new call for an existing connection. 1570 * 1571 * @param callId The id of the new call. 1572 * @param connection The connection information. 1573 * @return The new call. 1574 */ 1575 Call createCallForExistingConnection(String callId, ParcelableConnection connection) { 1576 Call call = new Call( 1577 mContext, 1578 this, 1579 mLock, 1580 mConnectionServiceRepository, 1581 mContactsAsyncHelper, 1582 mCallerInfoAsyncQueryFactory, 1583 connection.getHandle() /* handle */, 1584 null /* gatewayInfo */, 1585 null /* connectionManagerPhoneAccount */, 1586 connection.getPhoneAccount(), /* targetPhoneAccountHandle */ 1587 false /* isIncoming */, 1588 false /* isConference */, 1589 connection.getConnectTimeMillis() /* connectTimeMillis */); 1590 1591 setCallState(call, Call.getStateFromConnectionState(connection.getState()), 1592 "existing connection"); 1593 call.setConnectionCapabilities(connection.getConnectionCapabilities()); 1594 call.setCallerDisplayName(connection.getCallerDisplayName(), 1595 connection.getCallerDisplayNamePresentation()); 1596 1597 call.addListener(this); 1598 addCall(call); 1599 1600 return call; 1601 } 1602 1603 /** 1604 * Dumps the state of the {@link CallsManager}. 1605 * 1606 * @param pw The {@code IndentingPrintWriter} to write the state to. 1607 */ 1608 public void dump(IndentingPrintWriter pw) { 1609 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 1610 if (mCalls != null) { 1611 pw.println("mCalls: "); 1612 pw.increaseIndent(); 1613 for (Call call : mCalls) { 1614 pw.println(call); 1615 } 1616 pw.decreaseIndent(); 1617 } 1618 pw.println("mForegroundCall: " + (mForegroundCall == null ? "none" : mForegroundCall)); 1619 1620 if (mCallAudioManager != null) { 1621 pw.println("mCallAudioManager:"); 1622 pw.increaseIndent(); 1623 mCallAudioManager.dump(pw); 1624 pw.decreaseIndent(); 1625 } 1626 1627 if (mTtyManager != null) { 1628 pw.println("mTtyManager:"); 1629 pw.increaseIndent(); 1630 mTtyManager.dump(pw); 1631 pw.decreaseIndent(); 1632 } 1633 1634 if (mInCallController != null) { 1635 pw.println("mInCallController:"); 1636 pw.increaseIndent(); 1637 mInCallController.dump(pw); 1638 pw.decreaseIndent(); 1639 } 1640 1641 if (mConnectionServiceRepository != null) { 1642 pw.println("mConnectionServiceRepository:"); 1643 pw.increaseIndent(); 1644 mConnectionServiceRepository.dump(pw); 1645 pw.decreaseIndent(); 1646 } 1647 } 1648} 1649