1 /* 2 * Copyright (C) 2014 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 17 package com.android.services.telephony; 18 19 import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING; 20 import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE; 21 22 import android.annotation.NonNull; 23 import android.app.AlertDialog; 24 import android.app.Dialog; 25 import android.content.ActivityNotFoundException; 26 import android.content.BroadcastReceiver; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.DialogInterface; 30 import android.content.Intent; 31 import android.content.IntentFilter; 32 import android.net.Uri; 33 import android.os.Bundle; 34 import android.os.ParcelUuid; 35 import android.provider.DeviceConfig; 36 import android.telecom.Conference; 37 import android.telecom.Conferenceable; 38 import android.telecom.Connection; 39 import android.telecom.ConnectionRequest; 40 import android.telecom.ConnectionService; 41 import android.telecom.DisconnectCause; 42 import android.telecom.PhoneAccount; 43 import android.telecom.PhoneAccountHandle; 44 import android.telecom.TelecomManager; 45 import android.telecom.VideoProfile; 46 import android.telephony.AccessNetworkConstants; 47 import android.telephony.Annotation.DisconnectCauses; 48 import android.telephony.CarrierConfigManager; 49 import android.telephony.DataSpecificRegistrationInfo; 50 import android.telephony.DomainSelectionService; 51 import android.telephony.DomainSelectionService.SelectionAttributes; 52 import android.telephony.EmergencyRegResult; 53 import android.telephony.NetworkRegistrationInfo; 54 import android.telephony.PhoneNumberUtils; 55 import android.telephony.RadioAccessFamily; 56 import android.telephony.ServiceState; 57 import android.telephony.SubscriptionManager; 58 import android.telephony.TelephonyManager; 59 import android.telephony.emergency.EmergencyNumber; 60 import android.telephony.ims.ImsReasonInfo; 61 import android.telephony.ims.stub.ImsRegistrationImplBase; 62 import android.text.TextUtils; 63 import android.util.Pair; 64 import android.view.WindowManager; 65 66 import com.android.ims.ImsManager; 67 import com.android.internal.annotations.VisibleForTesting; 68 import com.android.internal.telephony.Call; 69 import com.android.internal.telephony.CallStateException; 70 import com.android.internal.telephony.GsmCdmaPhone; 71 import com.android.internal.telephony.IccCard; 72 import com.android.internal.telephony.IccCardConstants; 73 import com.android.internal.telephony.Phone; 74 import com.android.internal.telephony.PhoneConstants; 75 import com.android.internal.telephony.PhoneFactory; 76 import com.android.internal.telephony.RIL; 77 import com.android.internal.telephony.d2d.Communicator; 78 import com.android.internal.telephony.data.PhoneSwitcher; 79 import com.android.internal.telephony.domainselection.DomainSelectionConnection; 80 import com.android.internal.telephony.domainselection.DomainSelectionResolver; 81 import com.android.internal.telephony.domainselection.EmergencyCallDomainSelectionConnection; 82 import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection; 83 import com.android.internal.telephony.emergency.EmergencyStateTracker; 84 import com.android.internal.telephony.emergency.RadioOnHelper; 85 import com.android.internal.telephony.emergency.RadioOnStateListener; 86 import com.android.internal.telephony.imsphone.ImsExternalCallTracker; 87 import com.android.internal.telephony.imsphone.ImsPhone; 88 import com.android.internal.telephony.imsphone.ImsPhoneConnection; 89 import com.android.internal.telephony.imsphone.ImsPhoneMmiCode; 90 import com.android.internal.telephony.satellite.SatelliteController; 91 import com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender; 92 import com.android.internal.telephony.subscription.SubscriptionInfoInternal; 93 import com.android.internal.telephony.subscription.SubscriptionManagerService; 94 import com.android.phone.FrameworksUtils; 95 import com.android.phone.MMIDialogActivity; 96 import com.android.phone.PhoneUtils; 97 import com.android.phone.R; 98 import com.android.phone.callcomposer.CallComposerPictureManager; 99 import com.android.phone.settings.SuppServicesUiUtil; 100 101 import java.lang.ref.WeakReference; 102 import java.util.ArrayList; 103 import java.util.Arrays; 104 import java.util.Collection; 105 import java.util.Collections; 106 import java.util.HashMap; 107 import java.util.LinkedList; 108 import java.util.List; 109 import java.util.Map; 110 import java.util.Objects; 111 import java.util.Queue; 112 import java.util.Set; 113 import java.util.concurrent.CompletableFuture; 114 import java.util.concurrent.Executor; 115 import java.util.function.Consumer; 116 import java.util.regex.Pattern; 117 import java.util.stream.Stream; 118 119 import javax.annotation.Nullable; 120 121 /** 122 * Service for making GSM and CDMA connections. 123 */ 124 public class TelephonyConnectionService extends ConnectionService { 125 private static final String LOG_TAG = TelephonyConnectionService.class.getSimpleName(); 126 // Timeout before we continue with the emergency call without waiting for DDS switch response 127 // from the modem. 128 private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000; 129 130 // Timeout to start dynamic routing of normal routing emergency numbers. 131 @VisibleForTesting 132 public static final int TIMEOUT_TO_DYNAMIC_ROUTING_MS = 10000; 133 134 // Timeout before we terminate the outgoing DSDA call if HOLD did not complete in time on the 135 // existing call. 136 private static final int DEFAULT_DSDA_OUTGOING_CALL_HOLD_TIMEOUT_MS = 2000; 137 private static final String KEY_DOMAIN_COMPARE_FEATURE_ENABLED_FLAG = 138 "is_domain_selection_compare_feature_enabled"; 139 140 // If configured, reject attempts to dial numbers matching this pattern. 141 private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN = 142 Pattern.compile("\\*228[0-9]{0,2}"); 143 144 private final TelephonyConnectionServiceProxy mTelephonyConnectionServiceProxy = 145 new TelephonyConnectionServiceProxy() { 146 @Override 147 public Collection<Connection> getAllConnections() { 148 return TelephonyConnectionService.this.getAllConnections(); 149 } 150 @Override 151 public void addConference(TelephonyConference mTelephonyConference) { 152 TelephonyConnectionService.this.addTelephonyConference(mTelephonyConference); 153 } 154 @Override 155 public void addConference(ImsConference mImsConference) { 156 Connection conferenceHost = mImsConference.getConferenceHost(); 157 if (conferenceHost instanceof TelephonyConnection) { 158 TelephonyConnection tcConferenceHost = (TelephonyConnection) conferenceHost; 159 tcConferenceHost.setTelephonyConnectionService(TelephonyConnectionService.this); 160 tcConferenceHost.setPhoneAccountHandle(mImsConference.getPhoneAccountHandle()); 161 } 162 TelephonyConnectionService.this.addTelephonyConference(mImsConference); 163 } 164 @Override 165 public void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 166 Connection connection) { 167 TelephonyConnectionService.this 168 .addExistingConnection(phoneAccountHandle, connection); 169 } 170 @Override 171 public void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 172 Connection connection, Conference conference) { 173 TelephonyConnectionService.this 174 .addExistingConnection(phoneAccountHandle, connection, conference); 175 } 176 @Override 177 public void addConnectionToConferenceController(TelephonyConnection connection) { 178 TelephonyConnectionService.this.addConnectionToConferenceController(connection); 179 } 180 }; 181 182 private final BroadcastReceiver mTtyBroadcastReceiver = new BroadcastReceiver() { 183 @Override 184 public void onReceive(Context context, Intent intent) { 185 String action = intent.getAction(); 186 Log.v(this, "onReceive, action: %s", action); 187 if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) { 188 int newPreferredTtyMode = intent.getIntExtra( 189 TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF); 190 191 boolean isTtyNowEnabled = newPreferredTtyMode != TelecomManager.TTY_MODE_OFF; 192 if (isTtyNowEnabled != mIsTtyEnabled) { 193 handleTtyModeChange(isTtyNowEnabled); 194 } 195 } 196 } 197 }; 198 199 private final TelephonyConferenceController mTelephonyConferenceController = 200 new TelephonyConferenceController(mTelephonyConnectionServiceProxy); 201 private final CdmaConferenceController mCdmaConferenceController = 202 new CdmaConferenceController(this); 203 private ImsConferenceController mImsConferenceController; 204 205 private ComponentName mExpectedComponentName = null; 206 private RadioOnHelper mRadioOnHelper; 207 private EmergencyTonePlayer mEmergencyTonePlayer; 208 private HoldTracker mHoldTracker; 209 private boolean mIsTtyEnabled; 210 /** Set to true when there is an emergency call pending which will potential trigger a dial. 211 * This must be set to false when the call is dialed. */ 212 private volatile boolean mIsEmergencyCallPending; 213 214 // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has 215 // already tried to connect with. There should be only one TelephonyConnection trying to place a 216 // call at one time. We also only access this cache from a TelephonyConnection that wishes to 217 // redial, so we use a WeakReference that will become stale once the TelephonyConnection is 218 // destroyed. 219 @VisibleForTesting 220 public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache; 221 private DeviceState mDeviceState = new DeviceState(); 222 private EmergencyStateTracker mEmergencyStateTracker; 223 private SatelliteSOSMessageRecommender mSatelliteSOSMessageRecommender; 224 private DomainSelectionResolver mDomainSelectionResolver; 225 private EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection; 226 private TelephonyConnection mEmergencyConnection; 227 private String mEmergencyCallId = null; 228 private Executor mDomainSelectionMainExecutor; 229 private ImsManager mImsManager = null; 230 private DomainSelectionConnection mDomainSelectionConnection; 231 private TelephonyConnection mNormalCallConnection; 232 private SatelliteController mSatelliteController; 233 234 /** 235 * Keeps track of the status of a SIM slot. 236 */ 237 private static class SlotStatus { 238 public int slotId; 239 public int activeSubId; 240 // RAT capabilities 241 public int capabilities; 242 // By default, we will assume that the slots are not locked. 243 public boolean isLocked = false; 244 // Is the emergency number associated with the slot 245 public boolean hasDialedEmergencyNumber = false; 246 //SimState. 247 public int simState; 248 249 //helper to check if sim is really 'present' in the traditional sense. 250 // since eSIM always reports SIM_STATE_READY isSubActiveAndSimPresent()251 public boolean isSubActiveAndSimPresent() { 252 return (simState != TelephonyManager.SIM_STATE_ABSENT 253 && activeSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID); 254 } 255 SlotStatus(int slotId, int capabilities, int activeSubId)256 public SlotStatus(int slotId, int capabilities, int activeSubId) { 257 this.slotId = slotId; 258 this.capabilities = capabilities; 259 this.activeSubId = activeSubId; 260 } 261 } 262 263 /** 264 * SubscriptionManager dependencies for testing. 265 */ 266 @VisibleForTesting 267 public interface SubscriptionManagerProxy { getDefaultVoicePhoneId()268 int getDefaultVoicePhoneId(); getDefaultDataPhoneId()269 int getDefaultDataPhoneId(); getSimStateForSlotIdx(int slotId)270 int getSimStateForSlotIdx(int slotId); getPhoneId(int subId)271 int getPhoneId(int subId); 272 } 273 274 private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() { 275 @Override 276 public int getDefaultVoicePhoneId() { 277 return SubscriptionManager.getDefaultVoicePhoneId(); 278 } 279 280 @Override 281 public int getDefaultDataPhoneId() { 282 return getPhoneId(SubscriptionManager.getDefaultDataSubscriptionId()); 283 } 284 285 @Override 286 public int getSimStateForSlotIdx(int slotId) { 287 return TelephonyManager.getSimStateForSlotIndex(slotId); 288 } 289 290 @Override 291 public int getPhoneId(int subId) { 292 return SubscriptionManager.getPhoneId(subId); 293 } 294 295 }; 296 297 /** 298 * TelephonyManager dependencies for testing. 299 */ 300 @VisibleForTesting 301 public interface TelephonyManagerProxy { getPhoneCount()302 int getPhoneCount(); isCurrentEmergencyNumber(String number)303 boolean isCurrentEmergencyNumber(String number); getCurrentEmergencyNumberList()304 Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList(); 305 306 /** 307 * Determines whether concurrent IMS calls across both SIMs are possible, based on whether 308 * the device is DSDA capable, or if the DSDS device supports virtual DSDA. 309 */ isConcurrentCallsPossible()310 boolean isConcurrentCallsPossible(); 311 312 /** 313 * Gets the maximum number of SIMs that can be active, based on the device's multisim 314 * configuration. Returns 1 for DSDS, 2 for DSDA. 315 */ getMaxNumberOfSimultaneouslyActiveSims()316 int getMaxNumberOfSimultaneouslyActiveSims(); 317 } 318 319 private TelephonyManagerProxy mTelephonyManagerProxy; 320 321 private class TelephonyManagerProxyImpl implements TelephonyManagerProxy { 322 private final TelephonyManager mTelephonyManager; 323 324 TelephonyManagerProxyImpl(Context context)325 TelephonyManagerProxyImpl(Context context) { 326 mTelephonyManager = new TelephonyManager(context); 327 } 328 329 @Override getPhoneCount()330 public int getPhoneCount() { 331 return mTelephonyManager.getPhoneCount(); 332 } 333 334 @Override isCurrentEmergencyNumber(String number)335 public boolean isCurrentEmergencyNumber(String number) { 336 try { 337 return mTelephonyManager.isEmergencyNumber(number); 338 } catch (IllegalStateException ise) { 339 return false; 340 } 341 } 342 343 @Override getCurrentEmergencyNumberList()344 public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList() { 345 try { 346 return mTelephonyManager.getEmergencyNumberList(); 347 } catch (IllegalStateException ise) { 348 return new HashMap<>(); 349 } 350 } 351 352 @Override getMaxNumberOfSimultaneouslyActiveSims()353 public int getMaxNumberOfSimultaneouslyActiveSims() { 354 try { 355 return mTelephonyManager.getMaxNumberOfSimultaneouslyActiveSims(); 356 } catch (IllegalStateException ise) { 357 return 1; 358 } 359 } 360 361 @Override isConcurrentCallsPossible()362 public boolean isConcurrentCallsPossible() { 363 try { 364 return getMaxNumberOfSimultaneouslyActiveSims() > 1 365 || mTelephonyManager.getPhoneCapability().getMaxActiveVoiceSubscriptions() > 1; 366 } catch (IllegalStateException ise) { 367 return false; 368 } 369 } 370 } 371 372 /** 373 * PhoneFactory Dependencies for testing. 374 */ 375 @VisibleForTesting 376 public interface PhoneFactoryProxy { getPhone(int index)377 Phone getPhone(int index); getDefaultPhone()378 Phone getDefaultPhone(); getPhones()379 Phone[] getPhones(); 380 } 381 382 private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() { 383 @Override 384 public Phone getPhone(int index) { 385 return PhoneFactory.getPhone(index); 386 } 387 388 @Override 389 public Phone getDefaultPhone() { 390 return PhoneFactory.getDefaultPhone(); 391 } 392 393 @Override 394 public Phone[] getPhones() { 395 return PhoneFactory.getPhones(); 396 } 397 }; 398 399 /** 400 * PhoneUtils dependencies for testing. 401 */ 402 @VisibleForTesting 403 public interface PhoneUtilsProxy { getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle)404 int getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle); makePstnPhoneAccountHandle(Phone phone)405 PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone); makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, boolean isEmergency)406 PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, 407 boolean isEmergency); 408 } 409 410 private PhoneUtilsProxy mPhoneUtilsProxy = new PhoneUtilsProxy() { 411 @Override 412 public int getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle) { 413 return PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle); 414 } 415 416 @Override 417 public PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 418 return PhoneUtils.makePstnPhoneAccountHandle(phone); 419 } 420 421 @Override 422 public PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, 423 boolean isEmergency) { 424 return PhoneUtils.makePstnPhoneAccountHandleWithPrefix( 425 phone, prefix, isEmergency, phone.getUserHandle()); 426 } 427 }; 428 429 /** 430 * PhoneNumberUtils dependencies for testing. 431 */ 432 @VisibleForTesting 433 public interface PhoneNumberUtilsProxy { convertToEmergencyNumber(Context context, String number)434 String convertToEmergencyNumber(Context context, String number); 435 } 436 437 private PhoneNumberUtilsProxy mPhoneNumberUtilsProxy = new PhoneNumberUtilsProxy() { 438 @Override 439 public String convertToEmergencyNumber(Context context, String number) { 440 return PhoneNumberUtils.convertToEmergencyNumber(context, number); 441 } 442 }; 443 444 /** 445 * PhoneSwitcher dependencies for testing. 446 */ 447 @VisibleForTesting 448 public interface PhoneSwitcherProxy { getPhoneSwitcher()449 PhoneSwitcher getPhoneSwitcher(); 450 } 451 452 private PhoneSwitcherProxy mPhoneSwitcherProxy = new PhoneSwitcherProxy() { 453 @Override 454 public PhoneSwitcher getPhoneSwitcher() { 455 return PhoneSwitcher.getInstance(); 456 } 457 }; 458 459 /** 460 * DisconnectCause depends on PhoneGlobals in order to get a system context. Mock out 461 * dependency for testing. 462 */ 463 @VisibleForTesting 464 public interface DisconnectCauseFactory { toTelecomDisconnectCause(int telephonyDisconnectCause, String reason)465 DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, String reason); toTelecomDisconnectCause(int telephonyDisconnectCause, String reason, int phoneId)466 DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, 467 String reason, int phoneId); 468 } 469 470 private DisconnectCauseFactory mDisconnectCauseFactory = new DisconnectCauseFactory() { 471 @Override 472 public DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, 473 String reason) { 474 return DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause, reason); 475 } 476 477 @Override 478 public DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, String reason, 479 int phoneId) { 480 return DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause, reason, 481 phoneId); 482 } 483 }; 484 485 /** 486 * Overrides SubscriptionManager dependencies for testing. 487 */ 488 @VisibleForTesting setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)489 public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) { 490 mSubscriptionManagerProxy = proxy; 491 } 492 493 /** 494 * Overrides TelephonyManager dependencies for testing. 495 */ 496 @VisibleForTesting setTelephonyManagerProxy(TelephonyManagerProxy proxy)497 public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) { 498 mTelephonyManagerProxy = proxy; 499 } 500 501 /** 502 * Overrides PhoneFactory dependencies for testing. 503 */ 504 @VisibleForTesting setPhoneFactoryProxy(PhoneFactoryProxy proxy)505 public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) { 506 mPhoneFactoryProxy = proxy; 507 } 508 509 /** 510 * Overrides configuration and settings dependencies for testing. 511 */ 512 @VisibleForTesting setDeviceState(DeviceState state)513 public void setDeviceState(DeviceState state) { 514 mDeviceState = state; 515 } 516 517 /** 518 * Overrides radioOnHelper for testing. 519 */ 520 @VisibleForTesting setRadioOnHelper(RadioOnHelper radioOnHelper)521 public void setRadioOnHelper(RadioOnHelper radioOnHelper) { 522 mRadioOnHelper = radioOnHelper; 523 } 524 525 /** 526 * Overrides PhoneSwitcher dependencies for testing. 527 */ 528 @VisibleForTesting setPhoneSwitcherProxy(PhoneSwitcherProxy proxy)529 public void setPhoneSwitcherProxy(PhoneSwitcherProxy proxy) { 530 mPhoneSwitcherProxy = proxy; 531 } 532 533 /** 534 * Overrides PhoneNumberUtils dependencies for testing. 535 */ 536 @VisibleForTesting setPhoneNumberUtilsProxy(PhoneNumberUtilsProxy proxy)537 public void setPhoneNumberUtilsProxy(PhoneNumberUtilsProxy proxy) { 538 mPhoneNumberUtilsProxy = proxy; 539 } 540 541 /** 542 * Overrides PhoneUtils dependencies for testing. 543 */ 544 @VisibleForTesting setPhoneUtilsProxy(PhoneUtilsProxy proxy)545 public void setPhoneUtilsProxy(PhoneUtilsProxy proxy) { 546 mPhoneUtilsProxy = proxy; 547 } 548 549 /** 550 * Override DisconnectCause creation for testing. 551 */ 552 @VisibleForTesting setDisconnectCauseFactory(DisconnectCauseFactory factory)553 public void setDisconnectCauseFactory(DisconnectCauseFactory factory) { 554 mDisconnectCauseFactory = factory; 555 } 556 557 /** 558 * A listener for emergency calls. 559 */ 560 private final TelephonyConnection.TelephonyConnectionListener mEmergencyConnectionListener = 561 new TelephonyConnection.TelephonyConnectionListener() { 562 @Override 563 public void onOriginalConnectionConfigured(TelephonyConnection c) { 564 com.android.internal.telephony.Connection origConn = c.getOriginalConnection(); 565 if ((origConn == null) || (mEmergencyStateTracker == null)) { 566 // mEmergencyStateTracker is null when no emergency call has been dialed 567 // after bootup and normal call fails with 380 response. 568 return; 569 } 570 // Update the domain in the case that it changes,for example during initial 571 // setup or when there was an srvcc or internal redial. 572 mEmergencyStateTracker.onEmergencyCallDomainUpdated( 573 origConn.getPhoneType(), c.getTelecomCallId()); 574 } 575 576 @Override 577 public void onStateChanged(Connection connection, 578 @Connection.ConnectionState int state) { 579 if (mEmergencyCallDomainSelectionConnection == null) return; 580 if (connection == null) return; 581 TelephonyConnection c = (TelephonyConnection) connection; 582 Log.i(this, "onStateChanged callId=" + c.getTelecomCallId() 583 + ", state=" + state); 584 if (c.getState() == Connection.STATE_ACTIVE) { 585 mEmergencyStateTracker.onEmergencyCallStateChanged( 586 c.getOriginalConnection().getState(), c.getTelecomCallId()); 587 releaseEmergencyCallDomainSelection(false); 588 } 589 } 590 }; 591 592 private final TelephonyConnection.TelephonyConnectionListener 593 mEmergencyConnectionSatelliteListener = 594 new TelephonyConnection.TelephonyConnectionListener() { 595 @Override 596 public void onStateChanged(Connection connection, 597 @Connection.ConnectionState int state) { 598 if (connection == null) { 599 Log.d(this, 600 "onStateChanged for satellite listener: connection is null"); 601 return; 602 } 603 if (mSatelliteSOSMessageRecommender == null) { 604 Log.d(this, "onStateChanged for satellite listener: " 605 + "mSatelliteSOSMessageRecommender is null"); 606 return; 607 } 608 609 TelephonyConnection c = (TelephonyConnection) connection; 610 mSatelliteSOSMessageRecommender.onEmergencyCallConnectionStateChanged( 611 c.getTelecomCallId(), state); 612 if (state == Connection.STATE_DISCONNECTED 613 || state == Connection.STATE_ACTIVE) { 614 c.removeTelephonyConnectionListener(mEmergencyConnectionSatelliteListener); 615 mSatelliteSOSMessageRecommender = null; 616 } 617 } 618 }; 619 620 /** 621 * A listener for calls. 622 */ 623 private final TelephonyConnection.TelephonyConnectionListener mNormalCallConnectionListener = 624 new TelephonyConnection.TelephonyConnectionListener() { 625 @Override 626 public void onStateChanged( 627 Connection connection, @Connection.ConnectionState int state) { 628 TelephonyConnection c = (TelephonyConnection) connection; 629 if (c != null) { 630 switch(c.getState()) { 631 case Connection.STATE_ACTIVE: { 632 Log.d(LOG_TAG, "Call State->ACTIVE." 633 + "Clearing DomainSelectionConnection"); 634 if (mDomainSelectionConnection != null) { 635 mDomainSelectionConnection.finishSelection(); 636 mDomainSelectionConnection = null; 637 } 638 mNormalCallConnection = null; 639 } 640 break; 641 642 case Connection.STATE_DISCONNECTED: { 643 c.removeTelephonyConnectionListener(mNormalCallConnectionListener); 644 } 645 break; 646 } 647 } 648 } 649 }; 650 651 private static class StateHoldingListener extends 652 TelephonyConnection.TelephonyConnectionListener { 653 private final CompletableFuture<Boolean> mStateHoldingFuture; 654 StateHoldingListener(CompletableFuture<Boolean> future)655 StateHoldingListener(CompletableFuture<Boolean> future) { 656 mStateHoldingFuture = future; 657 } 658 659 @Override onStateChanged( Connection connection, @Connection.ConnectionState int state)660 public void onStateChanged( 661 Connection connection, @Connection.ConnectionState int state) { 662 TelephonyConnection c = (TelephonyConnection) connection; 663 if (c != null) { 664 switch (c.getState()) { 665 case Connection.STATE_HOLDING: { 666 Log.d(LOG_TAG, "Connection " + connection.getTelecomCallId() 667 + " changed to STATE_HOLDING!"); 668 mStateHoldingFuture.complete(true); 669 c.removeTelephonyConnectionListener(this); 670 } 671 break; 672 case Connection.STATE_DISCONNECTED: { 673 Log.d(LOG_TAG, "Connection " + connection.getTelecomCallId() 674 + " changed to STATE_DISCONNECTED!"); 675 mStateHoldingFuture.complete(false); 676 c.removeTelephonyConnectionListener(this); 677 } 678 break; 679 } 680 } 681 } 682 } 683 684 private final DomainSelectionConnection.DomainSelectionConnectionCallback 685 mEmergencyDomainSelectionConnectionCallback = 686 new DomainSelectionConnection.DomainSelectionConnectionCallback() { 687 @Override 688 public void onSelectionTerminated(@DisconnectCauses int cause) { 689 mDomainSelectionMainExecutor.execute(() -> { 690 Log.i(this, "onSelectionTerminated cause=" + cause); 691 if (mEmergencyCallDomainSelectionConnection == null) { 692 Log.i(this, "onSelectionTerminated no DomainSelectionConnection"); 693 return; 694 } 695 696 // Cross stack redial 697 if (cause == android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE 698 || cause == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE) { 699 if (mEmergencyConnection != null) { 700 boolean isPermanentFailure = 701 cause == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE; 702 Log.i(this, "onSelectionTerminated permanent=" + isPermanentFailure); 703 TelephonyConnection c = mEmergencyConnection; 704 Phone phone = mEmergencyCallDomainSelectionConnection.getPhone(); 705 mEmergencyConnection.removeTelephonyConnectionListener( 706 mEmergencyConnectionListener); 707 releaseEmergencyCallDomainSelection(true); 708 mEmergencyStateTracker.endCall(mEmergencyCallId); 709 mEmergencyCallId = null; 710 retryOutgoingOriginalConnection(c, phone, isPermanentFailure); 711 return; 712 } 713 } 714 if (mEmergencyConnection != null) { 715 mEmergencyConnection.hangup(android.telephony.DisconnectCause.OUT_OF_NETWORK); 716 mEmergencyConnection = null; 717 } 718 }); 719 } 720 }; 721 722 private final DomainSelectionConnection.DomainSelectionConnectionCallback 723 mCallDomainSelectionConnectionCallback = 724 new DomainSelectionConnection.DomainSelectionConnectionCallback() { 725 @Override 726 public void onSelectionTerminated(@DisconnectCauses int cause) { 727 mDomainSelectionMainExecutor.execute(new Runnable() { 728 int mCause = cause; 729 @Override 730 public void run() { 731 Log.v(this, "Call domain selection terminated."); 732 if (mDomainSelectionConnection != null) { 733 mDomainSelectionConnection = null; 734 } 735 if (mNormalCallConnection != null) { 736 // TODO: To support ShowPreciseFailedCause, TelephonyConnection 737 // .getShowPreciseFailedCause API should be added. 738 739 // If cause is NOT_VALID then, it's a redial cancellation and 740 // use cause code from original connection. 741 com.android.internal.telephony.Connection connection = 742 mNormalCallConnection.getOriginalConnection(); 743 if (connection != null) { 744 if (mCause == android.telephony.DisconnectCause.NOT_VALID) { 745 mCause = connection.getDisconnectCause(); 746 } 747 748 String reason = connection.getVendorDisconnectCause(); 749 int phoneId = mNormalCallConnection.getPhone().getPhoneId(); 750 mNormalCallConnection.setTelephonyConnectionDisconnected( 751 mDisconnectCauseFactory.toTelecomDisconnectCause( 752 mCause, reason, phoneId)); 753 Log.d(this, "Call connection closed. Cause: " + mCause 754 + " Reason: " + reason); 755 } 756 mNormalCallConnection.close(); 757 mNormalCallConnection = null; 758 } 759 } 760 }); 761 } 762 }; 763 764 /** 765 * A listener to actionable events specific to the TelephonyConnection. 766 */ 767 private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener = 768 new TelephonyConnection.TelephonyConnectionListener() { 769 @Override 770 public void onOriginalConnectionConfigured(TelephonyConnection c) { 771 addConnectionToConferenceController(c); 772 } 773 774 @Override 775 public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) { 776 retryOutgoingOriginalConnection(c, c.getPhone(), isPermanentFailure); 777 } 778 }; 779 780 private final TelephonyConferenceBase.TelephonyConferenceListener mTelephonyConferenceListener = 781 new TelephonyConferenceBase.TelephonyConferenceListener() { 782 @Override 783 public void onConferenceMembershipChanged(Connection connection) { 784 mHoldTracker.updateHoldCapability(); 785 } 786 }; 787 788 @Override onCreate()789 public void onCreate() { 790 super.onCreate(); 791 mImsConferenceController = new ImsConferenceController( 792 TelecomAccountRegistry.getInstance(this), 793 mTelephonyConnectionServiceProxy, 794 // FeatureFlagProxy; used to determine if standalone call emulation is enabled. 795 // TODO: Move to carrier config 796 () -> true); 797 setTelephonyManagerProxy(new TelephonyManagerProxyImpl(getApplicationContext())); 798 mExpectedComponentName = new ComponentName(this, this.getClass()); 799 mEmergencyTonePlayer = new EmergencyTonePlayer(this); 800 TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this); 801 mHoldTracker = new HoldTracker(); 802 mIsTtyEnabled = mDeviceState.isTtyModeEnabled(this); 803 mDomainSelectionMainExecutor = getApplicationContext().getMainExecutor(); 804 mDomainSelectionResolver = DomainSelectionResolver.getInstance(); 805 mSatelliteController = SatelliteController.getInstance(); 806 807 IntentFilter intentFilter = new IntentFilter( 808 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED); 809 registerReceiver(mTtyBroadcastReceiver, intentFilter, 810 android.Manifest.permission.MODIFY_PHONE_STATE, null, Context.RECEIVER_EXPORTED); 811 } 812 813 @Override onUnbind(Intent intent)814 public boolean onUnbind(Intent intent) { 815 unregisterReceiver(mTtyBroadcastReceiver); 816 return super.onUnbind(intent); 817 } 818 placeOutgoingConference(ConnectionRequest request, Connection resultConnection, Phone phone)819 private Conference placeOutgoingConference(ConnectionRequest request, 820 Connection resultConnection, Phone phone) { 821 if (resultConnection instanceof TelephonyConnection) { 822 return placeOutgoingConference((TelephonyConnection) resultConnection, phone, request); 823 } 824 return null; 825 } 826 placeOutgoingConference(TelephonyConnection conferenceHostConnection, Phone phone, ConnectionRequest request)827 private Conference placeOutgoingConference(TelephonyConnection conferenceHostConnection, 828 Phone phone, ConnectionRequest request) { 829 updatePhoneAccount(conferenceHostConnection, phone); 830 com.android.internal.telephony.Connection originalConnection = null; 831 try { 832 originalConnection = phone.startConference( 833 getParticipantsToDial(request.getParticipants()), 834 new ImsPhone.ImsDialArgs.Builder() 835 .setVideoState(request.getVideoState()) 836 .setRttTextStream(conferenceHostConnection.getRttTextStream()) 837 .build()); 838 } catch (CallStateException e) { 839 Log.e(this, e, "placeOutgoingConference, phone.startConference exception: " + e); 840 handleCallStateException(e, conferenceHostConnection, phone); 841 return null; 842 } 843 844 if (originalConnection == null) { 845 Log.d(this, "placeOutgoingConference, phone.startConference returned null"); 846 conferenceHostConnection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 847 android.telephony.DisconnectCause.OUTGOING_FAILURE, 848 "conferenceHostConnection is null", 849 phone.getPhoneId())); 850 conferenceHostConnection.clearOriginalConnection(); 851 conferenceHostConnection.destroy(); 852 } else { 853 conferenceHostConnection.setOriginalConnection(originalConnection); 854 } 855 856 return prepareConference(conferenceHostConnection, request.getAccountHandle()); 857 } 858 prepareConference(Connection conn, PhoneAccountHandle phoneAccountHandle)859 Conference prepareConference(Connection conn, PhoneAccountHandle phoneAccountHandle) { 860 if (!(conn instanceof TelephonyConnection)) { 861 Log.w(this, "prepareConference returning NULL conference"); 862 return null; 863 } 864 865 TelephonyConnection connection = (TelephonyConnection)conn; 866 867 ImsConference conference = new ImsConference(TelecomAccountRegistry.getInstance(this), 868 mTelephonyConnectionServiceProxy, connection, 869 phoneAccountHandle, () -> true, 870 ImsConferenceController.getCarrierConfig(connection.getPhone())); 871 mImsConferenceController.addConference(conference); 872 conference.setVideoState(connection, 873 connection.getVideoState()); 874 conference.setVideoProvider(connection, 875 connection.getVideoProvider()); 876 conference.setStatusHints(connection.getStatusHints()); 877 conference.setAddress(connection.getAddress(), 878 connection.getAddressPresentation()); 879 conference.setCallerDisplayName(connection.getCallerDisplayName(), 880 connection.getCallerDisplayNamePresentation()); 881 conference.setParticipants(connection.getParticipants()); 882 return conference; 883 } 884 885 @Override onCreateIncomingConference( @ullable PhoneAccountHandle connectionManagerPhoneAccount, @NonNull final ConnectionRequest request)886 public @Nullable Conference onCreateIncomingConference( 887 @Nullable PhoneAccountHandle connectionManagerPhoneAccount, 888 @NonNull final ConnectionRequest request) { 889 Log.i(this, "onCreateIncomingConference, request: " + request); 890 Connection connection = onCreateIncomingConnection(connectionManagerPhoneAccount, request); 891 Log.d(this, "onCreateIncomingConference, connection: %s", connection); 892 if (connection == null) { 893 Log.i(this, "onCreateIncomingConference, implementation returned null connection."); 894 return Conference.createFailedConference( 895 new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"), 896 request.getAccountHandle()); 897 } 898 899 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 900 false /* isEmergencyCall*/, null /* not an emergency call */); 901 if (phone == null) { 902 Log.d(this, "onCreateIncomingConference, phone is null"); 903 return Conference.createFailedConference( 904 DisconnectCauseUtil.toTelecomDisconnectCause( 905 android.telephony.DisconnectCause.OUT_OF_SERVICE, 906 "Phone is null"), 907 request.getAccountHandle()); 908 } 909 910 return prepareConference(connection, request.getAccountHandle()); 911 } 912 913 @Override onCreateOutgoingConference( @ullable PhoneAccountHandle connectionManagerPhoneAccount, @NonNull final ConnectionRequest request)914 public @Nullable Conference onCreateOutgoingConference( 915 @Nullable PhoneAccountHandle connectionManagerPhoneAccount, 916 @NonNull final ConnectionRequest request) { 917 Log.i(this, "onCreateOutgoingConference, request: " + request); 918 Connection connection = onCreateOutgoingConnection(connectionManagerPhoneAccount, request); 919 Log.d(this, "onCreateOutgoingConference, connection: %s", connection); 920 if (connection == null) { 921 Log.i(this, "onCreateOutgoingConference, implementation returned null connection."); 922 return Conference.createFailedConference( 923 new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"), 924 request.getAccountHandle()); 925 } 926 927 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 928 false /* isEmergencyCall*/, null /* not an emergency call */); 929 if (phone == null) { 930 Log.d(this, "onCreateOutgoingConference, phone is null"); 931 return Conference.createFailedConference( 932 DisconnectCauseUtil.toTelecomDisconnectCause( 933 android.telephony.DisconnectCause.OUT_OF_SERVICE, 934 "Phone is null"), 935 request.getAccountHandle()); 936 } 937 938 return placeOutgoingConference(request, connection, phone); 939 } 940 getParticipantsToDial(List<Uri> participants)941 private String[] getParticipantsToDial(List<Uri> participants) { 942 String[] participantsToDial = new String[participants.size()]; 943 int i = 0; 944 for (Uri participant : participants) { 945 participantsToDial[i] = participant.getSchemeSpecificPart(); 946 i++; 947 } 948 return participantsToDial; 949 } 950 951 @Override onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)952 public Connection onCreateOutgoingConnection( 953 PhoneAccountHandle connectionManagerPhoneAccount, 954 final ConnectionRequest request) { 955 Log.i(this, "onCreateOutgoingConnection, request: " + request); 956 957 Uri handle = request.getAddress(); 958 boolean isAdhocConference = request.isAdhocConferenceCall(); 959 960 if (!isAdhocConference && handle == null) { 961 Log.d(this, "onCreateOutgoingConnection, handle is null"); 962 return Connection.createFailedConnection( 963 mDisconnectCauseFactory.toTelecomDisconnectCause( 964 android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, 965 "No phone number supplied")); 966 } 967 968 String scheme = handle.getScheme(); 969 String number; 970 if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) { 971 // TODO: We don't check for SecurityException here (requires 972 // CALL_PRIVILEGED permission). 973 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 974 false /* isEmergencyCall */, null /* not an emergency call */); 975 if (phone == null) { 976 Log.d(this, "onCreateOutgoingConnection, phone is null"); 977 return Connection.createFailedConnection( 978 mDisconnectCauseFactory.toTelecomDisconnectCause( 979 android.telephony.DisconnectCause.OUT_OF_SERVICE, 980 "Phone is null")); 981 } 982 number = phone.getVoiceMailNumber(); 983 if (TextUtils.isEmpty(number)) { 984 Log.d(this, "onCreateOutgoingConnection, no voicemail number set."); 985 return Connection.createFailedConnection( 986 mDisconnectCauseFactory.toTelecomDisconnectCause( 987 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING, 988 "Voicemail scheme provided but no voicemail number set.", 989 phone.getPhoneId())); 990 } 991 992 // Convert voicemail: to tel: 993 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 994 } else { 995 if (!PhoneAccount.SCHEME_TEL.equals(scheme)) { 996 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme); 997 return Connection.createFailedConnection( 998 mDisconnectCauseFactory.toTelecomDisconnectCause( 999 android.telephony.DisconnectCause.INVALID_NUMBER, 1000 "Handle scheme is not type tel")); 1001 } 1002 1003 number = handle.getSchemeSpecificPart(); 1004 if (TextUtils.isEmpty(number)) { 1005 Log.d(this, "onCreateOutgoingConnection, unable to parse number"); 1006 return Connection.createFailedConnection( 1007 mDisconnectCauseFactory.toTelecomDisconnectCause( 1008 android.telephony.DisconnectCause.INVALID_NUMBER, 1009 "Unable to parse number")); 1010 } 1011 1012 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 1013 false /* isEmergencyCall*/, null /* not an emergency call */); 1014 if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) { 1015 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number 1016 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and 1017 // when dialed could lock LTE SIMs to 3G if not prohibited.. 1018 boolean disableActivation = false; 1019 CarrierConfigManager cfgManager = (CarrierConfigManager) 1020 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1021 if (cfgManager != null) { 1022 disableActivation = cfgManager.getConfigForSubId(phone.getSubId()) 1023 .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL); 1024 } 1025 1026 if (disableActivation) { 1027 return Connection.createFailedConnection( 1028 mDisconnectCauseFactory.toTelecomDisconnectCause( 1029 android.telephony.DisconnectCause 1030 .CDMA_ALREADY_ACTIVATED, 1031 "Tried to dial *228", 1032 phone.getPhoneId())); 1033 } 1034 } 1035 } 1036 1037 final boolean isEmergencyNumber = mTelephonyManagerProxy.isCurrentEmergencyNumber(number); 1038 // Find out if this is a test emergency number 1039 final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number); 1040 1041 // Convert into emergency number if necessary 1042 // This is required in some regions (e.g. Taiwan). 1043 if (isEmergencyNumber) { 1044 final Phone phone = getPhoneForAccount(request.getAccountHandle(), false, 1045 handle.getSchemeSpecificPart()); 1046 // We only do the conversion if the phone is not in service. The un-converted 1047 // emergency numbers will go to the correct destination when the phone is in-service, 1048 // so they will only need the special emergency call setup when the phone is out of 1049 // service. 1050 if (phone == null || phone.getServiceState().getState() 1051 != ServiceState.STATE_IN_SERVICE) { 1052 String convertedNumber = mPhoneNumberUtilsProxy.convertToEmergencyNumber(this, 1053 number); 1054 if (!TextUtils.equals(convertedNumber, number)) { 1055 Log.i(this, "onCreateOutgoingConnection, converted to emergency number"); 1056 number = convertedNumber; 1057 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 1058 } 1059 } 1060 } 1061 final String numberToDial = number; 1062 1063 final boolean isAirplaneModeOn = mDeviceState.isAirplaneModeOn(this); 1064 1065 boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn)) 1066 || isRadioPowerDownOnBluetooth(); 1067 boolean needToTurnOffSatellite = isSatelliteBlockingCall(isEmergencyNumber); 1068 1069 // Get the right phone object from the account data passed in. 1070 final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber, 1071 /* Note: when not an emergency, handle can be null for unknown callers */ 1072 handle == null ? null : handle.getSchemeSpecificPart()); 1073 1074 if (mDomainSelectionResolver.isDomainSelectionSupported()) { 1075 // Normal routing emergency number shall be handled by normal call domain selctor. 1076 if (isEmergencyNumber && !isNormalRouting(phone, number)) { 1077 final Connection resultConnection = 1078 placeEmergencyConnection(phone, 1079 request, numberToDial, isTestEmergencyNumber, 1080 handle, needToTurnOnRadio); 1081 if (resultConnection != null) return resultConnection; 1082 } 1083 } 1084 1085 if (needToTurnOnRadio || needToTurnOffSatellite) { 1086 final Uri resultHandle = handle; 1087 final int originalPhoneType = phone.getPhoneType(); 1088 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 1089 isEmergencyNumber, resultHandle, phone); 1090 if (mRadioOnHelper == null) { 1091 mRadioOnHelper = new RadioOnHelper(this); 1092 } 1093 1094 if (isEmergencyNumber) { 1095 mIsEmergencyCallPending = true; 1096 } 1097 int timeoutToOnTimeoutCallback = mDomainSelectionResolver.isDomainSelectionSupported() 1098 ? TIMEOUT_TO_DYNAMIC_ROUTING_MS : 0; 1099 mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() { 1100 @Override 1101 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) { 1102 handleOnComplete(isRadioReady, isEmergencyNumber, resultConnection, request, 1103 numberToDial, resultHandle, originalPhoneType, phone); 1104 } 1105 1106 @Override 1107 public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) { 1108 if (mDomainSelectionResolver.isDomainSelectionSupported()) { 1109 return isEmergencyNumber; 1110 } 1111 return false; 1112 } 1113 1114 @Override 1115 public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) { 1116 // HAL 1.4 introduced a new variant of dial for emergency calls, which includes 1117 // an isTesting parameter. For HAL 1.4+, do not wait for IN_SERVICE, this will 1118 // be handled at the RIL/vendor level by emergencyDial(...). 1119 boolean waitForInServiceToDialEmergency = isTestEmergencyNumber 1120 && phone.getHalVersion(HAL_SERVICE_VOICE) 1121 .less(RIL.RADIO_HAL_VERSION_1_4); 1122 if (mDomainSelectionResolver.isDomainSelectionSupported()) { 1123 if (isEmergencyNumber) { 1124 // Since the domain selection service is enabled, 1125 // dilaing normal routing emergency number only reaches here. 1126 if (!isVoiceInService(phone, imsVoiceCapable)) { 1127 // Wait for voice in service. 1128 // That is, wait for IMS registration on PS only network. 1129 serviceState = ServiceState.STATE_OUT_OF_SERVICE; 1130 waitForInServiceToDialEmergency = true; 1131 } 1132 } 1133 } 1134 if (isEmergencyNumber && !waitForInServiceToDialEmergency) { 1135 // We currently only look to make sure that the radio is on before dialing. 1136 // We should be able to make emergency calls at any time after the radio has 1137 // been powered on and isn't in the UNAVAILABLE state, even if it is 1138 // reporting the OUT_OF_SERVICE state. 1139 return phone.getState() == PhoneConstants.State.OFFHOOK 1140 || (phone.getServiceStateTracker().isRadioOn() 1141 && !mSatelliteController.isSatelliteEnabled()); 1142 } else { 1143 SubscriptionInfoInternal subInfo = SubscriptionManagerService 1144 .getInstance().getSubscriptionInfoInternal(phone.getSubId()); 1145 // Wait until we are in service and ready to make calls. This can happen 1146 // when we power down the radio on bluetooth to save power on watches or 1147 // if it is a test emergency number and we have to wait for the device 1148 // to move IN_SERVICE before the call can take place over normal 1149 // routing. 1150 return phone.getState() == PhoneConstants.State.OFFHOOK 1151 // Do not wait for voice in service on opportunistic SIMs. 1152 || subInfo != null && subInfo.isOpportunistic() 1153 || (serviceState == ServiceState.STATE_IN_SERVICE 1154 && !isSatelliteBlockingCall(isEmergencyNumber)); 1155 } 1156 } 1157 }, isEmergencyNumber && !isTestEmergencyNumber, phone, isTestEmergencyNumber, 1158 timeoutToOnTimeoutCallback); 1159 // Return the still unconnected GsmConnection and wait for the Radios to boot before 1160 // connecting it to the underlying Phone. 1161 return resultConnection; 1162 } else { 1163 if (!canAddCall() && !isEmergencyNumber) { 1164 Log.d(this, "onCreateOutgoingConnection, cannot add call ."); 1165 return Connection.createFailedConnection( 1166 new DisconnectCause(DisconnectCause.ERROR, 1167 getApplicationContext().getText( 1168 R.string.incall_error_cannot_add_call), 1169 getApplicationContext().getText( 1170 R.string.incall_error_cannot_add_call), 1171 "Add call restricted due to ongoing video call")); 1172 } 1173 1174 if (!isEmergencyNumber) { 1175 if (mSatelliteController.isSatelliteEnabled() 1176 || isCallDisallowedDueToSatellite(phone)) { 1177 Log.d(this, "onCreateOutgoingConnection, cannot make call in satellite mode."); 1178 return Connection.createFailedConnection( 1179 mDisconnectCauseFactory.toTelecomDisconnectCause( 1180 android.telephony.DisconnectCause.SATELLITE_ENABLED, 1181 "Call failed because satellite modem is enabled.")); 1182 } 1183 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 1184 false, handle, phone); 1185 if (isAdhocConference) { 1186 if (resultConnection instanceof TelephonyConnection) { 1187 TelephonyConnection conn = (TelephonyConnection)resultConnection; 1188 conn.setParticipants(request.getParticipants()); 1189 } 1190 return resultConnection; 1191 } else { 1192 if (mTelephonyManagerProxy.isConcurrentCallsPossible()) { 1193 Conferenceable c = maybeHoldCallsOnOtherSubs(request.getAccountHandle()); 1194 if (c != null) { 1195 delayDialForOtherSubHold(phone, c, (success) -> { 1196 Log.d(this, 1197 "onCreateOutgoingConn - delayDialForOtherSubHold" 1198 + " success = " + success); 1199 if (success) { 1200 placeOutgoingConnection(request, resultConnection, 1201 phone); 1202 } else { 1203 ((TelephonyConnection) resultConnection).hangup( 1204 android.telephony.DisconnectCause.LOCAL); 1205 } 1206 }); 1207 return resultConnection; 1208 } 1209 } 1210 return placeOutgoingConnection(request, resultConnection, phone); 1211 } 1212 } else { 1213 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 1214 true, handle, phone); 1215 1216 CompletableFuture<Void> maybeHoldFuture = CompletableFuture.completedFuture(null); 1217 if (mTelephonyManagerProxy.isConcurrentCallsPossible() 1218 && shouldHoldForEmergencyCall(phone)) { 1219 // If the PhoneAccountHandle was adjusted on building the TelephonyConnection, 1220 // the relevant PhoneAccountHandle will be updated in resultConnection. 1221 PhoneAccountHandle phoneAccountHandle = 1222 resultConnection.getPhoneAccountHandle() == null 1223 ? request.getAccountHandle() : resultConnection.getPhoneAccountHandle(); 1224 Conferenceable c = maybeHoldCallsOnOtherSubs(phoneAccountHandle); 1225 if (c != null) { 1226 maybeHoldFuture = delayDialForOtherSubHold(phone, c, (success) -> { 1227 Log.i(this, "onCreateOutgoingConn emergency-" 1228 + " delayDialForOtherSubHold success = " + success); 1229 if (!success) { 1230 // Terminates the existing call to make way for the emergency call. 1231 hangup(c, android.telephony.DisconnectCause 1232 .OUTGOING_EMERGENCY_CALL_PLACED); 1233 } 1234 }); 1235 } 1236 } 1237 Consumer<Boolean> ddsSwitchConsumer = (result) -> { 1238 Log.i(this, "onCreateOutgoingConn emergency-" 1239 + " delayDialForDdsSwitch result = " + result); 1240 placeOutgoingConnection(request, resultConnection, phone); 1241 }; 1242 maybeHoldFuture.thenRun(() -> delayDialForDdsSwitch(phone, ddsSwitchConsumer)); 1243 return resultConnection; 1244 } 1245 } 1246 } 1247 placeOutgoingConnection(ConnectionRequest request, Connection resultConnection, Phone phone)1248 private Connection placeOutgoingConnection(ConnectionRequest request, 1249 Connection resultConnection, Phone phone) { 1250 // If there was a failure, the resulting connection will not be a TelephonyConnection, 1251 // so don't place the call! 1252 if (resultConnection instanceof TelephonyConnection) { 1253 if (request.getExtras() != null && request.getExtras().getBoolean( 1254 TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) { 1255 ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true); 1256 } 1257 placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request); 1258 } 1259 return resultConnection; 1260 } 1261 isEmergencyNumberTestNumber(String number)1262 private boolean isEmergencyNumberTestNumber(String number) { 1263 number = PhoneNumberUtils.stripSeparators(number); 1264 Map<Integer, List<EmergencyNumber>> list = 1265 mTelephonyManagerProxy.getCurrentEmergencyNumberList(); 1266 // Do not worry about which subscription the test emergency call is on yet, only detect that 1267 // it is an emergency. 1268 for (Integer sub : list.keySet()) { 1269 for (EmergencyNumber eNumber : list.get(sub)) { 1270 if (number.equals(eNumber.getNumber()) 1271 && eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)) { 1272 Log.i(this, "isEmergencyNumberTestNumber: " + number + " has been detected as " 1273 + "a test emergency number.,"); 1274 return true; 1275 } 1276 } 1277 } 1278 return false; 1279 } 1280 1281 /** 1282 * @return whether radio has recently been turned on for emergency call but hasn't actually 1283 * dialed the call yet. 1284 */ isEmergencyCallPending()1285 public boolean isEmergencyCallPending() { 1286 return mIsEmergencyCallPending; 1287 } 1288 1289 /** 1290 * Whether the cellular radio is power off because the device is on Bluetooth. 1291 */ isRadioPowerDownOnBluetooth()1292 private boolean isRadioPowerDownOnBluetooth() { 1293 final boolean allowed = mDeviceState.isRadioPowerDownAllowedOnBluetooth(this); 1294 final int cellOn = mDeviceState.getCellOnStatus(this); 1295 return (allowed && cellOn == PhoneConstants.CELL_ON_FLAG && !isRadioOn()); 1296 } 1297 1298 /** 1299 * Handle the onComplete callback of RadioOnStateListener. 1300 */ handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, Connection originalConnection, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, Phone phone)1301 private void handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, 1302 Connection originalConnection, ConnectionRequest request, String numberToDial, 1303 Uri handle, int originalPhoneType, Phone phone) { 1304 // Make sure the Call has not already been canceled by the user. 1305 if (originalConnection.getState() == Connection.STATE_DISCONNECTED) { 1306 Log.i(this, "Call disconnected before the outgoing call was placed. Skipping call " 1307 + "placement."); 1308 if (isEmergencyNumber) { 1309 // If call is already canceled by the user, notify modem to exit emergency call 1310 // mode by sending radio on with forEmergencyCall=false. 1311 for (Phone curPhone : mPhoneFactoryProxy.getPhones()) { 1312 curPhone.setRadioPower(true, false, false, true); 1313 } 1314 mIsEmergencyCallPending = false; 1315 } 1316 return; 1317 } 1318 if (isRadioReady) { 1319 if (!isEmergencyNumber) { 1320 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial, 1321 handle, originalPhoneType, false); 1322 } else { 1323 delayDialForDdsSwitch(phone, result -> { 1324 Log.i(this, "handleOnComplete - delayDialForDdsSwitch " 1325 + "result = " + result); 1326 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, 1327 numberToDial, handle, originalPhoneType, true); 1328 mIsEmergencyCallPending = false; 1329 }); 1330 } 1331 } else { 1332 if (isSatelliteBlockingCall(isEmergencyNumber)) { 1333 Log.w(LOG_TAG, "handleOnComplete, failed to turn off satellite modem"); 1334 closeOrDestroyConnection(originalConnection, 1335 mDisconnectCauseFactory.toTelecomDisconnectCause( 1336 android.telephony.DisconnectCause.SATELLITE_ENABLED, 1337 "Failed to turn off satellite modem.")); 1338 } else { 1339 Log.w(LOG_TAG, "handleOnComplete, failed to turn on radio"); 1340 closeOrDestroyConnection(originalConnection, 1341 mDisconnectCauseFactory.toTelecomDisconnectCause( 1342 android.telephony.DisconnectCause.POWER_OFF, 1343 "Failed to turn on radio.")); 1344 } 1345 mIsEmergencyCallPending = false; 1346 } 1347 } 1348 adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, boolean isEmergencyNumber)1349 private void adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, 1350 ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, 1351 boolean isEmergencyNumber) { 1352 // If the PhoneType of the Phone being used is different than the Default Phone, then we 1353 // need to create a new Connection using that PhoneType and replace it in Telecom. 1354 if (phone.getPhoneType() != originalPhoneType) { 1355 Connection repConnection = getTelephonyConnection(request, numberToDial, 1356 isEmergencyNumber, handle, phone); 1357 // If there was a failure, the resulting connection will not be a TelephonyConnection, 1358 // so don't place the call, just return! 1359 if (repConnection instanceof TelephonyConnection) { 1360 placeOutgoingConnection((TelephonyConnection) repConnection, phone, request); 1361 } 1362 // Notify Telecom of the new Connection type. 1363 // TODO: Switch out the underlying connection instead of creating a new 1364 // one and causing UI Jank. 1365 boolean noActiveSimCard = SubscriptionManagerService.getInstance() 1366 .getActiveSubInfoCount(phone.getContext().getOpPackageName(), 1367 phone.getContext().getAttributionTag()) == 0; 1368 // If there's no active sim card and the device is in emergency mode, use E account. 1369 addExistingConnection(mPhoneUtilsProxy.makePstnPhoneAccountHandleWithPrefix( 1370 phone, "", isEmergencyNumber && noActiveSimCard), repConnection); 1371 // Remove the old connection from Telecom after. 1372 closeOrDestroyConnection(connectionToEvaluate, 1373 mDisconnectCauseFactory.toTelecomDisconnectCause( 1374 android.telephony.DisconnectCause.OUTGOING_CANCELED, 1375 "Reconnecting outgoing Emergency Call.", 1376 phone.getPhoneId())); 1377 } else { 1378 placeOutgoingConnection((TelephonyConnection) connectionToEvaluate, phone, request); 1379 } 1380 } 1381 1382 /** 1383 * @return {@code true} if any other call is disabling the ability to add calls, {@code false} 1384 * otherwise. 1385 */ canAddCall()1386 private boolean canAddCall() { 1387 Collection<Connection> connections = getAllConnections(); 1388 for (Connection connection : connections) { 1389 if (connection.getExtras() != null && 1390 connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) { 1391 return false; 1392 } 1393 } 1394 return true; 1395 } 1396 getTelephonyConnection(final ConnectionRequest request, final String number, boolean isEmergencyNumber, final Uri handle, Phone phone)1397 private Connection getTelephonyConnection(final ConnectionRequest request, final String number, 1398 boolean isEmergencyNumber, final Uri handle, Phone phone) { 1399 1400 if (phone == null) { 1401 final Context context = getApplicationContext(); 1402 if (mDeviceState.shouldCheckSimStateBeforeOutgoingCall(this)) { 1403 // Check SIM card state before the outgoing call. 1404 // Start the SIM unlock activity if PIN_REQUIRED. 1405 final Phone defaultPhone = mPhoneFactoryProxy.getDefaultPhone(); 1406 final IccCard icc = defaultPhone.getIccCard(); 1407 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN; 1408 if (icc != null) { 1409 simState = icc.getState(); 1410 } 1411 if (simState == IccCardConstants.State.PIN_REQUIRED) { 1412 final String simUnlockUiPackage = context.getResources().getString( 1413 R.string.config_simUnlockUiPackage); 1414 final String simUnlockUiClass = context.getResources().getString( 1415 R.string.config_simUnlockUiClass); 1416 if (simUnlockUiPackage != null && simUnlockUiClass != null) { 1417 Intent simUnlockIntent = new Intent().setComponent(new ComponentName( 1418 simUnlockUiPackage, simUnlockUiClass)); 1419 simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1420 try { 1421 context.startActivity(simUnlockIntent); 1422 } catch (ActivityNotFoundException exception) { 1423 Log.e(this, exception, "Unable to find SIM unlock UI activity."); 1424 } 1425 } 1426 return Connection.createFailedConnection( 1427 mDisconnectCauseFactory.toTelecomDisconnectCause( 1428 android.telephony.DisconnectCause.OUT_OF_SERVICE, 1429 "SIM_STATE_PIN_REQUIRED")); 1430 } 1431 } 1432 1433 Log.d(this, "onCreateOutgoingConnection, phone is null"); 1434 return Connection.createFailedConnection( 1435 mDisconnectCauseFactory.toTelecomDisconnectCause( 1436 android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null")); 1437 } 1438 1439 // Check both voice & data RAT to enable normal CS call, 1440 // when voice RAT is OOS but Data RAT is present. 1441 int state = phone.getServiceState().getState(); 1442 if (state == ServiceState.STATE_OUT_OF_SERVICE) { 1443 int dataNetType = phone.getServiceState().getDataNetworkType(); 1444 if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE || 1445 dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA || 1446 dataNetType == TelephonyManager.NETWORK_TYPE_NR) { 1447 state = phone.getServiceState().getDataRegistrationState(); 1448 } 1449 } 1450 1451 // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if 1452 // carrier configuration specifies that we cannot make non-emergency calls in ECM mode. 1453 if (!isEmergencyNumber && phone.isInEcm()) { 1454 boolean allowNonEmergencyCalls = true; 1455 CarrierConfigManager cfgManager = (CarrierConfigManager) 1456 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1457 if (cfgManager != null) { 1458 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId()) 1459 .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL); 1460 } 1461 1462 if (!allowNonEmergencyCalls) { 1463 return Connection.createFailedConnection( 1464 mDisconnectCauseFactory.toTelecomDisconnectCause( 1465 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY, 1466 "Cannot make non-emergency call in ECM mode.", 1467 phone.getPhoneId())); 1468 } 1469 } 1470 1471 if (!isEmergencyNumber) { 1472 switch (state) { 1473 case ServiceState.STATE_IN_SERVICE: 1474 case ServiceState.STATE_EMERGENCY_ONLY: 1475 break; 1476 case ServiceState.STATE_OUT_OF_SERVICE: 1477 if (phone.isUtEnabled() && number.endsWith("#")) { 1478 Log.d(this, "onCreateOutgoingConnection dial for UT"); 1479 break; 1480 } else { 1481 return Connection.createFailedConnection( 1482 mDisconnectCauseFactory.toTelecomDisconnectCause( 1483 android.telephony.DisconnectCause.OUT_OF_SERVICE, 1484 "ServiceState.STATE_OUT_OF_SERVICE", 1485 phone.getPhoneId())); 1486 } 1487 case ServiceState.STATE_POWER_OFF: 1488 // Don't disconnect if radio is power off because the device is on Bluetooth. 1489 if (isRadioPowerDownOnBluetooth()) { 1490 break; 1491 } 1492 return Connection.createFailedConnection( 1493 mDisconnectCauseFactory.toTelecomDisconnectCause( 1494 android.telephony.DisconnectCause.POWER_OFF, 1495 "ServiceState.STATE_POWER_OFF", 1496 phone.getPhoneId())); 1497 default: 1498 Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state); 1499 return Connection.createFailedConnection( 1500 mDisconnectCauseFactory.toTelecomDisconnectCause( 1501 android.telephony.DisconnectCause.OUTGOING_FAILURE, 1502 "Unknown service state " + state, 1503 phone.getPhoneId())); 1504 } 1505 } 1506 1507 final boolean isTtyModeEnabled = mDeviceState.isTtyModeEnabled(this); 1508 if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled 1509 && !isEmergencyNumber) { 1510 return Connection.createFailedConnection(mDisconnectCauseFactory.toTelecomDisconnectCause( 1511 android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED, 1512 null, phone.getPhoneId())); 1513 } 1514 1515 // Check for additional limits on CDMA phones. 1516 final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone); 1517 if (failedConnection != null) { 1518 return failedConnection; 1519 } 1520 1521 // Check roaming status to see if we should block custom call forwarding codes 1522 if (blockCallForwardingNumberWhileRoaming(phone, number)) { 1523 return Connection.createFailedConnection( 1524 mDisconnectCauseFactory.toTelecomDisconnectCause( 1525 android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING, 1526 "Call forwarding while roaming", 1527 phone.getPhoneId())); 1528 } 1529 1530 PhoneAccountHandle accountHandle = adjustAccountHandle(phone, request.getAccountHandle()); 1531 final TelephonyConnection connection = 1532 createConnectionFor(phone, null, true /* isOutgoing */, accountHandle, 1533 request.getTelecomCallId(), request.isAdhocConferenceCall()); 1534 if (connection == null) { 1535 return Connection.createFailedConnection( 1536 mDisconnectCauseFactory.toTelecomDisconnectCause( 1537 android.telephony.DisconnectCause.OUTGOING_FAILURE, 1538 "Invalid phone type", 1539 phone.getPhoneId())); 1540 } 1541 if (!Objects.equals(request.getAccountHandle(), accountHandle)) { 1542 Log.i(this, "onCreateOutgoingConnection, update phoneAccountHandle, accountHandle = " 1543 + accountHandle); 1544 connection.setPhoneAccountHandle(accountHandle); 1545 } 1546 connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED); 1547 connection.setTelephonyConnectionInitializing(); 1548 connection.setTelephonyVideoState(request.getVideoState()); 1549 connection.setRttTextStream(request.getRttTextStream()); 1550 connection.setTtyEnabled(isTtyModeEnabled); 1551 connection.setIsAdhocConferenceCall(request.isAdhocConferenceCall()); 1552 connection.setParticipants(request.getParticipants()); 1553 return connection; 1554 } 1555 1556 @Override onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1557 public Connection onCreateIncomingConnection( 1558 PhoneAccountHandle connectionManagerPhoneAccount, 1559 ConnectionRequest request) { 1560 Log.i(this, "onCreateIncomingConnection, request: " + request); 1561 // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM), 1562 // make sure the PhoneAccount lookup retrieves the default Emergency Phone. 1563 PhoneAccountHandle accountHandle = request.getAccountHandle(); 1564 boolean isEmergency = false; 1565 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 1566 accountHandle.getId())) { 1567 Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " + 1568 "Treat as an Emergency Call."); 1569 isEmergency = true; 1570 } 1571 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 1572 /* Note: when not an emergency, handle can be null for unknown callers */ 1573 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 1574 if (phone == null) { 1575 return Connection.createFailedConnection( 1576 mDisconnectCauseFactory.toTelecomDisconnectCause( 1577 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 1578 "Phone is null")); 1579 } 1580 1581 Bundle extras = request.getExtras(); 1582 String disconnectMessage = null; 1583 if (extras.containsKey(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE)) { 1584 disconnectMessage = extras.getString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE); 1585 Log.i(this, "onCreateIncomingConnection Disconnect message " + disconnectMessage); 1586 } 1587 1588 Call call = phone.getRingingCall(); 1589 if (!call.getState().isRinging() 1590 || (disconnectMessage != null 1591 && disconnectMessage.equals(TelecomManager.CALL_AUTO_DISCONNECT_MESSAGE_STRING))) { 1592 Log.i(this, "onCreateIncomingConnection, no ringing call"); 1593 Connection connection = Connection.createFailedConnection( 1594 mDisconnectCauseFactory.toTelecomDisconnectCause( 1595 android.telephony.DisconnectCause.INCOMING_MISSED, 1596 "Found no ringing call", 1597 phone.getPhoneId())); 1598 1599 long time = extras.getLong(TelecomManager.EXTRA_CALL_CREATED_EPOCH_TIME_MILLIS); 1600 if (time != 0) { 1601 Log.i(this, "onCreateIncomingConnection. Set connect time info."); 1602 connection.setConnectTimeMillis(time); 1603 } 1604 1605 Uri address = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS); 1606 if (address != null) { 1607 Log.i(this, "onCreateIncomingConnection. Set caller id info."); 1608 connection.setAddress(address, TelecomManager.PRESENTATION_ALLOWED); 1609 } 1610 1611 return connection; 1612 } 1613 1614 // If there are multiple Connections tracked in a call, grab the latest, since it is most 1615 // likely to be the incoming call. 1616 com.android.internal.telephony.Connection originalConnection = call.getLatestConnection(); 1617 if (isOriginalConnectionKnown(originalConnection)) { 1618 Log.i(this, "onCreateIncomingConnection, original connection already registered"); 1619 return Connection.createCanceledConnection(); 1620 } 1621 1622 TelephonyConnection connection = 1623 createConnectionFor(phone, originalConnection, false /* isOutgoing */, 1624 request.getAccountHandle(), request.getTelecomCallId(), 1625 request.isAdhocConferenceCall()); 1626 1627 handleIncomingRtt(request, originalConnection); 1628 if (connection == null) { 1629 return Connection.createCanceledConnection(); 1630 } else { 1631 // Add extra to call if answering this incoming call would cause an in progress call on 1632 // another subscription to be disconnected. 1633 maybeIndicateAnsweringWillDisconnect(connection, request.getAccountHandle()); 1634 1635 connection.setTtyEnabled(mDeviceState.isTtyModeEnabled(getApplicationContext())); 1636 return connection; 1637 } 1638 } 1639 handleIncomingRtt(ConnectionRequest request, com.android.internal.telephony.Connection originalConnection)1640 private void handleIncomingRtt(ConnectionRequest request, 1641 com.android.internal.telephony.Connection originalConnection) { 1642 if (originalConnection == null 1643 || originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) { 1644 if (request.isRequestingRtt()) { 1645 Log.w(this, "Requesting RTT on non-IMS call, ignoring"); 1646 } 1647 return; 1648 } 1649 1650 ImsPhoneConnection imsOriginalConnection = (ImsPhoneConnection) originalConnection; 1651 if (!request.isRequestingRtt()) { 1652 if (imsOriginalConnection.isRttEnabledForCall()) { 1653 Log.w(this, "Incoming call requested RTT but we did not get a RttTextStream"); 1654 } 1655 return; 1656 } 1657 1658 Log.i(this, "Setting RTT stream on ImsPhoneConnection in case we need it later"); 1659 imsOriginalConnection.setCurrentRttTextStream(request.getRttTextStream()); 1660 1661 if (!imsOriginalConnection.isRttEnabledForCall()) { 1662 if (request.isRequestingRtt()) { 1663 Log.w(this, "Incoming call processed as RTT but did not come in as one. Ignoring"); 1664 } 1665 return; 1666 } 1667 1668 Log.i(this, "Setting the call to be answered with RTT on."); 1669 imsOriginalConnection.getImsCall().setAnswerWithRtt(); 1670 } 1671 1672 /** 1673 * Called by the {@link ConnectionService} when a newly created {@link Connection} has been 1674 * added to the {@link ConnectionService} and sent to Telecom. Here it is safe to send 1675 * connection events. 1676 * 1677 * @param connection the {@link Connection}. 1678 */ 1679 @Override onCreateConnectionComplete(Connection connection)1680 public void onCreateConnectionComplete(Connection connection) { 1681 if (connection instanceof TelephonyConnection) { 1682 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 1683 maybeSendInternationalCallEvent(telephonyConnection); 1684 } 1685 } 1686 1687 @Override onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1688 public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, 1689 ConnectionRequest request) { 1690 Log.i(this, "onCreateIncomingConnectionFailed, request: " + request); 1691 // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM), 1692 // make sure the PhoneAccount lookup retrieves the default Emergency Phone. 1693 PhoneAccountHandle accountHandle = request.getAccountHandle(); 1694 boolean isEmergency = false; 1695 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 1696 accountHandle.getId())) { 1697 Log.w(this, "onCreateIncomingConnectionFailed:Emergency call failed... "); 1698 isEmergency = true; 1699 } 1700 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 1701 /* Note: when not an emergency, handle can be null for unknown callers */ 1702 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 1703 if (phone == null) { 1704 Log.w(this, "onCreateIncomingConnectionFailed: can not find corresponding phone."); 1705 return; 1706 } 1707 1708 Call call = phone.getRingingCall(); 1709 if (!call.getState().isRinging()) { 1710 Log.w(this, "onCreateIncomingConnectionFailed, no ringing call found for failed call"); 1711 return; 1712 } 1713 1714 com.android.internal.telephony.Connection originalConnection = 1715 call.getState() == Call.State.WAITING 1716 ? call.getLatestConnection() : call.getEarliestConnection(); 1717 TelephonyConnection knownConnection = 1718 getConnectionForOriginalConnection(originalConnection); 1719 if (knownConnection != null) { 1720 Log.w(this, "onCreateIncomingConnectionFailed, original connection already registered." 1721 + " Hanging it up."); 1722 knownConnection.onAbort(); 1723 return; 1724 } 1725 1726 TelephonyConnection connection = 1727 createConnectionFor(phone, originalConnection, false /* isOutgoing */, 1728 request.getAccountHandle(), request.getTelecomCallId()); 1729 if (connection == null) { 1730 Log.w(this, "onCreateIncomingConnectionFailed, TelephonyConnection created as null, " 1731 + "ignoring."); 1732 return; 1733 } 1734 1735 // We have to do all of this work because in some cases, hanging up the call maps to 1736 // different underlying signaling (CDMA), which is already encapsulated in 1737 // TelephonyConnection. 1738 connection.onReject(); 1739 connection.close(); 1740 } 1741 1742 /** 1743 * Called by the {@link ConnectionService} when a newly created {@link Conference} has been 1744 * added to the {@link ConnectionService} and sent to Telecom. Here it is safe to send 1745 * connection events. 1746 * 1747 * @param conference the {@link Conference}. 1748 */ 1749 @Override onCreateConferenceComplete(Conference conference)1750 public void onCreateConferenceComplete(Conference conference) { 1751 if (conference instanceof ImsConference) { 1752 ImsConference imsConference = (ImsConference)conference; 1753 TelephonyConnection telephonyConnection = 1754 (TelephonyConnection)(imsConference.getConferenceHost()); 1755 maybeSendInternationalCallEvent(telephonyConnection); 1756 } 1757 } 1758 onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1759 public void onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount, 1760 ConnectionRequest request) { 1761 Log.i(this, "onCreateIncomingConferenceFailed, request: " + request); 1762 onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request); 1763 } 1764 1765 @Override triggerConferenceRecalculate()1766 public void triggerConferenceRecalculate() { 1767 if (mTelephonyConferenceController.shouldRecalculate()) { 1768 mTelephonyConferenceController.recalculate(); 1769 } 1770 } 1771 1772 @Override onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1773 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 1774 ConnectionRequest request) { 1775 Log.i(this, "onCreateUnknownConnection, request: " + request); 1776 // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's 1777 // Emergency PhoneAccount 1778 PhoneAccountHandle accountHandle = request.getAccountHandle(); 1779 boolean isEmergency = false; 1780 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 1781 accountHandle.getId())) { 1782 Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " + 1783 "Treat as an Emergency Call."); 1784 isEmergency = true; 1785 } 1786 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 1787 /* Note: when not an emergency, handle can be null for unknown callers */ 1788 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 1789 if (phone == null) { 1790 return Connection.createFailedConnection( 1791 mDisconnectCauseFactory.toTelecomDisconnectCause( 1792 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 1793 "Phone is null")); 1794 } 1795 Bundle extras = request.getExtras(); 1796 1797 final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>(); 1798 1799 // Handle the case where an unknown connection has an IMS external call ID specified; we can 1800 // skip the rest of the guesswork and just grad that unknown call now. 1801 if (phone.getImsPhone() != null && extras != null && 1802 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) { 1803 1804 ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); 1805 ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker(); 1806 int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, 1807 -1); 1808 1809 if (externalCallTracker != null) { 1810 com.android.internal.telephony.Connection connection = 1811 externalCallTracker.getConnectionById(externalCallId); 1812 1813 if (connection != null) { 1814 allConnections.add(connection); 1815 } 1816 } 1817 } 1818 1819 if (allConnections.isEmpty()) { 1820 final Call ringingCall = phone.getRingingCall(); 1821 if (ringingCall.hasConnections()) { 1822 allConnections.addAll(ringingCall.getConnections()); 1823 } 1824 final Call foregroundCall = phone.getForegroundCall(); 1825 if ((foregroundCall.getState() != Call.State.DISCONNECTED) 1826 && (foregroundCall.hasConnections())) { 1827 allConnections.addAll(foregroundCall.getConnections()); 1828 } 1829 if (phone.getImsPhone() != null) { 1830 final Call imsFgCall = phone.getImsPhone().getForegroundCall(); 1831 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall 1832 .hasConnections()) { 1833 allConnections.addAll(imsFgCall.getConnections()); 1834 } 1835 } 1836 final Call backgroundCall = phone.getBackgroundCall(); 1837 if (backgroundCall.hasConnections()) { 1838 allConnections.addAll(phone.getBackgroundCall().getConnections()); 1839 } 1840 } 1841 1842 com.android.internal.telephony.Connection unknownConnection = null; 1843 for (com.android.internal.telephony.Connection telephonyConnection : allConnections) { 1844 if (!isOriginalConnectionKnown(telephonyConnection)) { 1845 unknownConnection = telephonyConnection; 1846 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection); 1847 break; 1848 } 1849 } 1850 1851 if (unknownConnection == null) { 1852 Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection."); 1853 return Connection.createCanceledConnection(); 1854 } 1855 1856 // We should rely on the originalConnection to get the video state. The request coming 1857 // from Telecom does not know the video state of the unknown call. 1858 int videoState = unknownConnection != null ? unknownConnection.getVideoState() : 1859 VideoProfile.STATE_AUDIO_ONLY; 1860 1861 TelephonyConnection connection = 1862 createConnectionFor(phone, unknownConnection, 1863 !unknownConnection.isIncoming() /* isOutgoing */, 1864 request.getAccountHandle(), request.getTelecomCallId() 1865 ); 1866 1867 if (connection == null) { 1868 return Connection.createCanceledConnection(); 1869 } else { 1870 connection.updateState(); 1871 return connection; 1872 } 1873 } 1874 1875 /** 1876 * Conferences two connections. 1877 * 1878 * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has 1879 * a limitation in that it can only specify conferenceables which are instances of 1880 * {@link android.telecom.RemoteConnection}. In the case of an {@link ImsConference}, the 1881 * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge 1882 * a {@link Conference} and a {@link Connection}. As a result when, merging a 1883 * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference} 1884 * require merging a {@link ConferenceParticipantConnection} which is a child of the 1885 * {@link Conference} with a {@link TelephonyConnection}. The 1886 * {@link ConferenceParticipantConnection} class does not have the capability to initiate a 1887 * conference merge, so we need to call 1888 * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or 1889 * {@code connection2}, one of which is an instance of {@link TelephonyConnection}. 1890 * 1891 * @param connection1 A connection to merge into a conference call. 1892 * @param connection2 A connection to merge into a conference call. 1893 */ 1894 @Override onConference(Connection connection1, Connection connection2)1895 public void onConference(Connection connection1, Connection connection2) { 1896 if (connection1 instanceof TelephonyConnection) { 1897 ((TelephonyConnection) connection1).performConference(connection2); 1898 } else if (connection2 instanceof TelephonyConnection) { 1899 ((TelephonyConnection) connection2).performConference(connection1); 1900 } else { 1901 Log.w(this, "onConference - cannot merge connections " + 1902 "Connection1: %s, Connection2: %2", connection1, connection2); 1903 } 1904 } 1905 1906 @Override onConnectionAdded(Connection connection)1907 public void onConnectionAdded(Connection connection) { 1908 if (connection instanceof Holdable && !isExternalConnection(connection)) { 1909 mHoldTracker.addHoldable((Holdable) connection); 1910 } 1911 } 1912 1913 @Override onConnectionRemoved(Connection connection)1914 public void onConnectionRemoved(Connection connection) { 1915 if (connection instanceof Holdable && !isExternalConnection(connection)) { 1916 mHoldTracker.removeHoldable((Holdable) connection); 1917 } 1918 } 1919 1920 @Override onConferenceAdded(Conference conference)1921 public void onConferenceAdded(Conference conference) { 1922 if (conference instanceof Holdable) { 1923 mHoldTracker.addHoldable((Holdable) conference); 1924 } 1925 } 1926 1927 @Override onConferenceRemoved(Conference conference)1928 public void onConferenceRemoved(Conference conference) { 1929 if (conference instanceof Holdable) { 1930 mHoldTracker.removeHoldable((Holdable) conference); 1931 } 1932 } 1933 isExternalConnection(Connection connection)1934 private boolean isExternalConnection(Connection connection) { 1935 return (connection.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) 1936 == Connection.PROPERTY_IS_EXTERNAL_CALL; 1937 } 1938 blockCallForwardingNumberWhileRoaming(Phone phone, String number)1939 private boolean blockCallForwardingNumberWhileRoaming(Phone phone, String number) { 1940 if (phone == null || TextUtils.isEmpty(number) || !phone.getServiceState().getRoaming()) { 1941 return false; 1942 } 1943 boolean allowPrefixIms = true; 1944 String[] blockPrefixes = null; 1945 CarrierConfigManager cfgManager = (CarrierConfigManager) 1946 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1947 if (cfgManager != null) { 1948 allowPrefixIms = cfgManager.getConfigForSubId(phone.getSubId()).getBoolean( 1949 CarrierConfigManager.KEY_SUPPORT_IMS_CALL_FORWARDING_WHILE_ROAMING_BOOL, 1950 true); 1951 if (allowPrefixIms && useImsForAudioOnlyCall(phone)) { 1952 return false; 1953 } 1954 blockPrefixes = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray( 1955 CarrierConfigManager.KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY); 1956 } 1957 1958 if (blockPrefixes != null) { 1959 for (String prefix : blockPrefixes) { 1960 if (number.startsWith(prefix)) { 1961 return true; 1962 } 1963 } 1964 } 1965 return false; 1966 } 1967 useImsForAudioOnlyCall(Phone phone)1968 private boolean useImsForAudioOnlyCall(Phone phone) { 1969 Phone imsPhone = phone.getImsPhone(); 1970 1971 return imsPhone != null 1972 && (imsPhone.isVoiceOverCellularImsEnabled() || imsPhone.isWifiCallingEnabled()) 1973 && (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE); 1974 } 1975 isRadioOn()1976 private boolean isRadioOn() { 1977 boolean result = false; 1978 for (Phone phone : mPhoneFactoryProxy.getPhones()) { 1979 result |= phone.isRadioOn(); 1980 } 1981 return result; 1982 } 1983 isSatelliteBlockingCall(boolean isEmergencyNumber)1984 private boolean isSatelliteBlockingCall(boolean isEmergencyNumber) { 1985 if (isEmergencyNumber) { 1986 return mSatelliteController.isSatelliteEnabled(); 1987 } else { 1988 return mSatelliteController.isDemoModeEnabled(); 1989 } 1990 } 1991 makeCachedConnectionPhonePair( TelephonyConnection c)1992 private Pair<WeakReference<TelephonyConnection>, Queue<Phone>> makeCachedConnectionPhonePair( 1993 TelephonyConnection c) { 1994 Queue<Phone> phones = new LinkedList<>(Arrays.asList(mPhoneFactoryProxy.getPhones())); 1995 return new Pair<>(new WeakReference<>(c), phones); 1996 } 1997 1998 // Update the mEmergencyRetryCache by removing the Phone used to call the last failed emergency 1999 // number and then moving it to the back of the queue if it is not a permanent failure cause 2000 // from the modem. updateCachedConnectionPhonePair(TelephonyConnection c, Phone phone, boolean isPermanentFailure)2001 private void updateCachedConnectionPhonePair(TelephonyConnection c, Phone phone, 2002 boolean isPermanentFailure) { 2003 // No cache exists, create a new one. 2004 if (mEmergencyRetryCache == null) { 2005 Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache"); 2006 mEmergencyRetryCache = makeCachedConnectionPhonePair(c); 2007 // Cache is stale, create a new one with the new TelephonyConnection. 2008 } else if (mEmergencyRetryCache.first.get() != c) { 2009 Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating."); 2010 mEmergencyRetryCache = makeCachedConnectionPhonePair(c); 2011 } 2012 2013 Queue<Phone> cachedPhones = mEmergencyRetryCache.second; 2014 // Need to refer default phone considering ImsPhone because 2015 // cachedPhones is a list that contains default phones. 2016 Phone phoneUsed = phone.getDefaultPhone(); 2017 if (phoneUsed == null) { 2018 return; 2019 } 2020 // Remove phone used from the list, but for temporary fail cause, it will be added 2021 // back to list further in this method. However in case of permanent failure, the 2022 // phone shouldn't be reused, hence it will not be added back again. 2023 cachedPhones.remove(phoneUsed); 2024 Log.i(this, "updateCachedConnectionPhonePair, isPermanentFailure:" + isPermanentFailure); 2025 if (!isPermanentFailure) { 2026 // In case of temporary failure, add the phone back, this will result adding it 2027 // to tail of list mEmergencyRetryCache.second, giving other phone more 2028 // priority and that is what we want. 2029 cachedPhones.offer(phoneUsed); 2030 } 2031 } 2032 2033 /** 2034 * Updates a cache containing all of the slots that are available for redial at any point. 2035 * 2036 * - If a Connection returns with the disconnect cause EMERGENCY_TEMP_FAILURE, keep that phone 2037 * in the cache, but move it to the lowest priority in the list. Then, place the emergency call 2038 * on the next phone in the list. 2039 * - If a Connection returns with the disconnect cause EMERGENCY_PERM_FAILURE, remove that phone 2040 * from the cache and pull another phone from the cache to place the emergency call. 2041 * 2042 * This will continue until there are no more slots to dial on. 2043 */ 2044 @VisibleForTesting retryOutgoingOriginalConnection(TelephonyConnection c, Phone phone, boolean isPermanentFailure)2045 public void retryOutgoingOriginalConnection(TelephonyConnection c, 2046 Phone phone, boolean isPermanentFailure) { 2047 int phoneId = (phone == null) ? -1 : phone.getPhoneId(); 2048 updateCachedConnectionPhonePair(c, phone, isPermanentFailure); 2049 // Pull next phone to use from the cache or null if it is empty 2050 Phone newPhoneToUse = (mEmergencyRetryCache.second != null) 2051 ? mEmergencyRetryCache.second.peek() : null; 2052 if (newPhoneToUse != null) { 2053 int videoState = c.getVideoState(); 2054 Bundle connExtras = c.getExtras(); 2055 Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse); 2056 c.clearOriginalConnection(); 2057 if (phoneId != newPhoneToUse.getPhoneId()) { 2058 if (mTelephonyManagerProxy.getMaxNumberOfSimultaneouslyActiveSims() < 2) { 2059 disconnectAllCallsOnOtherSubs( 2060 mPhoneUtilsProxy.makePstnPhoneAccountHandle(newPhoneToUse)); 2061 } 2062 updatePhoneAccount(c, newPhoneToUse); 2063 } 2064 if (mDomainSelectionResolver.isDomainSelectionSupported()) { 2065 onEmergencyRedial(c, newPhoneToUse); 2066 return; 2067 } 2068 placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras); 2069 } else { 2070 // We have run out of Phones to use. Disconnect the call and destroy the connection. 2071 Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting."); 2072 closeOrDestroyConnection(c, new DisconnectCause(DisconnectCause.ERROR)); 2073 } 2074 } 2075 updatePhoneAccount(TelephonyConnection connection, Phone phone)2076 private void updatePhoneAccount(TelephonyConnection connection, Phone phone) { 2077 PhoneAccountHandle pHandle = mPhoneUtilsProxy.makePstnPhoneAccountHandle(phone); 2078 // For ECall handling on MSIM, until the request reaches here (i.e PhoneApp), we don't know 2079 // on which phone account ECall can be placed. After deciding, we should notify Telecom of 2080 // the change so that the proper PhoneAccount can be displayed. 2081 Log.i(this, "updatePhoneAccount setPhoneAccountHandle, account = " + pHandle); 2082 connection.setPhoneAccountHandle(pHandle); 2083 } 2084 placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)2085 private void placeOutgoingConnection( 2086 TelephonyConnection connection, Phone phone, ConnectionRequest request) { 2087 placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras()); 2088 } 2089 placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras)2090 private void placeOutgoingConnection( 2091 TelephonyConnection connection, Phone phone, int videoState, Bundle extras) { 2092 2093 String number = (connection.getAddress() != null) 2094 ? connection.getAddress().getSchemeSpecificPart() 2095 : ""; 2096 2097 if (showDataDialog(phone, number)) { 2098 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 2099 android.telephony.DisconnectCause.DIALED_MMI, "UT is not available")); 2100 return; 2101 } 2102 2103 if (extras != null && extras.containsKey(TelecomManager.EXTRA_OUTGOING_PICTURE)) { 2104 ParcelUuid uuid = extras.getParcelable(TelecomManager.EXTRA_OUTGOING_PICTURE); 2105 CallComposerPictureManager.getInstance(phone.getContext(), phone.getSubId()) 2106 .storeUploadedPictureToCallLog(uuid.getUuid(), (uri) -> { 2107 if (uri != null) { 2108 try { 2109 Bundle b = new Bundle(); 2110 b.putParcelable(TelecomManager.EXTRA_PICTURE_URI, uri); 2111 connection.putTelephonyExtras(b); 2112 } catch (Exception e) { 2113 Log.e(this, e, "Couldn't set picture extra on outgoing call"); 2114 } 2115 } 2116 }); 2117 } 2118 2119 final com.android.internal.telephony.Connection originalConnection; 2120 try { 2121 if (phone != null) { 2122 boolean isEmergency = mTelephonyManagerProxy.isCurrentEmergencyNumber(number); 2123 Log.i(this, "placeOutgoingConnection isEmergency=" + isEmergency); 2124 if (isEmergency) { 2125 handleEmergencyCallStartedForSatelliteSOSMessageRecommender(connection, phone); 2126 if (!getAllConnections().isEmpty()) { 2127 if (!shouldHoldForEmergencyCall(phone)) { 2128 // If we do not support holding ongoing calls for an outgoing 2129 // emergency call, disconnect the ongoing calls. 2130 for (Connection c : getAllConnections()) { 2131 if (!c.equals(connection) 2132 && c.getState() != Connection.STATE_DISCONNECTED 2133 && c instanceof TelephonyConnection) { 2134 ((TelephonyConnection) c).hangup( 2135 android.telephony.DisconnectCause 2136 .OUTGOING_EMERGENCY_CALL_PLACED); 2137 } 2138 } 2139 for (Conference c : getAllConferences()) { 2140 if (c.getState() != Connection.STATE_DISCONNECTED 2141 && c instanceof Conference) { 2142 ((Conference) c).onDisconnect(); 2143 } 2144 } 2145 } else if (!isVideoCallHoldAllowed(phone)) { 2146 // If we do not support holding ongoing video call for an outgoing 2147 // emergency call, disconnect the ongoing video call. 2148 for (Connection c : getAllConnections()) { 2149 if (!c.equals(connection) 2150 && c.getState() == Connection.STATE_ACTIVE 2151 && VideoProfile.isVideo(c.getVideoState()) 2152 && c instanceof TelephonyConnection) { 2153 ((TelephonyConnection) c).hangup( 2154 android.telephony.DisconnectCause 2155 .OUTGOING_EMERGENCY_CALL_PLACED); 2156 break; 2157 } 2158 } 2159 } 2160 } 2161 if (mDomainSelectionResolver.isDomainSelectionSupported()) { 2162 if (isNormalRouting(phone, number) 2163 && handleOutgoingCallConnection(number, connection, 2164 phone, videoState)) { 2165 /** Normal routing emergency number shall be handled 2166 * by normal call domain selctor.*/ 2167 Log.i(this, "placeOutgoingConnection normal routing number"); 2168 return; 2169 } 2170 } 2171 } else if (handleOutgoingCallConnection(number, connection, 2172 phone, videoState)) { 2173 return; 2174 } 2175 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder() 2176 .setVideoState(videoState) 2177 .setIntentExtras(extras) 2178 .setRttTextStream(connection.getRttTextStream()) 2179 .build(), 2180 // We need to wait until the phone has been chosen in GsmCdmaPhone to 2181 // register for the associated TelephonyConnection call event listeners. 2182 connection::registerForCallEvents); 2183 } else { 2184 originalConnection = null; 2185 } 2186 } catch (CallStateException e) { 2187 Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e); 2188 connection.unregisterForCallEvents(); 2189 handleCallStateException(e, connection, phone); 2190 return; 2191 } 2192 if (originalConnection == null) { 2193 int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 2194 // On GSM phones, null connection means that we dialed an MMI code 2195 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM || 2196 phone.isUtEnabled()) { 2197 Log.d(this, "dialed MMI code"); 2198 int subId = phone.getSubId(); 2199 Log.d(this, "subId: "+subId); 2200 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; 2201 final Intent intent = new Intent(this, MMIDialogActivity.class); 2202 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 2203 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 2204 if (SubscriptionManager.isValidSubscriptionId(subId)) { 2205 SubscriptionManager.putSubscriptionIdExtra(intent, subId); 2206 } 2207 startActivity(intent); 2208 } 2209 Log.d(this, "placeOutgoingConnection, phone.dial returned null"); 2210 connection.setTelephonyConnectionDisconnected( 2211 mDisconnectCauseFactory.toTelecomDisconnectCause(telephonyDisconnectCause, 2212 "Connection is null", phone.getPhoneId())); 2213 connection.close(); 2214 } else { 2215 if (!getMainThreadHandler().getLooper().isCurrentThread()) { 2216 Log.w(this, "placeOriginalConnection - Unexpected, this call " 2217 + "should always be on the main thread."); 2218 getMainThreadHandler().post(() -> { 2219 if (connection.getOriginalConnection() == null) { 2220 connection.setOriginalConnection(originalConnection); 2221 } 2222 }); 2223 } else { 2224 connection.setOriginalConnection(originalConnection); 2225 } 2226 } 2227 } 2228 handleOutgoingCallConnectionByCallDomainSelection( int domain, Phone phone, String number, int videoState)2229 private void handleOutgoingCallConnectionByCallDomainSelection( 2230 int domain, Phone phone, String number, int videoState) { 2231 Log.d(this, "Call Domain Selected : " + domain); 2232 try { 2233 Bundle extras = mNormalCallConnection.getExtras(); 2234 if (extras == null) { 2235 extras = new Bundle(); 2236 } 2237 extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain); 2238 // Add flag to bundle for comparing legacy and new domain selection results. When 2239 // EXTRA_COMPARE_DOMAIN flag is true, legacy domain selection result is used for 2240 // placing the call and if both the results are not same then bug report is generated. 2241 DeviceConfig.Properties properties = //read all telephony properties 2242 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TELEPHONY); 2243 boolean compareDomainSelection = 2244 properties.getBoolean(KEY_DOMAIN_COMPARE_FEATURE_ENABLED_FLAG, false); 2245 extras.putBoolean(PhoneConstants.EXTRA_COMPARE_DOMAIN, compareDomainSelection); 2246 2247 if (phone != null) { 2248 Log.v(LOG_TAG, "Call dialing. Domain: " + domain); 2249 com.android.internal.telephony.Connection connection = 2250 phone.dial(number, new ImsPhone.ImsDialArgs.Builder() 2251 .setVideoState(videoState) 2252 .setIntentExtras(extras) 2253 .setRttTextStream(mNormalCallConnection.getRttTextStream()) 2254 .setIsWpsCall(NormalCallDomainSelectionConnection 2255 .isWpsCall(number)) 2256 .build(), 2257 mNormalCallConnection::registerForCallEvents); 2258 2259 mNormalCallConnection.setOriginalConnection(connection); 2260 mNormalCallConnection.addTelephonyConnectionListener(mNormalCallConnectionListener); 2261 return; 2262 } else { 2263 Log.w(this, "placeOutgoingCallConnection. Dialing failed. Phone is null"); 2264 mNormalCallConnection.setTelephonyConnectionDisconnected( 2265 mDisconnectCauseFactory.toTelecomDisconnectCause( 2266 android.telephony.DisconnectCause.OUTGOING_FAILURE, 2267 "Phone is null", phone.getPhoneId())); 2268 mNormalCallConnection.close(); 2269 } 2270 } catch (CallStateException e) { 2271 Log.e(this, e, "Call placeOutgoingCallConnection, phone.dial exception: " + e); 2272 mNormalCallConnection.unregisterForCallEvents(); 2273 handleCallStateException(e, mNormalCallConnection, phone); 2274 } catch (Exception e) { 2275 Log.e(this, e, "Call exception in placeOutgoingCallConnection:" + e); 2276 mNormalCallConnection.unregisterForCallEvents(); 2277 mNormalCallConnection.setTelephonyConnectionDisconnected(DisconnectCauseUtil 2278 .toTelecomDisconnectCause(android.telephony.DisconnectCause.OUTGOING_FAILURE, 2279 e.getMessage(), phone.getPhoneId())); 2280 mNormalCallConnection.close(); 2281 } 2282 if (mDomainSelectionConnection != null) { 2283 mDomainSelectionConnection.finishSelection(); 2284 mDomainSelectionConnection = null; 2285 } 2286 mNormalCallConnection = null; 2287 } 2288 handleOutgoingCallConnection( String number, TelephonyConnection connection, Phone phone, int videoState)2289 private boolean handleOutgoingCallConnection( 2290 String number, TelephonyConnection connection, Phone phone, int videoState) { 2291 2292 if (!mDomainSelectionResolver.isDomainSelectionSupported()) { 2293 return false; 2294 } 2295 2296 if (phone == null) { 2297 return false; 2298 } 2299 2300 String dialPart = PhoneNumberUtils.extractNetworkPortionAlt( 2301 PhoneNumberUtils.stripSeparators(number)); 2302 boolean isMmiCode = (dialPart.startsWith("*") || dialPart.startsWith("#")) 2303 && dialPart.endsWith("#"); 2304 boolean isSuppServiceCode = ImsPhoneMmiCode.isSuppServiceCodes(dialPart, phone); 2305 2306 // If the number is both an MMI code and a supplementary service code, 2307 // it shall be treated as UT. In this case, domain selection is not performed. 2308 if (isMmiCode && isSuppServiceCode) { 2309 Log.v(LOG_TAG, "UT code not handled by call domain selection."); 2310 return false; 2311 } 2312 2313 // Check and select same domain as ongoing call on the same subscription (if exists) 2314 int activeCallDomain = getActiveCallDomain(phone.getSubId()); 2315 if (activeCallDomain != NetworkRegistrationInfo.DOMAIN_UNKNOWN 2316 && !NormalCallDomainSelectionConnection.isWpsCall(number)) { 2317 Log.d(LOG_TAG, "Selecting same domain as ongoing call on same subId"); 2318 mNormalCallConnection = connection; 2319 handleOutgoingCallConnectionByCallDomainSelection( 2320 activeCallDomain, phone, number, videoState); 2321 return true; 2322 } 2323 2324 mDomainSelectionConnection = mDomainSelectionResolver 2325 .getDomainSelectionConnection(phone, SELECTOR_TYPE_CALLING, false); 2326 if (mDomainSelectionConnection == null) { 2327 return false; 2328 } 2329 Log.d(LOG_TAG, "Call Connection created"); 2330 SelectionAttributes selectionAttributes = 2331 new SelectionAttributes.Builder(phone.getPhoneId(), phone.getSubId(), 2332 SELECTOR_TYPE_CALLING) 2333 .setNumber(number) 2334 .setEmergency(false) 2335 .setVideoCall(VideoProfile.isVideo(videoState)) 2336 .build(); 2337 2338 NormalCallDomainSelectionConnection normalCallDomainSelectionConnection = 2339 (NormalCallDomainSelectionConnection) mDomainSelectionConnection; 2340 CompletableFuture<Integer> future = normalCallDomainSelectionConnection 2341 .createNormalConnection(selectionAttributes, 2342 mCallDomainSelectionConnectionCallback); 2343 Log.d(LOG_TAG, "Call Domain selection triggered."); 2344 2345 mNormalCallConnection = connection; 2346 future.thenAcceptAsync((domain) -> handleOutgoingCallConnectionByCallDomainSelection( 2347 domain, phone, number, videoState), mDomainSelectionMainExecutor); 2348 return true; 2349 } 2350 2351 @SuppressWarnings("FutureReturnValueIgnored") placeEmergencyConnection( final Phone phone, final ConnectionRequest request, final String numberToDial, final boolean isTestEmergencyNumber, final Uri handle, final boolean needToTurnOnRadio)2352 private Connection placeEmergencyConnection( 2353 final Phone phone, final ConnectionRequest request, 2354 final String numberToDial, final boolean isTestEmergencyNumber, 2355 final Uri handle, final boolean needToTurnOnRadio) { 2356 2357 final Connection resultConnection = 2358 getTelephonyConnection(request, numberToDial, true, handle, phone); 2359 2360 if (resultConnection instanceof TelephonyConnection) { 2361 Log.i(this, "placeEmergencyConnection"); 2362 2363 mIsEmergencyCallPending = true; 2364 ((TelephonyConnection) resultConnection).addTelephonyConnectionListener( 2365 mEmergencyConnectionListener); 2366 2367 if (mEmergencyStateTracker == null) { 2368 mEmergencyStateTracker = EmergencyStateTracker.getInstance(); 2369 } 2370 2371 mEmergencyCallId = resultConnection.getTelecomCallId(); 2372 CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencyCall( 2373 phone, mEmergencyCallId, isTestEmergencyNumber); 2374 future.thenAccept((result) -> { 2375 Log.d(this, "startEmergencyCall-complete result=" + result); 2376 if (mEmergencyCallId == null) { 2377 Log.i(this, "startEmergencyCall-complete dialing canceled"); 2378 return; 2379 } 2380 if (result == android.telephony.DisconnectCause.NOT_DISCONNECTED) { 2381 createEmergencyConnection(phone, (TelephonyConnection) resultConnection, 2382 numberToDial, request, needToTurnOnRadio, 2383 mEmergencyStateTracker.getEmergencyRegResult()); 2384 } else { 2385 mEmergencyConnection = null; 2386 String reason = "Couldn't setup emergency call"; 2387 if (result == android.telephony.DisconnectCause.POWER_OFF) { 2388 reason = "Failed to turn on radio."; 2389 } 2390 ((TelephonyConnection) resultConnection).setTelephonyConnectionDisconnected( 2391 mDisconnectCauseFactory.toTelecomDisconnectCause(result, reason)); 2392 ((TelephonyConnection) resultConnection).close(); 2393 mIsEmergencyCallPending = false; 2394 } 2395 }); 2396 mEmergencyConnection = (TelephonyConnection) resultConnection; 2397 return resultConnection; 2398 } 2399 Log.i(this, "placeEmergencyConnection returns null"); 2400 return null; 2401 } 2402 2403 @SuppressWarnings("FutureReturnValueIgnored") createEmergencyConnection(final Phone phone, final TelephonyConnection resultConnection, final String number, final ConnectionRequest request, boolean needToTurnOnRadio, final EmergencyRegResult regResult)2404 private void createEmergencyConnection(final Phone phone, 2405 final TelephonyConnection resultConnection, final String number, 2406 final ConnectionRequest request, boolean needToTurnOnRadio, 2407 final EmergencyRegResult regResult) { 2408 Log.i(this, "createEmergencyConnection"); 2409 2410 if (phone.getImsPhone() == null) { 2411 // Dialing emergency calls over IMS is not available without ImsPhone instance. 2412 Log.w(this, "createEmergencyConnection no ImsPhone"); 2413 dialCsEmergencyCall(phone, resultConnection, request); 2414 return; 2415 } 2416 2417 ImsManager imsManager = mImsManager; 2418 if (imsManager == null) { 2419 // mImsManager is not null only while unit test. 2420 imsManager = ImsManager.getInstance(phone.getContext(), phone.getPhoneId()); 2421 } 2422 if (!imsManager.isNonTtyOrTtyOnVolteEnabled()) { 2423 Log.w(this, "createEmergencyConnection - TTY on VoLTE is not supported."); 2424 dialCsEmergencyCall(phone, resultConnection, request); 2425 return; 2426 } 2427 2428 DomainSelectionConnection selectConnection = 2429 mDomainSelectionResolver.getDomainSelectionConnection( 2430 phone, SELECTOR_TYPE_CALLING, true); 2431 2432 if (selectConnection == null) { 2433 // While the domain selection service is enabled, the valid 2434 // {@link DomainSelectionConnection} is not available. 2435 // This can happen when the domain selection service is not available. 2436 Log.w(this, "createEmergencyConnection - no selectionConnection"); 2437 dialCsEmergencyCall(phone, resultConnection, request); 2438 return; 2439 } 2440 2441 mEmergencyCallDomainSelectionConnection = 2442 (EmergencyCallDomainSelectionConnection) selectConnection; 2443 2444 DomainSelectionService.SelectionAttributes attr = 2445 EmergencyCallDomainSelectionConnection.getSelectionAttributes( 2446 phone.getPhoneId(), phone.getSubId(), needToTurnOnRadio, 2447 request.getTelecomCallId(), number, 0, null, regResult); 2448 2449 CompletableFuture<Integer> future = 2450 mEmergencyCallDomainSelectionConnection.createEmergencyConnection( 2451 attr, mEmergencyDomainSelectionConnectionCallback); 2452 future.thenAcceptAsync((result) -> { 2453 Log.d(this, "createEmergencyConnection-complete result=" + result); 2454 if (mEmergencyCallId == null) { 2455 Log.i(this, "createEmergencyConnection-complete dialing canceled"); 2456 return; 2457 } 2458 Bundle extras = request.getExtras(); 2459 extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, result); 2460 placeOutgoingConnection(request, resultConnection, phone); 2461 mIsEmergencyCallPending = false; 2462 }, mDomainSelectionMainExecutor); 2463 } 2464 dialCsEmergencyCall(final Phone phone, final TelephonyConnection resultConnection, final ConnectionRequest request)2465 private void dialCsEmergencyCall(final Phone phone, 2466 final TelephonyConnection resultConnection, final ConnectionRequest request) { 2467 Log.d(this, "dialCsEmergencyCall"); 2468 Bundle extras = request.getExtras(); 2469 extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_CS); 2470 mDomainSelectionMainExecutor.execute( 2471 () -> { 2472 if (mEmergencyCallId == null) { 2473 Log.i(this, "dialCsEmergencyCall dialing canceled"); 2474 return; 2475 } 2476 placeOutgoingConnection(request, resultConnection, phone); 2477 }); 2478 } 2479 releaseEmergencyCallDomainSelection(boolean cancel)2480 private void releaseEmergencyCallDomainSelection(boolean cancel) { 2481 if (mEmergencyCallDomainSelectionConnection != null) { 2482 if (cancel) mEmergencyCallDomainSelectionConnection.cancelSelection(); 2483 else mEmergencyCallDomainSelectionConnection.finishSelection(); 2484 mEmergencyCallDomainSelectionConnection = null; 2485 } 2486 mIsEmergencyCallPending = false; 2487 mEmergencyConnection = null; 2488 } 2489 2490 /** 2491 * Determine whether reselection of domain is required or not. 2492 * @param c the {@link Connection} instance. 2493 * @param callFailCause the reason why CS call is disconnected. Allowed values are defined in 2494 * {@link com.android.internal.telephony.CallFailCause}. 2495 * @param reasonInfo the reason why PS call is disconnected. 2496 * @return {@code true} if reselection of domain is required. 2497 */ maybeReselectDomain(final TelephonyConnection c, int callFailCause, ImsReasonInfo reasonInfo)2498 public boolean maybeReselectDomain(final TelephonyConnection c, 2499 int callFailCause, ImsReasonInfo reasonInfo) { 2500 if (!mDomainSelectionResolver.isDomainSelectionSupported()) return false; 2501 2502 Log.i(this, "maybeReselectDomain csCause=" + callFailCause + ", psCause=" + reasonInfo); 2503 if (TextUtils.equals(mEmergencyCallId, c.getTelecomCallId())) { 2504 if (mEmergencyCallDomainSelectionConnection != null) { 2505 return maybeReselectDomainForEmergencyCall(c, callFailCause, reasonInfo); 2506 } 2507 Log.i(this, "maybeReselectDomain endCall()"); 2508 c.removeTelephonyConnectionListener(mEmergencyConnectionListener); 2509 mEmergencyStateTracker.endCall(c.getTelecomCallId()); 2510 mEmergencyCallId = null; 2511 return false; 2512 } 2513 2514 if (reasonInfo != null) { 2515 int reasonCode = reasonInfo.getCode(); 2516 int extraCode = reasonInfo.getExtraCode(); 2517 if ((reasonCode == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL) 2518 || (reasonCode == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED 2519 && extraCode == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY)) { 2520 // clear normal call domain selector 2521 c.removeTelephonyConnectionListener(mNormalCallConnectionListener); 2522 if (mDomainSelectionConnection != null) { 2523 mDomainSelectionConnection.finishSelection(); 2524 mDomainSelectionConnection = null; 2525 } 2526 mNormalCallConnection = null; 2527 2528 onEmergencyRedial(c, c.getPhone().getDefaultPhone()); 2529 return true; 2530 } 2531 } 2532 2533 return maybeReselectDomainForNormalCall(c, callFailCause, reasonInfo); 2534 } 2535 maybeReselectDomainForEmergencyCall(final TelephonyConnection c, int callFailCause, ImsReasonInfo reasonInfo)2536 private boolean maybeReselectDomainForEmergencyCall(final TelephonyConnection c, 2537 int callFailCause, ImsReasonInfo reasonInfo) { 2538 Log.i(this, "maybeReselectDomainForEmergencyCall " 2539 + "csCause=" + callFailCause + ", psCause=" + reasonInfo); 2540 2541 if (c.getOriginalConnection() != null 2542 && c.getOriginalConnection().getDisconnectCause() 2543 != android.telephony.DisconnectCause.LOCAL 2544 && c.getOriginalConnection().getDisconnectCause() 2545 != android.telephony.DisconnectCause.POWER_OFF) { 2546 2547 DomainSelectionService.SelectionAttributes attr = 2548 EmergencyCallDomainSelectionConnection.getSelectionAttributes( 2549 c.getPhone().getPhoneId(), c.getPhone().getSubId(), false, 2550 c.getTelecomCallId(), c.getAddress().getSchemeSpecificPart(), 2551 callFailCause, reasonInfo, null); 2552 2553 CompletableFuture<Integer> future = 2554 mEmergencyCallDomainSelectionConnection.reselectDomain(attr); 2555 // TeleponyConnection will clear original connection. Keep the reference to Phone. 2556 final Phone phone = c.getPhone().getDefaultPhone(); 2557 if (future != null) { 2558 future.thenAcceptAsync((result) -> { 2559 Log.d(this, "reselectDomain-complete"); 2560 if (mEmergencyCallId == null) { 2561 Log.i(this, "reselectDomain-complete dialing canceled"); 2562 return; 2563 } 2564 onEmergencyRedialOnDomain(c, phone, result); 2565 }, mDomainSelectionMainExecutor); 2566 return true; 2567 } 2568 } 2569 2570 Log.i(this, "maybeReselectDomainForEmergencyCall endCall()"); 2571 c.removeTelephonyConnectionListener(mEmergencyConnectionListener); 2572 releaseEmergencyCallDomainSelection(true); 2573 mEmergencyStateTracker.endCall(c.getTelecomCallId()); 2574 mEmergencyCallId = null; 2575 return false; 2576 } 2577 isNormalRouting(Phone phone, String number)2578 private boolean isNormalRouting(Phone phone, String number) { 2579 if (phone.getEmergencyNumberTracker() != null) { 2580 // Note: There can potentially be multiple instances of EmergencyNumber found; if any of 2581 // them have normal routing, then use normal routing. 2582 List<EmergencyNumber> nums = phone.getEmergencyNumberTracker().getEmergencyNumbers( 2583 number); 2584 return nums.stream().anyMatch(n -> 2585 n.getEmergencyCallRouting() == EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL); 2586 } 2587 return false; 2588 } 2589 2590 /** 2591 * Determines the phone to use for a normal routed emergency call. 2592 * @param number The emergency number. 2593 * @return The {@link Phone} to place the normal routed emergency call on, or {@code null} if 2594 * none was found. 2595 */ 2596 @VisibleForTesting getPhoneForNormalRoutedEmergencyCall(String number)2597 public Phone getPhoneForNormalRoutedEmergencyCall(String number) { 2598 return Stream.of(mPhoneFactoryProxy.getPhones()) 2599 .filter(p -> p.shouldPreferInServiceSimForNormalRoutedEmergencyCall() 2600 && isNormalRouting(p, number) 2601 && isAvailableForEmergencyCalls(p, 2602 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL)) 2603 .findFirst().orElse(null); 2604 } 2605 isVoiceInService(Phone phone, boolean imsVoiceCapable)2606 private boolean isVoiceInService(Phone phone, boolean imsVoiceCapable) { 2607 // Dialing normal call is available. 2608 if (phone.isWifiCallingEnabled()) { 2609 Log.i(this, "isVoiceInService VoWi-Fi available"); 2610 return true; 2611 } 2612 2613 ServiceState ss = phone.getServiceStateTracker().getServiceState(); 2614 if (ss.getState() != ServiceState.STATE_IN_SERVICE) return false; 2615 2616 NetworkRegistrationInfo regState = ss.getNetworkRegistrationInfo( 2617 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); 2618 if (regState != null) { 2619 int registrationState = regState.getRegistrationState(); 2620 if (registrationState != NetworkRegistrationInfo.REGISTRATION_STATE_HOME 2621 && registrationState != NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) { 2622 return true; 2623 } 2624 2625 int networkType = regState.getAccessNetworkTechnology(); 2626 if (networkType == TelephonyManager.NETWORK_TYPE_LTE) { 2627 DataSpecificRegistrationInfo regInfo = regState.getDataSpecificInfo(); 2628 if (regInfo.getLteAttachResultType() 2629 == DataSpecificRegistrationInfo.LTE_ATTACH_TYPE_COMBINED) { 2630 Log.i(this, "isVoiceInService combined attach"); 2631 return true; 2632 } 2633 } 2634 2635 if (networkType == TelephonyManager.NETWORK_TYPE_NR 2636 || networkType == TelephonyManager.NETWORK_TYPE_LTE) { 2637 Log.i(this, "isVoiceInService PS only network, IMS available " + imsVoiceCapable); 2638 return imsVoiceCapable; 2639 } 2640 } 2641 return true; 2642 } 2643 maybeReselectDomainForNormalCall( final TelephonyConnection c, int callFailCause, ImsReasonInfo reasonInfo)2644 private boolean maybeReselectDomainForNormalCall( 2645 final TelephonyConnection c, int callFailCause, ImsReasonInfo reasonInfo) { 2646 2647 Log.i(LOG_TAG, "maybeReselectDomainForNormalCall " + "csCause:" + callFailCause 2648 + ", psCause:" + reasonInfo); 2649 2650 if (mDomainSelectionConnection != null && c.getOriginalConnection() != null) { 2651 Phone phone = c.getPhone().getDefaultPhone(); 2652 final String number = c.getAddress().getSchemeSpecificPart(); 2653 int videoState = c.getOriginalConnection().getVideoState(); 2654 SelectionAttributes selectionAttributes = NormalCallDomainSelectionConnection 2655 .getSelectionAttributes(phone.getPhoneId(), phone.getSubId(), 2656 c.getTelecomCallId(), number, VideoProfile.isVideo(videoState), 2657 callFailCause, reasonInfo); 2658 2659 Log.d(LOG_TAG, "Reselecting the domain for call"); 2660 mNormalCallConnection = c; 2661 CompletableFuture<Integer> future = mDomainSelectionConnection 2662 .reselectDomain(selectionAttributes); 2663 if (future != null) { 2664 future.thenAcceptAsync((result) -> { 2665 onNormalCallRedial(c, phone, result, videoState); 2666 }, mDomainSelectionMainExecutor); 2667 return true; 2668 } 2669 } 2670 2671 c.removeTelephonyConnectionListener(mTelephonyConnectionListener); 2672 if (mDomainSelectionConnection != null) { 2673 mDomainSelectionConnection.finishSelection(); 2674 mDomainSelectionConnection = null; 2675 } 2676 mNormalCallConnection = null; 2677 Log.d(LOG_TAG, "Reselect call domain not triggered."); 2678 return false; 2679 } 2680 onEmergencyRedialOnDomain(TelephonyConnection connection, final Phone phone, @NetworkRegistrationInfo.Domain int domain)2681 private void onEmergencyRedialOnDomain(TelephonyConnection connection, 2682 final Phone phone, @NetworkRegistrationInfo.Domain int domain) { 2683 Log.i(this, "onEmergencyRedialOnDomain phoneId=" + phone.getPhoneId() 2684 + ", domain=" + DomainSelectionService.getDomainName(domain)); 2685 2686 String number = connection.getAddress().getSchemeSpecificPart(); 2687 2688 // Indicates undetectable emergency number with DialArgs 2689 boolean isEmergency = false; 2690 int eccCategory = EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED; 2691 if (connection.getEmergencyServiceCategory() != null) { 2692 isEmergency = true; 2693 eccCategory = connection.getEmergencyServiceCategory(); 2694 Log.i(this, "onEmergencyRedialOnDomain eccCategory=" + eccCategory); 2695 } 2696 2697 Bundle extras = new Bundle(); 2698 extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain); 2699 2700 com.android.internal.telephony.Connection originalConnection = 2701 connection.getOriginalConnection(); 2702 try { 2703 if (phone != null) { 2704 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder() 2705 .setVideoState(VideoProfile.STATE_AUDIO_ONLY) 2706 .setIntentExtras(extras) 2707 .setRttTextStream(connection.getRttTextStream()) 2708 .setIsEmergency(isEmergency) 2709 .setEccCategory(eccCategory) 2710 .build(), 2711 connection::registerForCallEvents); 2712 } 2713 } catch (CallStateException e) { 2714 Log.e(this, e, "onEmergencyRedialOnDomain, exception: " + e); 2715 } 2716 if (originalConnection == null) { 2717 Log.d(this, "onEmergencyRedialOnDomain, phone.dial returned null"); 2718 connection.setDisconnected( 2719 mDisconnectCauseFactory.toTelecomDisconnectCause( 2720 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 2721 "unknown error")); 2722 } else { 2723 connection.setOriginalConnection(originalConnection); 2724 } 2725 } 2726 2727 @SuppressWarnings("FutureReturnValueIgnored") onEmergencyRedial(final TelephonyConnection c, final Phone phone)2728 private void onEmergencyRedial(final TelephonyConnection c, final Phone phone) { 2729 Log.i(this, "onEmergencyRedial phoneId=" + phone.getPhoneId()); 2730 2731 final String number = c.getAddress().getSchemeSpecificPart(); 2732 final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number); 2733 2734 mIsEmergencyCallPending = true; 2735 c.addTelephonyConnectionListener(mEmergencyConnectionListener); 2736 handleEmergencyCallStartedForSatelliteSOSMessageRecommender(c, phone); 2737 2738 if (mEmergencyStateTracker == null) { 2739 mEmergencyStateTracker = EmergencyStateTracker.getInstance(); 2740 } 2741 2742 mEmergencyCallId = c.getTelecomCallId(); 2743 CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencyCall( 2744 phone, mEmergencyCallId, isTestEmergencyNumber); 2745 future.thenAccept((result) -> { 2746 Log.d(this, "onEmergencyRedial-complete result=" + result); 2747 if (mEmergencyCallId == null) { 2748 Log.i(this, "onEmergencyRedial-complete dialing canceled"); 2749 return; 2750 } 2751 if (result == android.telephony.DisconnectCause.NOT_DISCONNECTED) { 2752 DomainSelectionConnection selectConnection = 2753 mDomainSelectionResolver.getDomainSelectionConnection( 2754 phone, SELECTOR_TYPE_CALLING, true); 2755 2756 if (selectConnection == null) { 2757 Log.w(this, "onEmergencyRedial no selectionConnection, dial CS emergency call"); 2758 mIsEmergencyCallPending = false; 2759 mDomainSelectionMainExecutor.execute( 2760 () -> recreateEmergencyConnection(c, phone, 2761 NetworkRegistrationInfo.DOMAIN_CS)); 2762 return; 2763 } 2764 2765 mEmergencyCallDomainSelectionConnection = 2766 (EmergencyCallDomainSelectionConnection) selectConnection; 2767 2768 mEmergencyConnection = c; 2769 2770 DomainSelectionService.SelectionAttributes attr = 2771 EmergencyCallDomainSelectionConnection.getSelectionAttributes( 2772 phone.getPhoneId(), 2773 phone.getSubId(), false, 2774 c.getTelecomCallId(), 2775 c.getAddress().getSchemeSpecificPart(), 2776 0, null, mEmergencyStateTracker.getEmergencyRegResult()); 2777 2778 CompletableFuture<Integer> domainFuture = 2779 mEmergencyCallDomainSelectionConnection.createEmergencyConnection( 2780 attr, mEmergencyDomainSelectionConnectionCallback); 2781 domainFuture.thenAcceptAsync((domain) -> { 2782 Log.d(this, "onEmergencyRedial-createEmergencyConnection-complete domain=" 2783 + domain); 2784 recreateEmergencyConnection(c, phone, domain); 2785 mIsEmergencyCallPending = false; 2786 }, mDomainSelectionMainExecutor); 2787 } else { 2788 c.setTelephonyConnectionDisconnected( 2789 mDisconnectCauseFactory.toTelecomDisconnectCause(result, "unknown error")); 2790 c.close(); 2791 mIsEmergencyCallPending = false; 2792 } 2793 }); 2794 } 2795 recreateEmergencyConnection(final TelephonyConnection connection, final Phone phone, final @NetworkRegistrationInfo.Domain int result)2796 private void recreateEmergencyConnection(final TelephonyConnection connection, 2797 final Phone phone, final @NetworkRegistrationInfo.Domain int result) { 2798 Log.d(this, "recreateEmergencyConnection result=" + result); 2799 if (mEmergencyCallId == null) { 2800 Log.i(this, "recreateEmergencyConnection dialing canceled"); 2801 return; 2802 } 2803 if (!getAllConnections().isEmpty()) { 2804 if (!shouldHoldForEmergencyCall(phone)) { 2805 // If we do not support holding ongoing calls for an outgoing 2806 // emergency call, disconnect the ongoing calls. 2807 for (Connection c : getAllConnections()) { 2808 if (!c.equals(connection) 2809 && c.getState() != Connection.STATE_DISCONNECTED 2810 && c instanceof TelephonyConnection) { 2811 ((TelephonyConnection) c).hangup( 2812 android.telephony.DisconnectCause 2813 .OUTGOING_EMERGENCY_CALL_PLACED); 2814 } 2815 } 2816 for (Conference c : getAllConferences()) { 2817 if (c.getState() != Connection.STATE_DISCONNECTED) { 2818 c.onDisconnect(); 2819 } 2820 } 2821 } else if (!isVideoCallHoldAllowed(phone)) { 2822 // If we do not support holding ongoing video call for an outgoing 2823 // emergency call, disconnect the ongoing video call. 2824 for (Connection c : getAllConnections()) { 2825 if (!c.equals(connection) 2826 && c.getState() == Connection.STATE_ACTIVE 2827 && VideoProfile.isVideo(c.getVideoState()) 2828 && c instanceof TelephonyConnection) { 2829 ((TelephonyConnection) c).hangup( 2830 android.telephony.DisconnectCause 2831 .OUTGOING_EMERGENCY_CALL_PLACED); 2832 break; 2833 } 2834 } 2835 } 2836 } 2837 onEmergencyRedialOnDomain(connection, phone, result); 2838 } 2839 onNormalCallRedial(TelephonyConnection connection, Phone phone, @NetworkRegistrationInfo.Domain int domain, int videocallState)2840 private void onNormalCallRedial(TelephonyConnection connection, Phone phone, 2841 @NetworkRegistrationInfo.Domain int domain, int videocallState) { 2842 2843 Log.v(LOG_TAG, "Redialing the call in domain:" 2844 + DomainSelectionService.getDomainName(domain)); 2845 2846 String number = connection.getAddress().getSchemeSpecificPart(); 2847 2848 Bundle extras = new Bundle(); 2849 extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain); 2850 // Add flag to bundle for comparing legacy and new domain selection results. When 2851 // EXTRA_COMPARE_DOMAIN flag is true, legacy domain selection result is used for 2852 // placing the call and if both the results are not same then bug report is generated. 2853 DeviceConfig.Properties properties = //read all telephony properties 2854 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TELEPHONY); 2855 boolean compareDomainSelection = 2856 properties.getBoolean(KEY_DOMAIN_COMPARE_FEATURE_ENABLED_FLAG, false); 2857 extras.putBoolean(PhoneConstants.EXTRA_COMPARE_DOMAIN, compareDomainSelection); 2858 2859 com.android.internal.telephony.Connection originalConnection = 2860 connection.getOriginalConnection(); 2861 if (originalConnection instanceof ImsPhoneConnection) { 2862 if (((ImsPhoneConnection) originalConnection).isRttEnabledForCall()) { 2863 extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true); 2864 } 2865 } 2866 2867 try { 2868 if (phone != null) { 2869 Log.d(LOG_TAG, "Redialing Call."); 2870 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder() 2871 .setVideoState(videocallState) 2872 .setIntentExtras(extras) 2873 .setRttTextStream(connection.getRttTextStream()) 2874 .setIsEmergency(false) 2875 .build(), 2876 connection::registerForCallEvents); 2877 } 2878 } catch (Exception e) { 2879 Log.e(LOG_TAG, e, "Call redial exception: " + e); 2880 } 2881 if (originalConnection == null) { 2882 Log.e(LOG_TAG, new Exception("Phone is null"), 2883 "Call redial failure due to phone.dial returned null"); 2884 connection.setDisconnected(mDisconnectCauseFactory.toTelecomDisconnectCause( 2885 android.telephony.DisconnectCause.OUTGOING_FAILURE, "connection is null")); 2886 connection.close(); 2887 } else { 2888 connection.setOriginalConnection(originalConnection); 2889 } 2890 } 2891 onLocalHangup(TelephonyConnection c)2892 protected void onLocalHangup(TelephonyConnection c) { 2893 if (TextUtils.equals(mEmergencyCallId, c.getTelecomCallId())) { 2894 Log.i(this, "onLocalHangup " + mEmergencyCallId); 2895 c.removeTelephonyConnectionListener(mEmergencyConnectionListener); 2896 releaseEmergencyCallDomainSelection(true); 2897 mEmergencyStateTracker.endCall(c.getTelecomCallId()); 2898 mEmergencyCallId = null; 2899 } 2900 } 2901 2902 @VisibleForTesting getEmergencyConnectionListener()2903 public TelephonyConnection.TelephonyConnectionListener getEmergencyConnectionListener() { 2904 return mEmergencyConnectionListener; 2905 } 2906 2907 @VisibleForTesting 2908 public TelephonyConnection.TelephonyConnectionListener getEmergencyConnectionSatelliteListener()2909 getEmergencyConnectionSatelliteListener() { 2910 return mEmergencyConnectionSatelliteListener; 2911 } 2912 isVideoCallHoldAllowed(Phone phone)2913 private boolean isVideoCallHoldAllowed(Phone phone) { 2914 CarrierConfigManager cfgManager = (CarrierConfigManager) 2915 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 2916 if (cfgManager == null) { 2917 // For some reason CarrierConfigManager is unavailable, return default 2918 Log.w(this, "isVideoCallHoldAllowed: couldn't get CarrierConfigManager"); 2919 return true; 2920 } 2921 return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean( 2922 CarrierConfigManager.KEY_ALLOW_HOLD_VIDEO_CALL_BOOL, true); 2923 } 2924 shouldHoldForEmergencyCall(Phone phone)2925 private boolean shouldHoldForEmergencyCall(Phone phone) { 2926 CarrierConfigManager cfgManager = (CarrierConfigManager) 2927 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 2928 if (cfgManager == null) { 2929 // For some reason CarrierConfigManager is unavailable, return default 2930 Log.w(this, "shouldHoldForEmergencyCall: couldn't get CarrierConfigManager"); 2931 return true; 2932 } 2933 return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean( 2934 CarrierConfigManager.KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL, true); 2935 } 2936 handleCallStateException(CallStateException e, TelephonyConnection connection, Phone phone)2937 private void handleCallStateException(CallStateException e, TelephonyConnection connection, 2938 Phone phone) { 2939 int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 2940 switch (e.getError()) { 2941 case CallStateException.ERROR_OUT_OF_SERVICE: 2942 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE; 2943 break; 2944 case CallStateException.ERROR_POWER_OFF: 2945 cause = android.telephony.DisconnectCause.POWER_OFF; 2946 break; 2947 case CallStateException.ERROR_ALREADY_DIALING: 2948 cause = android.telephony.DisconnectCause.ALREADY_DIALING; 2949 break; 2950 case CallStateException.ERROR_CALL_RINGING: 2951 cause = android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING; 2952 break; 2953 case CallStateException.ERROR_CALLING_DISABLED: 2954 cause = android.telephony.DisconnectCause.CALLING_DISABLED; 2955 break; 2956 case CallStateException.ERROR_TOO_MANY_CALLS: 2957 cause = android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS; 2958 break; 2959 case CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS: 2960 cause = android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS; 2961 break; 2962 case CallStateException.ERROR_FDN_BLOCKED: 2963 cause = android.telephony.DisconnectCause.FDN_BLOCKED; 2964 break; 2965 } 2966 connection.setTelephonyConnectionDisconnected( 2967 DisconnectCauseUtil.toTelecomDisconnectCause(cause, e.getMessage(), 2968 phone.getPhoneId())); 2969 connection.close(); 2970 } 2971 createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId)2972 private TelephonyConnection createConnectionFor( 2973 Phone phone, 2974 com.android.internal.telephony.Connection originalConnection, 2975 boolean isOutgoing, 2976 PhoneAccountHandle phoneAccountHandle, 2977 String telecomCallId) { 2978 return createConnectionFor(phone, originalConnection, isOutgoing, phoneAccountHandle, 2979 telecomCallId, false); 2980 } 2981 createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, boolean isAdhocConference)2982 private TelephonyConnection createConnectionFor( 2983 Phone phone, 2984 com.android.internal.telephony.Connection originalConnection, 2985 boolean isOutgoing, 2986 PhoneAccountHandle phoneAccountHandle, 2987 String telecomCallId, 2988 boolean isAdhocConference) { 2989 TelephonyConnection returnConnection = null; 2990 int phoneType = phone.getPhoneType(); 2991 int callDirection = isOutgoing ? android.telecom.Call.Details.DIRECTION_OUTGOING 2992 : android.telecom.Call.Details.DIRECTION_INCOMING; 2993 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 2994 returnConnection = new GsmConnection(originalConnection, telecomCallId, callDirection); 2995 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 2996 boolean allowsMute = allowsMute(phone); 2997 returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer, 2998 allowsMute, callDirection, telecomCallId); 2999 } 3000 if (returnConnection != null) { 3001 if (!isAdhocConference) { 3002 // Listen to Telephony specific callbacks from the connection 3003 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener); 3004 } 3005 returnConnection.setVideoPauseSupported( 3006 TelecomAccountRegistry.getInstance(this).isVideoPauseSupported( 3007 phoneAccountHandle)); 3008 returnConnection.setManageImsConferenceCallSupported( 3009 TelecomAccountRegistry.getInstance(this).isManageImsConferenceCallSupported( 3010 phoneAccountHandle)); 3011 returnConnection.setShowPreciseFailedCause( 3012 TelecomAccountRegistry.getInstance(this).isShowPreciseFailedCause( 3013 phoneAccountHandle)); 3014 returnConnection.setTelephonyConnectionService(this); 3015 } 3016 return returnConnection; 3017 } 3018 isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)3019 private boolean isOriginalConnectionKnown( 3020 com.android.internal.telephony.Connection originalConnection) { 3021 return (getConnectionForOriginalConnection(originalConnection) != null); 3022 } 3023 getConnectionForOriginalConnection( com.android.internal.telephony.Connection originalConnection)3024 private TelephonyConnection getConnectionForOriginalConnection( 3025 com.android.internal.telephony.Connection originalConnection) { 3026 for (Connection connection : getAllConnections()) { 3027 if (connection instanceof TelephonyConnection) { 3028 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 3029 if (telephonyConnection.getOriginalConnection() == originalConnection) { 3030 return telephonyConnection; 3031 } 3032 } 3033 } 3034 return null; 3035 } 3036 3037 /** 3038 * Determines which {@link Phone} will be used to place the call. 3039 * @param accountHandle The {@link PhoneAccountHandle} which was sent from Telecom to place the 3040 * call on. 3041 * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise. 3042 * @param emergencyNumberAddress When {@code isEmergency} is {@code true}, will be the phone 3043 * of the emergency call. Otherwise, this can be {@code null} . 3044 * @return 3045 */ getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, @Nullable String emergencyNumberAddress)3046 private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, 3047 @Nullable String emergencyNumberAddress) { 3048 Phone chosenPhone = null; 3049 int subId = mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(accountHandle); 3050 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 3051 int phoneId = mSubscriptionManagerProxy.getPhoneId(subId); 3052 chosenPhone = mPhoneFactoryProxy.getPhone(phoneId); 3053 Log.i(this, "getPhoneForAccount: handle=%s, subId=%s", accountHandle, 3054 (chosenPhone == null ? "null" : chosenPhone.getSubId())); 3055 } 3056 3057 // If this isn't an emergency call, just use the chosen phone (or null if none was found). 3058 if (!isEmergency) { 3059 return chosenPhone; 3060 } 3061 3062 // Check if this call should be treated as a normal routed emergency call; we'll return null 3063 // if this is not a normal routed emergency call. 3064 Phone normalRoutingPhone = getPhoneForNormalRoutedEmergencyCall(emergencyNumberAddress); 3065 if (normalRoutingPhone != null) { 3066 Log.i(this, "getPhoneForAccount: normal routed emergency number," 3067 + "using phoneId=%d/subId=%d", normalRoutingPhone.getPhoneId(), 3068 normalRoutingPhone.getSubId()); 3069 return normalRoutingPhone; 3070 } 3071 3072 // Default emergency call phone selection logic: 3073 // This is an emergency call and the phone we originally planned to make this call 3074 // with is not in service or was invalid, try to find one that is in service, using the 3075 // default as a last chance backup. 3076 if (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone)) { 3077 Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service " 3078 + "or invalid for emergency call.", accountHandle); 3079 chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress); 3080 Log.i(this, "getPhoneForAccount: emergency call - using subId: %s", 3081 (chosenPhone == null ? "null" : chosenPhone.getSubId())); 3082 } 3083 return chosenPhone; 3084 } 3085 3086 /** 3087 * If needed, block until the default data is switched for outgoing emergency call, or 3088 * timeout expires. 3089 * @param phone The Phone to switch the DDS on. 3090 * @param completeConsumer The consumer to call once the default data subscription has been 3091 * switched, provides {@code true} result if the switch happened 3092 * successfully or {@code false} if the operation timed out/failed. 3093 */ delayDialForDdsSwitch(Phone phone, Consumer<Boolean> completeConsumer)3094 private void delayDialForDdsSwitch(Phone phone, Consumer<Boolean> completeConsumer) { 3095 if (phone == null) { 3096 // Do not block indefinitely. 3097 completeConsumer.accept(false); 3098 return; 3099 } 3100 try { 3101 // Waiting for PhoneSwitcher to complete the operation. 3102 CompletableFuture<Boolean> future = possiblyOverrideDefaultDataForEmergencyCall(phone); 3103 // In the case that there is an issue or bug in PhoneSwitcher logic, do not wait 3104 // indefinitely for the future to complete. Instead, set a timeout that will complete 3105 // the future as to not block the outgoing call indefinitely. 3106 CompletableFuture<Boolean> timeout = new CompletableFuture<>(); 3107 phone.getContext().getMainThreadHandler().postDelayed( 3108 () -> timeout.complete(false), DEFAULT_DATA_SWITCH_TIMEOUT_MS); 3109 // Also ensure that the Consumer is completed on the main thread. 3110 future.acceptEitherAsync(timeout, completeConsumer, 3111 phone.getContext().getMainExecutor()); 3112 } catch (Exception e) { 3113 Log.w(this, "delayDialForDdsSwitch - exception= " 3114 + e.getMessage()); 3115 3116 } 3117 } 3118 3119 /** 3120 * If needed, block until Default Data subscription is switched for outgoing emergency call. 3121 * 3122 * In some cases, we need to try to switch the Default Data subscription before placing the 3123 * emergency call on DSDS devices. This includes the following situation: 3124 * - The modem does not support processing GNSS SUPL requests on the non-default data 3125 * subscription. For some carriers that do not provide a control plane fallback mechanism, the 3126 * SUPL request will be dropped and we will not be able to get the user's location for the 3127 * emergency call. In this case, we need to swap default data temporarily. 3128 * @param phone Evaluates whether or not the default data should be moved to the phone 3129 * specified. Should not be null. 3130 */ possiblyOverrideDefaultDataForEmergencyCall( @onNull Phone phone)3131 private CompletableFuture<Boolean> possiblyOverrideDefaultDataForEmergencyCall( 3132 @NonNull Phone phone) { 3133 int phoneCount = mTelephonyManagerProxy.getPhoneCount(); 3134 // Do not override DDS if this is a single SIM device. 3135 if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) { 3136 return CompletableFuture.completedFuture(Boolean.TRUE); 3137 } 3138 3139 // Do not switch Default data if this device supports emergency SUPL on non-DDS. 3140 final boolean gnssSuplRequiresDefaultData = 3141 mDeviceState.isSuplDdsSwitchRequiredForEmergencyCall(this); 3142 if (!gnssSuplRequiresDefaultData) { 3143 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not " 3144 + "require DDS switch."); 3145 return CompletableFuture.completedFuture(Boolean.TRUE); 3146 } 3147 3148 CarrierConfigManager cfgManager = (CarrierConfigManager) 3149 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 3150 if (cfgManager == null) { 3151 // For some reason CarrierConfigManager is unavailable. Do not block emergency call. 3152 Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: couldn't get" 3153 + "CarrierConfigManager"); 3154 return CompletableFuture.completedFuture(Boolean.TRUE); 3155 } 3156 3157 // Only override default data if we are IN_SERVICE already. 3158 if (!isAvailableForEmergencyCalls(phone)) { 3159 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS"); 3160 return CompletableFuture.completedFuture(Boolean.TRUE); 3161 } 3162 3163 // Only override default data if we are not roaming, we do not want to switch onto a network 3164 // that only supports data plane only (if we do not know). 3165 boolean isRoaming = phone.getServiceState().getVoiceRoaming(); 3166 // In some roaming conditions, we know the roaming network doesn't support control plane 3167 // fallback even though the home operator does. For these operators we will need to do a DDS 3168 // switch anyway to make sure the SUPL request doesn't fail. 3169 boolean roamingNetworkSupportsControlPlaneFallback = true; 3170 String[] dataPlaneRoamPlmns = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray( 3171 CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY); 3172 if (dataPlaneRoamPlmns != null && Arrays.asList(dataPlaneRoamPlmns).contains( 3173 phone.getServiceState().getOperatorNumeric())) { 3174 roamingNetworkSupportsControlPlaneFallback = false; 3175 } 3176 if (isRoaming && roamingNetworkSupportsControlPlaneFallback) { 3177 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: roaming network is assumed " 3178 + "to support CP fallback, not switching DDS."); 3179 return CompletableFuture.completedFuture(Boolean.TRUE); 3180 } 3181 // Do not try to swap default data if we support CS fallback or it is assumed that the 3182 // roaming network supports control plane fallback, we do not want to introduce 3183 // a lag in emergency call setup time if possible. 3184 final boolean supportsCpFallback = cfgManager.getConfigForSubId(phone.getSubId()) 3185 .getInt(CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, 3186 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY) 3187 != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY; 3188 if (supportsCpFallback && roamingNetworkSupportsControlPlaneFallback) { 3189 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier " 3190 + "supports CP fallback."); 3191 return CompletableFuture.completedFuture(Boolean.TRUE); 3192 } 3193 3194 // Get extension time, may be 0 for some carriers that support ECBM as well. Use 3195 // CarrierConfig default if format fails. 3196 int extensionTime = 0; 3197 try { 3198 extensionTime = Integer.parseInt(cfgManager.getConfigForSubId(phone.getSubId()) 3199 .getString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0")); 3200 } catch (NumberFormatException e) { 3201 // Just use default. 3202 } 3203 CompletableFuture<Boolean> modemResultFuture = new CompletableFuture<>(); 3204 try { 3205 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for " 3206 + extensionTime + "seconds"); 3207 mPhoneSwitcherProxy.getPhoneSwitcher().overrideDefaultDataForEmergency( 3208 phone.getPhoneId(), extensionTime, modemResultFuture); 3209 // Catch all exceptions, we want to continue with emergency call if possible. 3210 } catch (Exception e) { 3211 Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: exception = " 3212 + e.getMessage()); 3213 modemResultFuture = CompletableFuture.completedFuture(Boolean.FALSE); 3214 } 3215 return modemResultFuture; 3216 } 3217 addTelephonyConnectionListener(Conferenceable c, TelephonyConnection.TelephonyConnectionListener listener)3218 private void addTelephonyConnectionListener(Conferenceable c, 3219 TelephonyConnection.TelephonyConnectionListener listener) { 3220 if (c instanceof TelephonyConnection) { 3221 TelephonyConnection telephonyConnection = (TelephonyConnection) c; 3222 telephonyConnection.addTelephonyConnectionListener(listener); 3223 } else if (c instanceof ImsConference) { 3224 ImsConference imsConference = (ImsConference) c; 3225 TelephonyConnection conferenceHost = 3226 (TelephonyConnection) imsConference.getConferenceHost(); 3227 conferenceHost.addTelephonyConnectionListener(listener); 3228 } else { 3229 throw new IllegalArgumentException( 3230 "addTelephonyConnectionListener(): Unexpected conferenceable! " + c); 3231 } 3232 } 3233 listenForHoldStateChanged( @onNull Conferenceable conferenceable)3234 private CompletableFuture<Boolean> listenForHoldStateChanged( 3235 @NonNull Conferenceable conferenceable) { 3236 CompletableFuture<Boolean> future = new CompletableFuture<>(); 3237 final StateHoldingListener stateHoldingListener = new StateHoldingListener(future); 3238 addTelephonyConnectionListener(conferenceable, stateHoldingListener); 3239 return future; 3240 } 3241 3242 // Returns a future that waits for the STATE_HOLDING confirmation on the input 3243 // {@link Conferenceable}, or times out. delayDialForOtherSubHold(Phone phone, Conferenceable c, Consumer<Boolean> completeConsumer)3244 private CompletableFuture<Void> delayDialForOtherSubHold(Phone phone, Conferenceable c, 3245 Consumer<Boolean> completeConsumer) { 3246 if (c == null || phone == null) { 3247 // Unexpected inputs 3248 completeConsumer.accept(false); 3249 return CompletableFuture.completedFuture(null); 3250 } 3251 3252 try { 3253 CompletableFuture<Boolean> stateHoldingFuture = listenForHoldStateChanged(c); 3254 // a timeout that will complete the future to not block the outgoing call indefinitely. 3255 CompletableFuture<Boolean> timeout = new CompletableFuture<>(); 3256 phone.getContext().getMainThreadHandler().postDelayed( 3257 () -> timeout.complete(false), DEFAULT_DSDA_OUTGOING_CALL_HOLD_TIMEOUT_MS); 3258 // Ensure that the Consumer is completed on the main thread. 3259 return stateHoldingFuture.acceptEitherAsync(timeout, completeConsumer, 3260 phone.getContext().getMainExecutor()); 3261 } catch (Exception e) { 3262 Log.w(this, "delayDialForOtherSubHold - exception= " 3263 + e.getMessage()); 3264 completeConsumer.accept(false); 3265 return CompletableFuture.completedFuture(null); 3266 } 3267 } 3268 3269 /** 3270 * Get the Phone to use for an emergency call of the given emergency number address: 3271 * a) If there are multiple Phones with the Subscriptions that support the emergency number 3272 * address, and one of them is the default voice Phone, consider the default voice phone 3273 * if 1.4 HAL is supported, or if it is available for emergency call. 3274 * b) If there are multiple Phones with the Subscriptions that support the emergency number 3275 * address, and none of them is the default voice Phone, use one of these Phones if 1.4 HAL 3276 * is supported, or if it is available for emergency call. 3277 * c) If there is no Phone that supports the emergency call for the address, use the defined 3278 * Priority list to select the Phone via {@link #getFirstPhoneForEmergencyCall}. 3279 */ getPhoneForEmergencyCall(String emergencyNumberAddress)3280 public Phone getPhoneForEmergencyCall(String emergencyNumberAddress) { 3281 // Find the list of available Phones for the given emergency number address 3282 List<Phone> potentialEmergencyPhones = new ArrayList<>(); 3283 int defaultVoicePhoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId(); 3284 for (Phone phone : mPhoneFactoryProxy.getPhones()) { 3285 if (phone.getEmergencyNumberTracker() != null) { 3286 if (phone.getEmergencyNumberTracker().isEmergencyNumber( 3287 emergencyNumberAddress)) { 3288 if (isAvailableForEmergencyCalls(phone)) { 3289 // a) 3290 if (phone.getPhoneId() == defaultVoicePhoneId) { 3291 Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports" 3292 + " emergency number: " + phone.getPhoneId()); 3293 return phone; 3294 } 3295 potentialEmergencyPhones.add(phone); 3296 } 3297 } 3298 } 3299 } 3300 // b) 3301 if (potentialEmergencyPhones.size() > 0) { 3302 Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports emergency number:" 3303 + potentialEmergencyPhones.get(0).getPhoneId()); 3304 return getFirstPhoneForEmergencyCall(potentialEmergencyPhones); 3305 } 3306 // c) 3307 return getFirstPhoneForEmergencyCall(); 3308 } 3309 3310 @VisibleForTesting getFirstPhoneForEmergencyCall()3311 public Phone getFirstPhoneForEmergencyCall() { 3312 return getFirstPhoneForEmergencyCall(null); 3313 } 3314 3315 /** 3316 * Retrieves the most sensible Phone to use for an emergency call using the following Priority 3317 * list (for multi-SIM devices): 3318 * 1) The Phone that is in emergency SMS mode 3319 * 2) The phone based on User's SIM preference of Voice calling or Data in order 3320 * 3) The First Phone that is currently IN_SERVICE or is available for emergency calling 3321 * 4) Prioritize phones that have the dialed emergency number as part of their emergency 3322 * number list 3323 * 5) If there is a PUK locked SIM, compare the SIMs that are not PUK locked. If all the SIMs 3324 * are locked, skip to condition 6). 3325 * 6) The Phone with more Capabilities. 3326 * 7) The First Phone that has a SIM card in it (Starting from Slot 0...N) 3327 * 8) The Default Phone (Currently set as Slot 0) 3328 */ 3329 @VisibleForTesting 3330 @NonNull getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber)3331 public Phone getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber) { 3332 int phoneCount = mTelephonyManagerProxy.getPhoneCount(); 3333 for (int i = 0; i < phoneCount; i++) { 3334 Phone phone = mPhoneFactoryProxy.getPhone(i); 3335 // 1) 3336 if (phone != null && phone.isInEmergencySmsMode()) { 3337 if (isAvailableForEmergencyCalls(phone)) { 3338 if (phonesWithEmergencyNumber == null 3339 || phonesWithEmergencyNumber.contains(phone)) { 3340 return phone; 3341 } 3342 } 3343 } 3344 } 3345 3346 // 2) 3347 int phoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId(); 3348 if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) { 3349 phoneId = mSubscriptionManagerProxy.getDefaultDataPhoneId(); 3350 } 3351 if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) { 3352 Phone selectedPhone = mPhoneFactoryProxy.getPhone(phoneId); 3353 if (selectedPhone != null && isAvailableForEmergencyCalls(selectedPhone)) { 3354 if (phonesWithEmergencyNumber == null 3355 || phonesWithEmergencyNumber.contains(selectedPhone)) { 3356 return selectedPhone; 3357 } 3358 } 3359 } 3360 3361 Phone firstPhoneWithSim = null; 3362 List<SlotStatus> phoneSlotStatus = new ArrayList<>(phoneCount); 3363 for (int i = 0; i < phoneCount; i++) { 3364 Phone phone = mPhoneFactoryProxy.getPhone(i); 3365 if (phone == null) { 3366 continue; 3367 } 3368 // 3) 3369 if (isAvailableForEmergencyCalls(phone)) { 3370 if (phonesWithEmergencyNumber == null 3371 || phonesWithEmergencyNumber.contains(phone)) { 3372 // the slot has the radio on & state is in service. 3373 Log.i(this, 3374 "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i); 3375 return phone; 3376 } 3377 } 3378 // 6) 3379 // Store the RAF Capabilities for sorting later. 3380 int radioAccessFamily = phone.getRadioAccessFamily(); 3381 SlotStatus status = new SlotStatus(i, radioAccessFamily, phone.getSubId()); 3382 phoneSlotStatus.add(status); 3383 Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" + 3384 Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i + " subId:" 3385 + phone.getSubId()); 3386 // 5) 3387 // Report Slot's PIN/PUK lock status for sorting later. 3388 int simState = mSubscriptionManagerProxy.getSimStateForSlotIdx(i); 3389 // Record SimState. 3390 status.simState = simState; 3391 if (simState == TelephonyManager.SIM_STATE_PIN_REQUIRED || 3392 simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) { 3393 status.isLocked = true; 3394 } 3395 3396 // 4) Store if the Phone has the corresponding emergency number 3397 if (phonesWithEmergencyNumber != null) { 3398 for (Phone phoneWithEmergencyNumber : phonesWithEmergencyNumber) { 3399 if (phoneWithEmergencyNumber != null 3400 && phoneWithEmergencyNumber.getPhoneId() == i) { 3401 status.hasDialedEmergencyNumber = true; 3402 } 3403 } 3404 } 3405 // 7) 3406 if (firstPhoneWithSim == null && 3407 (phone.getSubId() != SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { 3408 // The slot has a SIM card inserted (and an active subscription), but is not in 3409 // service, so keep track of this Phone. 3410 // Do not return because we want to make sure that none of the other Phones 3411 // are in service (because that is always faster). 3412 firstPhoneWithSim = phone; 3413 Log.i(this, "getFirstPhoneForEmergencyCall, SIM with active sub, Phone Id:" + 3414 firstPhoneWithSim.getPhoneId()); 3415 } 3416 } 3417 // 8) 3418 if (firstPhoneWithSim == null && phoneSlotStatus.isEmpty()) { 3419 if (phonesWithEmergencyNumber != null) { 3420 for (Phone phoneWithEmergencyNumber : phonesWithEmergencyNumber) { 3421 if (phoneWithEmergencyNumber != null) { 3422 return phoneWithEmergencyNumber; 3423 } 3424 } 3425 } 3426 3427 // No Phones available, get the default 3428 Log.i(this, "getFirstPhoneForEmergencyCall, return default phone"); 3429 return mPhoneFactoryProxy.getDefaultPhone(); 3430 } else { 3431 // 6) 3432 final int defaultPhoneId = mPhoneFactoryProxy.getDefaultPhone().getPhoneId(); 3433 final Phone firstOccupiedSlot = firstPhoneWithSim; 3434 if (!phoneSlotStatus.isEmpty()) { 3435 Log.i(this, "getFirstPhoneForEmergencyCall, list size: " + phoneSlotStatus.size() 3436 + " defaultPhoneId: " + defaultPhoneId + " firstOccupiedSlot: " 3437 + firstOccupiedSlot); 3438 // Only sort if there are enough elements to do so. 3439 if (phoneSlotStatus.size() > 1) { 3440 Collections.sort(phoneSlotStatus, (o1, o2) -> { 3441 // Sort by non-absent SIM (SIM without active sub is considered absent). 3442 if (o1.isSubActiveAndSimPresent() && !o2.isSubActiveAndSimPresent()) { 3443 return 1; 3444 } 3445 if (o2.isSubActiveAndSimPresent() && !o1.isSubActiveAndSimPresent()) { 3446 return -1; 3447 } 3448 // First start by seeing if either of the phone slots are locked. If they 3449 // are, then sort by non-locked SIM first. If they are both locked, sort 3450 // by capability instead. 3451 if (o1.isLocked && !o2.isLocked) { 3452 return -1; 3453 } 3454 if (o2.isLocked && !o1.isLocked) { 3455 return 1; 3456 } 3457 // Prefer slots where the number is considered emergency. 3458 if (!o1.hasDialedEmergencyNumber && o2.hasDialedEmergencyNumber) { 3459 return -1; 3460 } 3461 if (o1.hasDialedEmergencyNumber && !o2.hasDialedEmergencyNumber) { 3462 return 1; 3463 } 3464 // sort by number of RadioAccessFamily Capabilities. 3465 int compare = RadioAccessFamily.compare(o1.capabilities, o2.capabilities); 3466 if (compare == 0) { 3467 if (firstOccupiedSlot != null) { 3468 // If the RAF capability is the same, choose based on whether or 3469 // not any of the slots are occupied with a SIM card (if both 3470 // are, always choose the first). 3471 if (o1.slotId == firstOccupiedSlot.getPhoneId()) { 3472 return 1; 3473 } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) { 3474 return -1; 3475 } 3476 } else { 3477 // No slots have SIMs detected in them, so weight the default 3478 // Phone Id greater than the others. 3479 if (o1.slotId == defaultPhoneId) { 3480 return 1; 3481 } else if (o2.slotId == defaultPhoneId) { 3482 return -1; 3483 } 3484 } 3485 } 3486 return compare; 3487 }); 3488 } 3489 int mostCapablePhoneId = phoneSlotStatus.get(phoneSlotStatus.size() - 1).slotId; 3490 Log.i(this, "getFirstPhoneForEmergencyCall, Using Phone Id: " + mostCapablePhoneId + 3491 "with highest capability"); 3492 return mPhoneFactoryProxy.getPhone(mostCapablePhoneId); 3493 } else { 3494 // 7) 3495 return firstPhoneWithSim; 3496 } 3497 } 3498 } 3499 isAvailableForEmergencyCalls(Phone phone)3500 private boolean isAvailableForEmergencyCalls(Phone phone) { 3501 return isAvailableForEmergencyCalls(phone, 3502 EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY); 3503 } 3504 3505 /** 3506 * Determines if the phone is available for an emergency call given the specified routing. 3507 * 3508 * @param phone the phone to check the service availability for 3509 * @param routing the emergency call routing for this call 3510 */ 3511 @VisibleForTesting isAvailableForEmergencyCalls(Phone phone, @EmergencyNumber.EmergencyCallRouting int routing)3512 public boolean isAvailableForEmergencyCalls(Phone phone, 3513 @EmergencyNumber.EmergencyCallRouting int routing) { 3514 if (phone.getImsRegistrationTech() == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) { 3515 // When a Phone is registered to Cross-SIM calling, there must always be a Phone on the 3516 // other sub which is registered to cellular, so that must be selected. 3517 Log.d(this, "isAvailableForEmergencyCalls: skipping over phone " 3518 + phone + " as it is registered to CROSS_SIM"); 3519 return false; 3520 } 3521 3522 // In service phones are always appropriate for emergency calls. 3523 if (ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState()) { 3524 return true; 3525 } 3526 3527 // If the call routing is unknown or is using emergency routing, an emergency only attach is 3528 // sufficient for placing the emergency call. Normal routed emergency calls cannot be 3529 // placed on an emergency-only phone. 3530 return (routing != EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL 3531 && phone.getServiceState().isEmergencyOnly()); 3532 } 3533 3534 /** 3535 * Determines if the connection should allow mute. 3536 * 3537 * @param phone The current phone. 3538 * @return {@code True} if the connection should allow mute. 3539 */ allowsMute(Phone phone)3540 private boolean allowsMute(Phone phone) { 3541 // For CDMA phones, check if we are in Emergency Callback Mode (ECM). Mute is disallowed 3542 // in ECM mode. 3543 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 3544 if (phone.isInEcm()) { 3545 return false; 3546 } 3547 } 3548 3549 return true; 3550 } 3551 getTelephonyConnectionListener()3552 TelephonyConnection.TelephonyConnectionListener getTelephonyConnectionListener() { 3553 return mTelephonyConnectionListener; 3554 } 3555 3556 /** 3557 * When a {@link TelephonyConnection} has its underlying original connection configured, 3558 * we need to add it to the correct conference controller. 3559 * 3560 * @param connection The connection to be added to the controller 3561 */ addConnectionToConferenceController(TelephonyConnection connection)3562 public void addConnectionToConferenceController(TelephonyConnection connection) { 3563 // TODO: Need to revisit what happens when the original connection for the 3564 // TelephonyConnection changes. If going from CDMA --> GSM (for example), the 3565 // instance of TelephonyConnection will still be a CdmaConnection, not a GsmConnection. 3566 // The CDMA conference controller makes the assumption that it will only have CDMA 3567 // connections in it, while the other conference controllers aren't as restrictive. Really, 3568 // when we go between CDMA and GSM we should replace the TelephonyConnection. 3569 if (connection.isImsConnection()) { 3570 Log.d(this, "Adding IMS connection to conference controller: " + connection); 3571 mImsConferenceController.add(connection); 3572 mTelephonyConferenceController.remove(connection); 3573 if (connection instanceof CdmaConnection) { 3574 mCdmaConferenceController.remove((CdmaConnection) connection); 3575 } 3576 } else { 3577 int phoneType = connection.getCall().getPhone().getPhoneType(); 3578 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 3579 Log.d(this, "Adding GSM connection to conference controller: " + connection); 3580 mTelephonyConferenceController.add(connection); 3581 if (connection instanceof CdmaConnection) { 3582 mCdmaConferenceController.remove((CdmaConnection) connection); 3583 } 3584 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA && 3585 connection instanceof CdmaConnection) { 3586 Log.d(this, "Adding CDMA connection to conference controller: " + connection); 3587 mCdmaConferenceController.add((CdmaConnection) connection); 3588 mTelephonyConferenceController.remove(connection); 3589 } 3590 Log.d(this, "Removing connection from IMS conference controller: " + connection); 3591 mImsConferenceController.remove(connection); 3592 } 3593 } 3594 3595 /** 3596 * Create a new CDMA connection. CDMA connections have additional limitations when creating 3597 * additional calls which are handled in this method. Specifically, CDMA has a "FLASH" command 3598 * that can be used for three purposes: merging a call, swapping unmerged calls, and adding 3599 * a new outgoing call. The function of the flash command depends on the context of the current 3600 * set of calls. This method will prevent an outgoing call from being made if it is not within 3601 * the right circumstances to support adding a call. 3602 */ checkAdditionalOutgoingCallLimits(Phone phone)3603 private Connection checkAdditionalOutgoingCallLimits(Phone phone) { 3604 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 3605 // Check to see if any CDMA conference calls exist, and if they do, check them for 3606 // limitations. 3607 for (Conference conference : getAllConferences()) { 3608 if (conference instanceof CdmaConference) { 3609 CdmaConference cdmaConf = (CdmaConference) conference; 3610 3611 // If the CDMA conference has not been merged, add-call will not work, so fail 3612 // this request to add a call. 3613 if ((cdmaConf.getConnectionCapabilities() 3614 & Connection.CAPABILITY_MERGE_CONFERENCE) != 0) { 3615 return Connection.createFailedConnection(new DisconnectCause( 3616 DisconnectCause.RESTRICTED, 3617 null, 3618 getResources().getString(R.string.callFailed_cdma_call_limit), 3619 "merge-capable call exists, prevent flash command.")); 3620 } 3621 } 3622 } 3623 } 3624 3625 return null; // null means nothing went wrong, and call should continue. 3626 } 3627 3628 /** 3629 * For outgoing dialed calls, potentially send a ConnectionEvent if the user is on WFC and is 3630 * dialing an international number. 3631 * @param telephonyConnection The connection. 3632 */ maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection)3633 private void maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection) { 3634 if (telephonyConnection == null || telephonyConnection.getPhone() == null || 3635 telephonyConnection.getPhone().getDefaultPhone() == null) { 3636 return; 3637 } 3638 Phone phone = telephonyConnection.getPhone().getDefaultPhone(); 3639 if (phone instanceof GsmCdmaPhone) { 3640 GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone; 3641 if (telephonyConnection.isOutgoingCall() && 3642 gsmCdmaPhone.isNotificationOfWfcCallRequired( 3643 telephonyConnection.getOriginalConnection().getOrigDialString())) { 3644 // Send connection event to InCall UI to inform the user of the fact they 3645 // are potentially placing an international call on WFC. 3646 Log.i(this, "placeOutgoingConnection - sending international call on WFC " + 3647 "confirmation event"); 3648 telephonyConnection.sendTelephonyConnectionEvent( 3649 TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null); 3650 } 3651 } 3652 } 3653 handleTtyModeChange(boolean isTtyEnabled)3654 private void handleTtyModeChange(boolean isTtyEnabled) { 3655 Log.i(this, "handleTtyModeChange; isTtyEnabled=%b", isTtyEnabled); 3656 mIsTtyEnabled = isTtyEnabled; 3657 for (Connection connection : getAllConnections()) { 3658 if (connection instanceof TelephonyConnection) { 3659 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 3660 telephonyConnection.setTtyEnabled(isTtyEnabled); 3661 } 3662 } 3663 } 3664 closeOrDestroyConnection(Connection connection, DisconnectCause cause)3665 private void closeOrDestroyConnection(Connection connection, DisconnectCause cause) { 3666 if (connection instanceof TelephonyConnection) { 3667 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 3668 telephonyConnection.setTelephonyConnectionDisconnected(cause); 3669 // Close destroys the connection and notifies TelephonyConnection listeners. 3670 telephonyConnection.close(); 3671 } else { 3672 connection.setDisconnected(cause); 3673 connection.destroy(); 3674 } 3675 } 3676 showDataDialog(Phone phone, String number)3677 private boolean showDataDialog(Phone phone, String number) { 3678 boolean ret = false; 3679 final Context context = getApplicationContext(); 3680 String suppKey = MmiCodeUtil.getSuppServiceKey(number); 3681 if (suppKey != null) { 3682 boolean clirOverUtPrecautions = false; 3683 boolean cfOverUtPrecautions = false; 3684 boolean cbOverUtPrecautions = false; 3685 boolean cwOverUtPrecautions = false; 3686 3687 CarrierConfigManager cfgManager = (CarrierConfigManager) 3688 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 3689 if (cfgManager != null) { 3690 clirOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 3691 .getBoolean(CarrierConfigManager.KEY_CALLER_ID_OVER_UT_WARNING_BOOL); 3692 cfOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 3693 .getBoolean(CarrierConfigManager.KEY_CALL_FORWARDING_OVER_UT_WARNING_BOOL); 3694 cbOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 3695 .getBoolean(CarrierConfigManager.KEY_CALL_BARRING_OVER_UT_WARNING_BOOL); 3696 cwOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 3697 .getBoolean(CarrierConfigManager.KEY_CALL_WAITING_OVER_UT_WARNING_BOOL); 3698 } 3699 3700 boolean isSsOverUtPrecautions = SuppServicesUiUtil 3701 .isSsOverUtPrecautions(context, phone); 3702 if (isSsOverUtPrecautions) { 3703 boolean showDialog = false; 3704 if (suppKey == MmiCodeUtil.BUTTON_CLIR_KEY && clirOverUtPrecautions) { 3705 showDialog = true; 3706 } else if (suppKey == MmiCodeUtil.CALL_FORWARDING_KEY && cfOverUtPrecautions) { 3707 showDialog = true; 3708 } else if (suppKey == MmiCodeUtil.CALL_BARRING_KEY && cbOverUtPrecautions) { 3709 showDialog = true; 3710 } else if (suppKey == MmiCodeUtil.BUTTON_CW_KEY && cwOverUtPrecautions) { 3711 showDialog = true; 3712 } 3713 3714 if (showDialog) { 3715 Log.d(this, "Creating UT Data enable dialog"); 3716 String message = SuppServicesUiUtil.makeMessage(context, suppKey, phone); 3717 AlertDialog.Builder builder = FrameworksUtils.makeAlertDialogBuilder(context); 3718 DialogInterface.OnClickListener networkSettingsClickListener = 3719 new Dialog.OnClickListener() { 3720 @Override 3721 public void onClick(DialogInterface dialog, int which) { 3722 Intent intent = new Intent(Intent.ACTION_MAIN); 3723 ComponentName mobileNetworkSettingsComponent 3724 = new ComponentName( 3725 context.getString( 3726 R.string.mobile_network_settings_package), 3727 context.getString( 3728 R.string.mobile_network_settings_class)); 3729 intent.setComponent(mobileNetworkSettingsComponent); 3730 context.startActivity(intent); 3731 } 3732 }; 3733 Dialog dialog = builder.setMessage(message) 3734 .setNeutralButton(context.getResources().getString( 3735 R.string.settings_label), 3736 networkSettingsClickListener) 3737 .setPositiveButton(context.getResources().getString( 3738 R.string.supp_service_over_ut_precautions_dialog_dismiss), null) 3739 .create(); 3740 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 3741 dialog.show(); 3742 ret = true; 3743 } 3744 } 3745 } 3746 return ret; 3747 } 3748 3749 /** 3750 * Adds a {@link Conference} to the telephony ConnectionService and registers a listener for 3751 * changes to the conference. Should be used instead of {@link #addConference(Conference)}. 3752 * @param conference The conference. 3753 */ addTelephonyConference(@onNull TelephonyConferenceBase conference)3754 public void addTelephonyConference(@NonNull TelephonyConferenceBase conference) { 3755 addConference(conference); 3756 conference.addTelephonyConferenceListener(mTelephonyConferenceListener); 3757 } 3758 3759 /** 3760 * Sends a test device to device message on the active call which supports it. 3761 * Used exclusively from the telephony shell command to send a test message. 3762 * 3763 * @param message the message 3764 * @param value the value 3765 */ sendTestDeviceToDeviceMessage(int message, int value)3766 public void sendTestDeviceToDeviceMessage(int message, int value) { 3767 getAllConnections().stream() 3768 .filter(f -> f instanceof TelephonyConnection) 3769 .forEach(t -> { 3770 TelephonyConnection tc = (TelephonyConnection) t; 3771 if (!tc.isImsConnection()) { 3772 Log.w(this, "sendTestDeviceToDeviceMessage: not an IMS connection"); 3773 return; 3774 } 3775 Communicator c = tc.getCommunicator(); 3776 if (c == null) { 3777 Log.w(this, "sendTestDeviceToDeviceMessage: D2D not enabled"); 3778 return; 3779 } 3780 3781 c.sendMessages(Set.of(new Communicator.Message(message, value))); 3782 3783 }); 3784 } 3785 3786 /** 3787 * Overrides the current D2D transport, forcing the specified one to be active. Used for test. 3788 * @param transport The class simple name of the transport to make active. 3789 */ setActiveDeviceToDeviceTransport(@onNull String transport)3790 public void setActiveDeviceToDeviceTransport(@NonNull String transport) { 3791 getAllConnections().stream() 3792 .filter(f -> f instanceof TelephonyConnection) 3793 .forEach(t -> { 3794 TelephonyConnection tc = (TelephonyConnection) t; 3795 Communicator c = tc.getCommunicator(); 3796 if (c == null) { 3797 Log.w(this, "setActiveDeviceToDeviceTransport: D2D not enabled"); 3798 return; 3799 } 3800 Log.i(this, "setActiveDeviceToDeviceTransport: callId=%s, set to: %s", 3801 tc.getTelecomCallId(), transport); 3802 c.setTransportActive(transport); 3803 }); 3804 } 3805 adjustAccountHandle(Phone phone, PhoneAccountHandle origAccountHandle)3806 private PhoneAccountHandle adjustAccountHandle(Phone phone, 3807 PhoneAccountHandle origAccountHandle) { 3808 int origSubId = PhoneUtils.getSubIdForPhoneAccountHandle(origAccountHandle); 3809 int subId = phone.getSubId(); 3810 if (origSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID 3811 && subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID 3812 && origSubId != subId) { 3813 PhoneAccountHandle handle = TelecomAccountRegistry.getInstance(this) 3814 .getPhoneAccountHandleForSubId(subId); 3815 if (handle != null) { 3816 return handle; 3817 } 3818 } 3819 return origAccountHandle; 3820 } 3821 3822 /** 3823 * For the passed in incoming {@link TelephonyConnection}, for non- dual active voice devices, 3824 * adds {@link Connection#EXTRA_ANSWERING_DROPS_FG_CALL} if there are ongoing calls on another 3825 * subscription (ie phone account handle) than the one passed in. 3826 * @param connection The connection. 3827 * @param phoneAccountHandle The {@link PhoneAccountHandle} the incoming call originated on; 3828 * this is passed in because 3829 * {@link Connection#getPhoneAccountHandle()} is not set until after 3830 * {@link ConnectionService#onCreateIncomingConnection( 3831 * PhoneAccountHandle, ConnectionRequest)} returns. 3832 */ maybeIndicateAnsweringWillDisconnect(@onNull TelephonyConnection connection, @NonNull PhoneAccountHandle phoneAccountHandle)3833 public void maybeIndicateAnsweringWillDisconnect(@NonNull TelephonyConnection connection, 3834 @NonNull PhoneAccountHandle phoneAccountHandle) { 3835 if (mTelephonyManagerProxy.isConcurrentCallsPossible()) { 3836 return; 3837 } 3838 if (isCallPresentOnOtherSub(phoneAccountHandle)) { 3839 Log.i(this, "maybeIndicateAnsweringWillDisconnect; answering call %s will cause a call " 3840 + "on another subscription to drop.", connection.getTelecomCallId()); 3841 Bundle extras = new Bundle(); 3842 extras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true); 3843 connection.putExtras(extras); 3844 } 3845 } 3846 3847 /** 3848 * Checks to see if there are calls present on a sub other than the one passed in. 3849 * @param incomingHandle The new incoming connection {@link PhoneAccountHandle} 3850 */ isCallPresentOnOtherSub(@onNull PhoneAccountHandle incomingHandle)3851 private boolean isCallPresentOnOtherSub(@NonNull PhoneAccountHandle incomingHandle) { 3852 return getAllConnections().stream() 3853 .filter(c -> 3854 // Exclude multiendpoint calls as they're not on this device. 3855 (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0 3856 // Include any calls not on same sub as current connection. 3857 && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle)) 3858 .count() > 0; 3859 } 3860 3861 /** 3862 * Where there are ongoing calls on another subscription other than the one specified, 3863 * disconnect these calls for non-DSDA devices. This is used where there is an incoming call on 3864 * one sub, but there are ongoing calls on another sub which need to be disconnected. 3865 * @param incomingHandle The incoming {@link PhoneAccountHandle}. 3866 */ maybeDisconnectCallsOnOtherSubs(@onNull PhoneAccountHandle incomingHandle)3867 public void maybeDisconnectCallsOnOtherSubs(@NonNull PhoneAccountHandle incomingHandle) { 3868 Log.i(this, "maybeDisconnectCallsOnOtherSubs: check for calls not on %s", incomingHandle); 3869 maybeDisconnectCallsOnOtherSubs(getAllConnections(), incomingHandle, 3870 mTelephonyManagerProxy); 3871 } 3872 3873 /** 3874 * Used by {@link #maybeDisconnectCallsOnOtherSubs(PhoneAccountHandle)} to evaluate and perform 3875 * call disconnection. This method exists as a convenience so that it is possible to unit test 3876 * the core functionality. 3877 * @param connections the calls to check. 3878 * @param incomingHandle the incoming handle. 3879 * @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance. 3880 */ 3881 @VisibleForTesting maybeDisconnectCallsOnOtherSubs(@onNull Collection<Connection> connections, @NonNull PhoneAccountHandle incomingHandle, TelephonyManagerProxy telephonyManagerProxy)3882 public static void maybeDisconnectCallsOnOtherSubs(@NonNull Collection<Connection> connections, 3883 @NonNull PhoneAccountHandle incomingHandle, 3884 TelephonyManagerProxy telephonyManagerProxy) { 3885 if (telephonyManagerProxy.isConcurrentCallsPossible()) { 3886 return; 3887 } 3888 connections.stream() 3889 .filter(c -> 3890 // Exclude multiendpoint calls as they're not on this device. 3891 (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) 3892 == 0 3893 // Include any calls not on same sub as current connection. 3894 && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle)) 3895 .forEach(c -> { 3896 if (c instanceof TelephonyConnection) { 3897 TelephonyConnection tc = (TelephonyConnection) c; 3898 if (!tc.shouldTreatAsEmergencyCall()) { 3899 Log.i(LOG_TAG, 3900 "maybeDisconnectCallsOnOtherSubs: disconnect %s due to " 3901 + "incoming call on other sub.", 3902 tc.getTelecomCallId()); 3903 // Note: intentionally calling hangup instead of onDisconnect. 3904 // onDisconnect posts the disconnection to a handle which means that the 3905 // disconnection will take place AFTER we answer the incoming call. 3906 tc.hangup(android.telephony.DisconnectCause.LOCAL); 3907 } 3908 } 3909 }); 3910 } 3911 onHold(Conferenceable conferenceable)3912 static void onHold(Conferenceable conferenceable) { 3913 if (conferenceable instanceof Connection) { 3914 Connection connection = (Connection) conferenceable; 3915 connection.onHold(); 3916 } else if (conferenceable instanceof Conference) { 3917 Conference conference = (Conference) conferenceable; 3918 conference.onHold(); 3919 } else { 3920 throw new IllegalArgumentException( 3921 "onHold(): Unexpected conferenceable! " + conferenceable); 3922 } 3923 } 3924 onUnhold(Conferenceable conferenceable)3925 static void onUnhold(Conferenceable conferenceable) { 3926 if (conferenceable instanceof Connection) { 3927 Connection connection = (Connection) conferenceable; 3928 connection.onUnhold(); 3929 } else if (conferenceable instanceof Conference) { 3930 Conference conference = (Conference) conferenceable; 3931 conference.onUnhold(); 3932 } else { 3933 throw new IllegalArgumentException( 3934 "onUnhold(): Unexpected conferenceable! " + conferenceable); 3935 } 3936 } 3937 hangup(Conferenceable conferenceable, int code)3938 private static void hangup(Conferenceable conferenceable, int code) { 3939 if (conferenceable instanceof TelephonyConnection) { 3940 ((TelephonyConnection) conferenceable).hangup(code); 3941 } else if (conferenceable instanceof Conference) { 3942 ((Conference) conferenceable).onDisconnect(); 3943 } else { 3944 Log.w(LOG_TAG, "hangup(): Unexpected conferenceable! " + conferenceable); 3945 } 3946 } 3947 3948 /** 3949 * Evaluates whether a connection or conference exists on subscriptions other than the one 3950 * corresponding to the existing {@link PhoneAccountHandle}. 3951 * @param connections all individual connections, including conference participants. 3952 * @param conferences all conferences. 3953 * @param currentHandle the existing call handle; 3954 * @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance. 3955 */ maybeGetFirstConferenceableFromOtherSubscription( @onNull Collection<Connection> connections, @NonNull Collection<Conference> conferences, @NonNull PhoneAccountHandle currentHandle, TelephonyManagerProxy telephonyManagerProxy)3956 private static @Nullable Conferenceable maybeGetFirstConferenceableFromOtherSubscription( 3957 @NonNull Collection<Connection> connections, 3958 @NonNull Collection<Conference> conferences, 3959 @NonNull PhoneAccountHandle currentHandle, 3960 TelephonyManagerProxy telephonyManagerProxy) { 3961 if (!telephonyManagerProxy.isConcurrentCallsPossible()) { 3962 return null; 3963 } 3964 3965 List<Conference> otherSubConferences = conferences.stream() 3966 .filter(c -> 3967 // Exclude multiendpoint calls as they're not on this device. 3968 (c.getConnectionProperties() 3969 & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0 3970 // Include any conferences not on same sub as current connection. 3971 && !Objects.equals(c.getPhoneAccountHandle(), 3972 currentHandle)) 3973 .toList(); 3974 if (!otherSubConferences.isEmpty()) { 3975 Log.i(LOG_TAG, "maybeGetFirstConferenceable: found " 3976 + otherSubConferences.get(0).getTelecomCallId() + " on " 3977 + otherSubConferences.get(0).getPhoneAccountHandle()); 3978 return otherSubConferences.get(0); 3979 } 3980 3981 // Considers Connections (including conference participants) only if no conferences. 3982 List<Connection> otherSubConnections = connections.stream() 3983 .filter(c -> 3984 // Exclude multiendpoint calls as they're not on this device. 3985 (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0 3986 // Include any calls not on same sub as current connection. 3987 && !Objects.equals(c.getPhoneAccountHandle(), 3988 currentHandle)).toList(); 3989 3990 if (!otherSubConnections.isEmpty()) { 3991 if (otherSubConnections.size() > 1) { 3992 Log.w(LOG_TAG, "Unexpected number of connections: " 3993 + otherSubConnections.size() + " on other sub!"); 3994 } 3995 Log.i(LOG_TAG, "maybeGetFirstConferenceable: found " 3996 + otherSubConnections.get(0).getTelecomCallId() + " on " 3997 + otherSubConnections.get(0).getPhoneAccountHandle()); 3998 return otherSubConnections.get(0); 3999 } 4000 return null; 4001 } 4002 4003 /** 4004 * Where there are ongoing calls on multiple subscriptions for DSDA devices, let the 'hold' 4005 * button perform an unhold on the other sub's Connection or Conference. This covers for Dialer 4006 * apps that may not have a dedicated 'swap' button for calls across different subs. 4007 * @param currentHandle The {@link PhoneAccountHandle} of the current active voice call. 4008 */ maybeUnholdCallsOnOtherSubs( @onNull PhoneAccountHandle currentHandle)4009 public void maybeUnholdCallsOnOtherSubs( 4010 @NonNull PhoneAccountHandle currentHandle) { 4011 Log.i(this, "maybeUnholdCallsOnOtherSubs: check for calls not on %s", 4012 currentHandle); 4013 maybeUnholdCallsOnOtherSubs(getAllConnections(), getAllConferences(), 4014 currentHandle, mTelephonyManagerProxy); 4015 } 4016 4017 /** 4018 * Where there are ongoing calls on multiple subscriptions for DSDA devices, let the 'hold' 4019 * button perform an unhold on the other sub's Connection or Conference. This is a convenience 4020 * method to unit test the core functionality. 4021 * 4022 * @param connections all individual connections, including conference participants. 4023 * @param conferences all conferences. 4024 * @param currentHandle The {@link PhoneAccountHandle} of the current active call. 4025 * @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance. 4026 */ 4027 @VisibleForTesting maybeUnholdCallsOnOtherSubs(@onNull Collection<Connection> connections, @NonNull Collection<Conference> conferences, @NonNull PhoneAccountHandle currentHandle, TelephonyManagerProxy telephonyManagerProxy)4028 protected static void maybeUnholdCallsOnOtherSubs(@NonNull Collection<Connection> connections, 4029 @NonNull Collection<Conference> conferences, 4030 @NonNull PhoneAccountHandle currentHandle, 4031 TelephonyManagerProxy telephonyManagerProxy) { 4032 Conferenceable c = maybeGetFirstConferenceableFromOtherSubscription( 4033 connections, conferences, currentHandle, telephonyManagerProxy); 4034 if (c != null) { 4035 onUnhold(c); 4036 } 4037 } 4038 4039 /** 4040 * For DSDA devices, when an outgoing call is dialed out from the 2nd sub, holds the first call. 4041 * 4042 * @param outgoingHandle The outgoing {@link PhoneAccountHandle}. 4043 * @return the Conferenceable representing the Connection or Conference to be held. 4044 */ maybeHoldCallsOnOtherSubs( @onNull PhoneAccountHandle outgoingHandle)4045 private @Nullable Conferenceable maybeHoldCallsOnOtherSubs( 4046 @NonNull PhoneAccountHandle outgoingHandle) { 4047 Log.i(this, "maybeHoldCallsOnOtherSubs: check for calls not on %s", 4048 outgoingHandle); 4049 return maybeHoldCallsOnOtherSubs(getAllConnections(), getAllConferences(), 4050 outgoingHandle, mTelephonyManagerProxy); 4051 } 4052 4053 /** 4054 * For DSDA devices, when an outgoing call is dialed out from the 2nd sub, holds the first call. 4055 * This is a convenience method to unit test the core functionality. 4056 * 4057 * @param connections all individual connections, including conference participants. 4058 * @param conferences all conferences. 4059 * @param outgoingHandle The outgoing {@link PhoneAccountHandle}. 4060 * @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance. 4061 * @return the {@link Conferenceable} representing the Connection or Conference to be held. 4062 */ 4063 @VisibleForTesting maybeHoldCallsOnOtherSubs( @onNull Collection<Connection> connections, @NonNull Collection<Conference> conferences, @NonNull PhoneAccountHandle outgoingHandle, TelephonyManagerProxy telephonyManagerProxy)4064 protected static @Nullable Conferenceable maybeHoldCallsOnOtherSubs( 4065 @NonNull Collection<Connection> connections, 4066 @NonNull Collection<Conference> conferences, 4067 @NonNull PhoneAccountHandle outgoingHandle, 4068 TelephonyManagerProxy telephonyManagerProxy) { 4069 Conferenceable c = maybeGetFirstConferenceableFromOtherSubscription( 4070 connections, conferences, outgoingHandle, telephonyManagerProxy); 4071 if (c != null) { 4072 onHold(c); 4073 return c; 4074 } 4075 return null; 4076 } 4077 disconnectAllCallsOnOtherSubs(@onNull PhoneAccountHandle handle)4078 private void disconnectAllCallsOnOtherSubs (@NonNull PhoneAccountHandle handle) { 4079 Collection<Connection>connections = getAllConnections(); 4080 connections.stream() 4081 .filter(c -> 4082 (c.getState() == Connection.STATE_ACTIVE 4083 || c.getState() == Connection.STATE_HOLDING) 4084 // Include any calls not on same sub as current connection. 4085 && !Objects.equals(c.getPhoneAccountHandle(), handle)) 4086 .forEach(c -> { 4087 if (c instanceof TelephonyConnection) { 4088 TelephonyConnection tc = (TelephonyConnection) c; 4089 Log.i(LOG_TAG, "disconnectAllCallsOnOtherSubs: disconnect" + 4090 " %s due to redial happened on other sub.", 4091 tc.getTelecomCallId()); 4092 tc.hangup(android.telephony.DisconnectCause.LOCAL); 4093 } 4094 }); 4095 } 4096 getActiveCallDomain(int subId)4097 private @NetworkRegistrationInfo.Domain int getActiveCallDomain(int subId) { 4098 for (Connection c: getAllConnections()) { 4099 if ((c instanceof TelephonyConnection)) { 4100 TelephonyConnection connection = (TelephonyConnection) c; 4101 Phone phone = connection.getPhone(); 4102 if (phone == null) { 4103 continue; 4104 } 4105 4106 if (phone.getSubId() == subId) { 4107 if (phone instanceof GsmCdmaPhone) { 4108 return NetworkRegistrationInfo.DOMAIN_CS; 4109 } else if (phone instanceof ImsPhone) { 4110 return NetworkRegistrationInfo.DOMAIN_PS; 4111 } 4112 } 4113 } 4114 } 4115 return NetworkRegistrationInfo.DOMAIN_UNKNOWN; 4116 } 4117 handleEmergencyCallStartedForSatelliteSOSMessageRecommender( @onNull TelephonyConnection connection, @NonNull Phone phone)4118 private void handleEmergencyCallStartedForSatelliteSOSMessageRecommender( 4119 @NonNull TelephonyConnection connection, @NonNull Phone phone) { 4120 if (mSatelliteSOSMessageRecommender == null) { 4121 mSatelliteSOSMessageRecommender = new SatelliteSOSMessageRecommender( 4122 phone.getContext().getMainLooper()); 4123 } 4124 connection.addTelephonyConnectionListener(mEmergencyConnectionSatelliteListener); 4125 mSatelliteSOSMessageRecommender.onEmergencyCallStarted(connection, phone); 4126 } 4127 4128 /** 4129 * Check whether making a call is disallowed while using satellite 4130 * @param phone phone object whose supported services needs to be checked 4131 * @return {@code true} if network does not support calls while using satellite 4132 * else {@code false}. 4133 */ isCallDisallowedDueToSatellite(Phone phone)4134 private boolean isCallDisallowedDueToSatellite(Phone phone) { 4135 if (phone == null) { 4136 return false; 4137 } 4138 4139 ServiceState serviceState = phone.getServiceState(); 4140 if (!serviceState.isUsingNonTerrestrialNetwork()) { 4141 // Device is not connected to satellite 4142 return false; 4143 } 4144 4145 for (NetworkRegistrationInfo nri : serviceState.getNetworkRegistrationInfoList()) { 4146 if (nri.isNonTerrestrialNetwork() 4147 && nri.getAvailableServices().contains( 4148 NetworkRegistrationInfo.SERVICE_TYPE_VOICE)) { 4149 // Call is supported while using satellite 4150 return false; 4151 } 4152 } 4153 4154 // Call is disallowed while using satellite 4155 return true; 4156 } 4157 } 4158