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