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