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