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