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