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