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