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