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