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 android.annotation.NonNull; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.content.ActivityNotFoundException; 23 import android.content.BroadcastReceiver; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.Intent; 28 import android.content.IntentFilter; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.ParcelUuid; 32 import android.telecom.Conference; 33 import android.telecom.Connection; 34 import android.telecom.ConnectionRequest; 35 import android.telecom.ConnectionService; 36 import android.telecom.DisconnectCause; 37 import android.telecom.PhoneAccount; 38 import android.telecom.PhoneAccountHandle; 39 import android.telecom.TelecomManager; 40 import android.telecom.VideoProfile; 41 import android.telephony.CarrierConfigManager; 42 import android.telephony.PhoneNumberUtils; 43 import android.telephony.RadioAccessFamily; 44 import android.telephony.ServiceState; 45 import android.telephony.SubscriptionManager; 46 import android.telephony.TelephonyManager; 47 import android.telephony.emergency.EmergencyNumber; 48 import android.text.TextUtils; 49 import android.util.Pair; 50 import android.view.WindowManager; 51 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.internal.telephony.Call; 54 import com.android.internal.telephony.CallStateException; 55 import com.android.internal.telephony.GsmCdmaPhone; 56 import com.android.internal.telephony.IccCard; 57 import com.android.internal.telephony.IccCardConstants; 58 import com.android.internal.telephony.Phone; 59 import com.android.internal.telephony.PhoneConstants; 60 import com.android.internal.telephony.PhoneFactory; 61 import com.android.internal.telephony.PhoneSwitcher; 62 import com.android.internal.telephony.RIL; 63 import com.android.internal.telephony.SubscriptionController; 64 import com.android.internal.telephony.d2d.Communicator; 65 import com.android.internal.telephony.imsphone.ImsExternalCallTracker; 66 import com.android.internal.telephony.imsphone.ImsPhone; 67 import com.android.internal.telephony.imsphone.ImsPhoneConnection; 68 import com.android.phone.MMIDialogActivity; 69 import com.android.phone.PhoneUtils; 70 import com.android.phone.R; 71 import com.android.phone.callcomposer.CallComposerPictureManager; 72 import com.android.phone.settings.SuppServicesUiUtil; 73 74 import java.lang.ref.WeakReference; 75 import java.util.ArrayList; 76 import java.util.Arrays; 77 import java.util.Collection; 78 import java.util.Collections; 79 import java.util.HashMap; 80 import java.util.HashSet; 81 import java.util.LinkedList; 82 import java.util.List; 83 import java.util.Map; 84 import java.util.Objects; 85 import java.util.Queue; 86 import java.util.concurrent.CompletableFuture; 87 import java.util.function.Consumer; 88 import java.util.regex.Pattern; 89 90 import javax.annotation.Nullable; 91 92 /** 93 * Service for making GSM and CDMA connections. 94 */ 95 public class TelephonyConnectionService extends ConnectionService { 96 private static final String LOG_TAG = TelephonyConnectionService.class.getSimpleName(); 97 // Timeout before we continue with the emergency call without waiting for DDS switch response 98 // from the modem. 99 private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000; 100 101 // If configured, reject attempts to dial numbers matching this pattern. 102 private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN = 103 Pattern.compile("\\*228[0-9]{0,2}"); 104 105 private final TelephonyConnectionServiceProxy mTelephonyConnectionServiceProxy = 106 new TelephonyConnectionServiceProxy() { 107 @Override 108 public Collection<Connection> getAllConnections() { 109 return TelephonyConnectionService.this.getAllConnections(); 110 } 111 @Override 112 public void addConference(TelephonyConference mTelephonyConference) { 113 TelephonyConnectionService.this.addTelephonyConference(mTelephonyConference); 114 } 115 @Override 116 public void addConference(ImsConference mImsConference) { 117 TelephonyConnectionService.this.addTelephonyConference(mImsConference); 118 } 119 @Override 120 public void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 121 Connection connection) { 122 TelephonyConnectionService.this 123 .addExistingConnection(phoneAccountHandle, connection); 124 } 125 @Override 126 public void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 127 Connection connection, Conference conference) { 128 TelephonyConnectionService.this 129 .addExistingConnection(phoneAccountHandle, connection, conference); 130 } 131 @Override 132 public void addConnectionToConferenceController(TelephonyConnection connection) { 133 TelephonyConnectionService.this.addConnectionToConferenceController(connection); 134 } 135 }; 136 137 private final BroadcastReceiver mTtyBroadcastReceiver = new BroadcastReceiver() { 138 @Override 139 public void onReceive(Context context, Intent intent) { 140 String action = intent.getAction(); 141 Log.v(this, "onReceive, action: %s", action); 142 if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) { 143 int newPreferredTtyMode = intent.getIntExtra( 144 TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF); 145 146 boolean isTtyNowEnabled = newPreferredTtyMode != TelecomManager.TTY_MODE_OFF; 147 if (isTtyNowEnabled != mIsTtyEnabled) { 148 handleTtyModeChange(isTtyNowEnabled); 149 } 150 } 151 } 152 }; 153 154 private final TelephonyConferenceController mTelephonyConferenceController = 155 new TelephonyConferenceController(mTelephonyConnectionServiceProxy); 156 private final CdmaConferenceController mCdmaConferenceController = 157 new CdmaConferenceController(this); 158 private ImsConferenceController mImsConferenceController; 159 160 private ComponentName mExpectedComponentName = null; 161 private RadioOnHelper mRadioOnHelper; 162 private EmergencyTonePlayer mEmergencyTonePlayer; 163 private HoldTracker mHoldTracker; 164 private boolean mIsTtyEnabled; 165 /** Set to true when there is an emergency call pending which will potential trigger a dial. 166 * This must be set to false when the call is dialed. */ 167 private volatile boolean mIsEmergencyCallPending; 168 169 // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has 170 // already tried to connect with. There should be only one TelephonyConnection trying to place a 171 // call at one time. We also only access this cache from a TelephonyConnection that wishes to 172 // redial, so we use a WeakReference that will become stale once the TelephonyConnection is 173 // destroyed. 174 @VisibleForTesting 175 public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache; 176 private DeviceState mDeviceState = new DeviceState(); 177 178 /** 179 * Keeps track of the status of a SIM slot. 180 */ 181 private static class SlotStatus { 182 public int slotId; 183 // RAT capabilities 184 public int capabilities; 185 // By default, we will assume that the slots are not locked. 186 public boolean isLocked = false; 187 // Is the emergency number associated with the slot 188 public boolean hasDialedEmergencyNumber = false; 189 //SimState 190 public int simState; 191 SlotStatus(int slotId, int capabilities)192 public SlotStatus(int slotId, int capabilities) { 193 this.slotId = slotId; 194 this.capabilities = capabilities; 195 } 196 } 197 198 /** 199 * SubscriptionManager dependencies for testing. 200 */ 201 @VisibleForTesting 202 public interface SubscriptionManagerProxy { getDefaultVoicePhoneId()203 int getDefaultVoicePhoneId(); getSimStateForSlotIdx(int slotId)204 int getSimStateForSlotIdx(int slotId); getPhoneId(int subId)205 int getPhoneId(int subId); 206 } 207 208 private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() { 209 @Override 210 public int getDefaultVoicePhoneId() { 211 return SubscriptionManager.getDefaultVoicePhoneId(); 212 } 213 214 @Override 215 public int getSimStateForSlotIdx(int slotId) { 216 return SubscriptionManager.getSimStateForSlotIndex(slotId); 217 } 218 219 @Override 220 public int getPhoneId(int subId) { 221 return SubscriptionManager.getPhoneId(subId); 222 } 223 }; 224 225 /** 226 * TelephonyManager dependencies for testing. 227 */ 228 @VisibleForTesting 229 public interface TelephonyManagerProxy { getPhoneCount()230 int getPhoneCount(); hasIccCard(int slotId)231 boolean hasIccCard(int slotId); isCurrentEmergencyNumber(String number)232 boolean isCurrentEmergencyNumber(String number); getCurrentEmergencyNumberList()233 Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList(); 234 } 235 236 private TelephonyManagerProxy mTelephonyManagerProxy; 237 238 private class TelephonyManagerProxyImpl implements TelephonyManagerProxy { 239 private final TelephonyManager mTelephonyManager; 240 241 TelephonyManagerProxyImpl(Context context)242 TelephonyManagerProxyImpl(Context context) { 243 mTelephonyManager = new TelephonyManager(context); 244 } 245 246 @Override getPhoneCount()247 public int getPhoneCount() { 248 return mTelephonyManager.getPhoneCount(); 249 } 250 251 @Override hasIccCard(int slotId)252 public boolean hasIccCard(int slotId) { 253 return mTelephonyManager.hasIccCard(slotId); 254 } 255 256 @Override isCurrentEmergencyNumber(String number)257 public boolean isCurrentEmergencyNumber(String number) { 258 try { 259 return mTelephonyManager.isEmergencyNumber(number); 260 } catch (IllegalStateException ise) { 261 return false; 262 } 263 } 264 265 @Override getCurrentEmergencyNumberList()266 public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList() { 267 try { 268 return mTelephonyManager.getEmergencyNumberList(); 269 } catch (IllegalStateException ise) { 270 return new HashMap<>(); 271 } 272 } 273 } 274 275 /** 276 * PhoneFactory Dependencies for testing. 277 */ 278 @VisibleForTesting 279 public interface PhoneFactoryProxy { getPhone(int index)280 Phone getPhone(int index); getDefaultPhone()281 Phone getDefaultPhone(); getPhones()282 Phone[] getPhones(); 283 } 284 285 private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() { 286 @Override 287 public Phone getPhone(int index) { 288 return PhoneFactory.getPhone(index); 289 } 290 291 @Override 292 public Phone getDefaultPhone() { 293 return PhoneFactory.getDefaultPhone(); 294 } 295 296 @Override 297 public Phone[] getPhones() { 298 return PhoneFactory.getPhones(); 299 } 300 }; 301 302 /** 303 * PhoneUtils dependencies for testing. 304 */ 305 @VisibleForTesting 306 public interface PhoneUtilsProxy { getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle)307 int getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle); makePstnPhoneAccountHandle(Phone phone)308 PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone); makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, boolean isEmergency)309 PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, 310 boolean isEmergency); 311 } 312 313 private PhoneUtilsProxy mPhoneUtilsProxy = new PhoneUtilsProxy() { 314 @Override 315 public int getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle) { 316 return PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle); 317 } 318 319 @Override 320 public PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) { 321 return PhoneUtils.makePstnPhoneAccountHandle(phone); 322 } 323 324 @Override 325 public PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, 326 boolean isEmergency) { 327 return PhoneUtils.makePstnPhoneAccountHandleWithPrefix(phone, prefix, isEmergency); 328 } 329 }; 330 331 /** 332 * PhoneNumberUtils dependencies for testing. 333 */ 334 @VisibleForTesting 335 public interface PhoneNumberUtilsProxy { convertToEmergencyNumber(Context context, String number)336 String convertToEmergencyNumber(Context context, String number); 337 } 338 339 private PhoneNumberUtilsProxy mPhoneNumberUtilsProxy = new PhoneNumberUtilsProxy() { 340 @Override 341 public String convertToEmergencyNumber(Context context, String number) { 342 return PhoneNumberUtils.convertToEmergencyNumber(context, number); 343 } 344 }; 345 346 /** 347 * PhoneSwitcher dependencies for testing. 348 */ 349 @VisibleForTesting 350 public interface PhoneSwitcherProxy { getPhoneSwitcher()351 PhoneSwitcher getPhoneSwitcher(); 352 } 353 354 private PhoneSwitcherProxy mPhoneSwitcherProxy = new PhoneSwitcherProxy() { 355 @Override 356 public PhoneSwitcher getPhoneSwitcher() { 357 return PhoneSwitcher.getInstance(); 358 } 359 }; 360 361 /** 362 * DisconnectCause depends on PhoneGlobals in order to get a system context. Mock out 363 * dependency for testing. 364 */ 365 @VisibleForTesting 366 public interface DisconnectCauseFactory { toTelecomDisconnectCause(int telephonyDisconnectCause, String reason)367 DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, String reason); toTelecomDisconnectCause(int telephonyDisconnectCause, String reason, int phoneId)368 DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, 369 String reason, int phoneId); 370 } 371 372 private DisconnectCauseFactory mDisconnectCauseFactory = new DisconnectCauseFactory() { 373 @Override 374 public DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, 375 String reason) { 376 return DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause, reason); 377 } 378 379 @Override 380 public DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, String reason, 381 int phoneId) { 382 return DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause, reason, 383 phoneId); 384 } 385 }; 386 387 /** 388 * Overrides SubscriptionManager dependencies for testing. 389 */ 390 @VisibleForTesting setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)391 public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) { 392 mSubscriptionManagerProxy = proxy; 393 } 394 395 /** 396 * Overrides TelephonyManager dependencies for testing. 397 */ 398 @VisibleForTesting setTelephonyManagerProxy(TelephonyManagerProxy proxy)399 public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) { 400 mTelephonyManagerProxy = proxy; 401 } 402 403 /** 404 * Overrides PhoneFactory dependencies for testing. 405 */ 406 @VisibleForTesting setPhoneFactoryProxy(PhoneFactoryProxy proxy)407 public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) { 408 mPhoneFactoryProxy = proxy; 409 } 410 411 /** 412 * Overrides configuration and settings dependencies for testing. 413 */ 414 @VisibleForTesting setDeviceState(DeviceState state)415 public void setDeviceState(DeviceState state) { 416 mDeviceState = state; 417 } 418 419 /** 420 * Overrides radioOnHelper for testing. 421 */ 422 @VisibleForTesting setRadioOnHelper(RadioOnHelper radioOnHelper)423 public void setRadioOnHelper(RadioOnHelper radioOnHelper) { 424 mRadioOnHelper = radioOnHelper; 425 } 426 427 /** 428 * Overrides PhoneSwitcher dependencies for testing. 429 */ 430 @VisibleForTesting setPhoneSwitcherProxy(PhoneSwitcherProxy proxy)431 public void setPhoneSwitcherProxy(PhoneSwitcherProxy proxy) { 432 mPhoneSwitcherProxy = proxy; 433 } 434 435 /** 436 * Overrides PhoneNumberUtils dependencies for testing. 437 */ 438 @VisibleForTesting setPhoneNumberUtilsProxy(PhoneNumberUtilsProxy proxy)439 public void setPhoneNumberUtilsProxy(PhoneNumberUtilsProxy proxy) { 440 mPhoneNumberUtilsProxy = proxy; 441 } 442 443 /** 444 * Overrides PhoneUtils dependencies for testing. 445 */ 446 @VisibleForTesting setPhoneUtilsProxy(PhoneUtilsProxy proxy)447 public void setPhoneUtilsProxy(PhoneUtilsProxy proxy) { 448 mPhoneUtilsProxy = proxy; 449 } 450 451 /** 452 * Override DisconnectCause creation for testing. 453 */ 454 @VisibleForTesting setDisconnectCauseFactory(DisconnectCauseFactory factory)455 public void setDisconnectCauseFactory(DisconnectCauseFactory factory) { 456 mDisconnectCauseFactory = factory; 457 } 458 459 /** 460 * A listener to actionable events specific to the TelephonyConnection. 461 */ 462 private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener = 463 new TelephonyConnection.TelephonyConnectionListener() { 464 @Override 465 public void onOriginalConnectionConfigured(TelephonyConnection c) { 466 addConnectionToConferenceController(c); 467 } 468 469 @Override 470 public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) { 471 retryOutgoingOriginalConnection(c, isPermanentFailure); 472 } 473 }; 474 475 private final TelephonyConferenceBase.TelephonyConferenceListener mTelephonyConferenceListener = 476 new TelephonyConferenceBase.TelephonyConferenceListener() { 477 @Override 478 public void onConferenceMembershipChanged(Connection connection) { 479 mHoldTracker.updateHoldCapability(connection.getPhoneAccountHandle()); 480 } 481 }; 482 483 @Override onCreate()484 public void onCreate() { 485 super.onCreate(); 486 mImsConferenceController = new ImsConferenceController( 487 TelecomAccountRegistry.getInstance(this), 488 mTelephonyConnectionServiceProxy, 489 // FeatureFlagProxy; used to determine if standalone call emulation is enabled. 490 // TODO: Move to carrier config 491 () -> true); 492 setTelephonyManagerProxy(new TelephonyManagerProxyImpl(getApplicationContext())); 493 mExpectedComponentName = new ComponentName(this, this.getClass()); 494 mEmergencyTonePlayer = new EmergencyTonePlayer(this); 495 TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this); 496 mHoldTracker = new HoldTracker(); 497 mIsTtyEnabled = mDeviceState.isTtyModeEnabled(this); 498 499 IntentFilter intentFilter = new IntentFilter( 500 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED); 501 registerReceiver(mTtyBroadcastReceiver, intentFilter, 502 android.Manifest.permission.MODIFY_PHONE_STATE, null); 503 } 504 505 @Override onUnbind(Intent intent)506 public boolean onUnbind(Intent intent) { 507 unregisterReceiver(mTtyBroadcastReceiver); 508 return super.onUnbind(intent); 509 } 510 placeOutgoingConference(ConnectionRequest request, Connection resultConnection, Phone phone)511 private Conference placeOutgoingConference(ConnectionRequest request, 512 Connection resultConnection, Phone phone) { 513 if (resultConnection instanceof TelephonyConnection) { 514 return placeOutgoingConference((TelephonyConnection) resultConnection, phone, request); 515 } 516 return null; 517 } 518 placeOutgoingConference(TelephonyConnection conferenceHostConnection, Phone phone, ConnectionRequest request)519 private Conference placeOutgoingConference(TelephonyConnection conferenceHostConnection, 520 Phone phone, ConnectionRequest request) { 521 updatePhoneAccount(conferenceHostConnection, phone); 522 com.android.internal.telephony.Connection originalConnection = null; 523 try { 524 originalConnection = phone.startConference( 525 getParticipantsToDial(request.getParticipants()), 526 new ImsPhone.ImsDialArgs.Builder() 527 .setVideoState(request.getVideoState()) 528 .setRttTextStream(conferenceHostConnection.getRttTextStream()) 529 .build()); 530 } catch (CallStateException e) { 531 Log.e(this, e, "placeOutgoingConference, phone.startConference exception: " + e); 532 handleCallStateException(e, conferenceHostConnection, phone); 533 return null; 534 } 535 536 if (originalConnection == null) { 537 Log.d(this, "placeOutgoingConference, phone.startConference returned null"); 538 conferenceHostConnection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 539 android.telephony.DisconnectCause.OUTGOING_FAILURE, 540 "conferenceHostConnection is null", 541 phone.getPhoneId())); 542 conferenceHostConnection.clearOriginalConnection(); 543 conferenceHostConnection.destroy(); 544 } else { 545 conferenceHostConnection.setOriginalConnection(originalConnection); 546 } 547 548 return prepareConference(conferenceHostConnection, request.getAccountHandle()); 549 } 550 prepareConference(Connection conn, PhoneAccountHandle phoneAccountHandle)551 Conference prepareConference(Connection conn, PhoneAccountHandle phoneAccountHandle) { 552 if (!(conn instanceof TelephonyConnection)) { 553 Log.w(this, "prepareConference returning NULL conference"); 554 return null; 555 } 556 557 TelephonyConnection connection = (TelephonyConnection)conn; 558 559 ImsConference conference = new ImsConference(TelecomAccountRegistry.getInstance(this), 560 mTelephonyConnectionServiceProxy, connection, 561 phoneAccountHandle, () -> true, 562 ImsConferenceController.getCarrierConfig(connection.getPhone())); 563 mImsConferenceController.addConference(conference); 564 conference.setVideoState(connection, 565 connection.getVideoState()); 566 conference.setVideoProvider(connection, 567 connection.getVideoProvider()); 568 conference.setStatusHints(connection.getStatusHints()); 569 conference.setAddress(connection.getAddress(), 570 connection.getAddressPresentation()); 571 conference.setCallerDisplayName(connection.getCallerDisplayName(), 572 connection.getCallerDisplayNamePresentation()); 573 conference.setParticipants(connection.getParticipants()); 574 return conference; 575 } 576 577 @Override onCreateIncomingConference( @ullable PhoneAccountHandle connectionManagerPhoneAccount, @NonNull final ConnectionRequest request)578 public @Nullable Conference onCreateIncomingConference( 579 @Nullable PhoneAccountHandle connectionManagerPhoneAccount, 580 @NonNull final ConnectionRequest request) { 581 Log.i(this, "onCreateIncomingConference, request: " + request); 582 Connection connection = onCreateIncomingConnection(connectionManagerPhoneAccount, request); 583 Log.d(this, "onCreateIncomingConference, connection: %s", connection); 584 if (connection == null) { 585 Log.i(this, "onCreateIncomingConference, implementation returned null connection."); 586 return Conference.createFailedConference( 587 new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"), 588 request.getAccountHandle()); 589 } 590 591 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 592 false /* isEmergencyCall*/, null /* not an emergency call */); 593 if (phone == null) { 594 Log.d(this, "onCreateIncomingConference, phone is null"); 595 return Conference.createFailedConference( 596 DisconnectCauseUtil.toTelecomDisconnectCause( 597 android.telephony.DisconnectCause.OUT_OF_SERVICE, 598 "Phone is null"), 599 request.getAccountHandle()); 600 } 601 602 return prepareConference(connection, request.getAccountHandle()); 603 } 604 605 @Override onCreateOutgoingConference( @ullable PhoneAccountHandle connectionManagerPhoneAccount, @NonNull final ConnectionRequest request)606 public @Nullable Conference onCreateOutgoingConference( 607 @Nullable PhoneAccountHandle connectionManagerPhoneAccount, 608 @NonNull final ConnectionRequest request) { 609 Log.i(this, "onCreateOutgoingConference, request: " + request); 610 Connection connection = onCreateOutgoingConnection(connectionManagerPhoneAccount, request); 611 Log.d(this, "onCreateOutgoingConference, connection: %s", connection); 612 if (connection == null) { 613 Log.i(this, "onCreateOutgoingConference, implementation returned null connection."); 614 return Conference.createFailedConference( 615 new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"), 616 request.getAccountHandle()); 617 } 618 619 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 620 false /* isEmergencyCall*/, null /* not an emergency call */); 621 if (phone == null) { 622 Log.d(this, "onCreateOutgoingConference, phone is null"); 623 return Conference.createFailedConference( 624 DisconnectCauseUtil.toTelecomDisconnectCause( 625 android.telephony.DisconnectCause.OUT_OF_SERVICE, 626 "Phone is null"), 627 request.getAccountHandle()); 628 } 629 630 return placeOutgoingConference(request, connection, phone); 631 } 632 getParticipantsToDial(List<Uri> participants)633 private String[] getParticipantsToDial(List<Uri> participants) { 634 String[] participantsToDial = new String[participants.size()]; 635 int i = 0; 636 for (Uri participant : participants) { 637 participantsToDial[i] = participant.getSchemeSpecificPart(); 638 i++; 639 } 640 return participantsToDial; 641 } 642 643 @Override onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)644 public Connection onCreateOutgoingConnection( 645 PhoneAccountHandle connectionManagerPhoneAccount, 646 final ConnectionRequest request) { 647 Log.i(this, "onCreateOutgoingConnection, request: " + request); 648 649 Uri handle = request.getAddress(); 650 boolean isAdhocConference = request.isAdhocConferenceCall(); 651 652 if (!isAdhocConference && handle == null) { 653 Log.d(this, "onCreateOutgoingConnection, handle is null"); 654 return Connection.createFailedConnection( 655 mDisconnectCauseFactory.toTelecomDisconnectCause( 656 android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, 657 "No phone number supplied")); 658 } 659 660 String scheme = handle.getScheme(); 661 String number; 662 if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) { 663 // TODO: We don't check for SecurityException here (requires 664 // CALL_PRIVILEGED permission). 665 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 666 false /* isEmergencyCall */, null /* not an emergency call */); 667 if (phone == null) { 668 Log.d(this, "onCreateOutgoingConnection, phone is null"); 669 return Connection.createFailedConnection( 670 mDisconnectCauseFactory.toTelecomDisconnectCause( 671 android.telephony.DisconnectCause.OUT_OF_SERVICE, 672 "Phone is null")); 673 } 674 number = phone.getVoiceMailNumber(); 675 if (TextUtils.isEmpty(number)) { 676 Log.d(this, "onCreateOutgoingConnection, no voicemail number set."); 677 return Connection.createFailedConnection( 678 mDisconnectCauseFactory.toTelecomDisconnectCause( 679 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING, 680 "Voicemail scheme provided but no voicemail number set.", 681 phone.getPhoneId())); 682 } 683 684 // Convert voicemail: to tel: 685 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 686 } else { 687 if (!PhoneAccount.SCHEME_TEL.equals(scheme)) { 688 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme); 689 return Connection.createFailedConnection( 690 mDisconnectCauseFactory.toTelecomDisconnectCause( 691 android.telephony.DisconnectCause.INVALID_NUMBER, 692 "Handle scheme is not type tel")); 693 } 694 695 number = handle.getSchemeSpecificPart(); 696 if (TextUtils.isEmpty(number)) { 697 Log.d(this, "onCreateOutgoingConnection, unable to parse number"); 698 return Connection.createFailedConnection( 699 mDisconnectCauseFactory.toTelecomDisconnectCause( 700 android.telephony.DisconnectCause.INVALID_NUMBER, 701 "Unable to parse number")); 702 } 703 704 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 705 false /* isEmergencyCall*/, null /* not an emergency call */); 706 if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) { 707 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number 708 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and 709 // when dialed could lock LTE SIMs to 3G if not prohibited.. 710 boolean disableActivation = false; 711 CarrierConfigManager cfgManager = (CarrierConfigManager) 712 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 713 if (cfgManager != null) { 714 disableActivation = cfgManager.getConfigForSubId(phone.getSubId()) 715 .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL); 716 } 717 718 if (disableActivation) { 719 return Connection.createFailedConnection( 720 mDisconnectCauseFactory.toTelecomDisconnectCause( 721 android.telephony.DisconnectCause 722 .CDMA_ALREADY_ACTIVATED, 723 "Tried to dial *228", 724 phone.getPhoneId())); 725 } 726 } 727 } 728 729 final boolean isEmergencyNumber = mTelephonyManagerProxy.isCurrentEmergencyNumber(number); 730 // Find out if this is a test emergency number 731 final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number); 732 733 // Convert into emergency number if necessary 734 // This is required in some regions (e.g. Taiwan). 735 if (isEmergencyNumber) { 736 final Phone phone = getPhoneForAccount(request.getAccountHandle(), false, 737 handle.getSchemeSpecificPart()); 738 // We only do the conversion if the phone is not in service. The un-converted 739 // emergency numbers will go to the correct destination when the phone is in-service, 740 // so they will only need the special emergency call setup when the phone is out of 741 // service. 742 if (phone == null || phone.getServiceState().getState() 743 != ServiceState.STATE_IN_SERVICE) { 744 String convertedNumber = mPhoneNumberUtilsProxy.convertToEmergencyNumber(this, 745 number); 746 if (!TextUtils.equals(convertedNumber, number)) { 747 Log.i(this, "onCreateOutgoingConnection, converted to emergency number"); 748 number = convertedNumber; 749 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 750 } 751 } 752 } 753 final String numberToDial = number; 754 755 final boolean isAirplaneModeOn = mDeviceState.isAirplaneModeOn(this); 756 757 boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn)) 758 || isRadioPowerDownOnBluetooth(); 759 760 // Get the right phone object from the account data passed in. 761 final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber, 762 /* Note: when not an emergency, handle can be null for unknown callers */ 763 handle == null ? null : handle.getSchemeSpecificPart()); 764 765 if (needToTurnOnRadio) { 766 final Uri resultHandle = handle; 767 final int originalPhoneType = phone.getPhoneType(); 768 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 769 isEmergencyNumber, resultHandle, phone); 770 if (mRadioOnHelper == null) { 771 mRadioOnHelper = new RadioOnHelper(this); 772 } 773 774 if (isEmergencyNumber) { 775 mIsEmergencyCallPending = true; 776 } 777 mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() { 778 @Override 779 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) { 780 handleOnComplete(isRadioReady, isEmergencyNumber, resultConnection, request, 781 numberToDial, resultHandle, originalPhoneType, phone); 782 } 783 784 @Override 785 public boolean isOkToCall(Phone phone, int serviceState) { 786 // HAL 1.4 introduced a new variant of dial for emergency calls, which includes 787 // an isTesting parameter. For HAL 1.4+, do not wait for IN_SERVICE, this will 788 // be handled at the RIL/vendor level by emergencyDial(...). 789 boolean waitForInServiceToDialEmergency = isTestEmergencyNumber 790 && phone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_4); 791 if (isEmergencyNumber && !waitForInServiceToDialEmergency) { 792 // We currently only look to make sure that the radio is on before dialing. 793 // We should be able to make emergency calls at any time after the radio has 794 // been powered on and isn't in the UNAVAILABLE state, even if it is 795 // reporting the OUT_OF_SERVICE state. 796 return (phone.getState() == PhoneConstants.State.OFFHOOK) 797 || phone.getServiceStateTracker().isRadioOn(); 798 } else { 799 // Wait until we are in service and ready to make calls. This can happen 800 // when we power down the radio on bluetooth to save power on watches or if 801 // it is a test emergency number and we have to wait for the device to move 802 // IN_SERVICE before the call can take place over normal routing. 803 return (phone.getState() == PhoneConstants.State.OFFHOOK) 804 // Do not wait for voice in service on opportunistic SIMs. 805 || SubscriptionController.getInstance().isOpportunistic( 806 phone.getSubId()) 807 || serviceState == ServiceState.STATE_IN_SERVICE; 808 } 809 } 810 }, isEmergencyNumber && !isTestEmergencyNumber, phone, isTestEmergencyNumber); 811 // Return the still unconnected GsmConnection and wait for the Radios to boot before 812 // connecting it to the underlying Phone. 813 return resultConnection; 814 } else { 815 if (!canAddCall() && !isEmergencyNumber) { 816 Log.d(this, "onCreateOutgoingConnection, cannot add call ."); 817 return Connection.createFailedConnection( 818 new DisconnectCause(DisconnectCause.ERROR, 819 getApplicationContext().getText( 820 R.string.incall_error_cannot_add_call), 821 getApplicationContext().getText( 822 R.string.incall_error_cannot_add_call), 823 "Add call restricted due to ongoing video call")); 824 } 825 826 if (!isEmergencyNumber) { 827 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 828 false, handle, phone); 829 if (isAdhocConference) { 830 if (resultConnection instanceof TelephonyConnection) { 831 TelephonyConnection conn = (TelephonyConnection)resultConnection; 832 conn.setParticipants(request.getParticipants()); 833 } 834 return resultConnection; 835 } else { 836 return placeOutgoingConnection(request, resultConnection, phone); 837 } 838 } else { 839 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 840 true, handle, phone); 841 delayDialForDdsSwitch(phone, (result) -> { 842 Log.i(this, "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result); 843 placeOutgoingConnection(request, resultConnection, phone); 844 }); 845 return resultConnection; 846 } 847 } 848 } 849 placeOutgoingConnection(ConnectionRequest request, Connection resultConnection, Phone phone)850 private Connection placeOutgoingConnection(ConnectionRequest request, 851 Connection resultConnection, Phone phone) { 852 // If there was a failure, the resulting connection will not be a TelephonyConnection, 853 // so don't place the call! 854 if (resultConnection instanceof TelephonyConnection) { 855 if (request.getExtras() != null && request.getExtras().getBoolean( 856 TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) { 857 ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true); 858 } 859 placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request); 860 } 861 return resultConnection; 862 } 863 isEmergencyNumberTestNumber(String number)864 private boolean isEmergencyNumberTestNumber(String number) { 865 number = PhoneNumberUtils.stripSeparators(number); 866 Map<Integer, List<EmergencyNumber>> list = 867 mTelephonyManagerProxy.getCurrentEmergencyNumberList(); 868 // Do not worry about which subscription the test emergency call is on yet, only detect that 869 // it is an emergency. 870 for (Integer sub : list.keySet()) { 871 for (EmergencyNumber eNumber : list.get(sub)) { 872 if (number.equals(eNumber.getNumber()) 873 && eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)) { 874 Log.i(this, "isEmergencyNumberTestNumber: " + number + " has been detected as " 875 + "a test emergency number.,"); 876 return true; 877 } 878 } 879 } 880 return false; 881 } 882 883 /** 884 * @return whether radio has recently been turned on for emergency call but hasn't actually 885 * dialed the call yet. 886 */ isEmergencyCallPending()887 public boolean isEmergencyCallPending() { 888 return mIsEmergencyCallPending; 889 } 890 891 /** 892 * Whether the cellular radio is power off because the device is on Bluetooth. 893 */ isRadioPowerDownOnBluetooth()894 private boolean isRadioPowerDownOnBluetooth() { 895 final boolean allowed = mDeviceState.isRadioPowerDownAllowedOnBluetooth(this); 896 final int cellOn = mDeviceState.getCellOnStatus(this); 897 return (allowed && cellOn == PhoneConstants.CELL_ON_FLAG && !isRadioOn()); 898 } 899 900 /** 901 * Handle the onComplete callback of RadioOnStateListener. 902 */ handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, Connection originalConnection, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, Phone phone)903 private void handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, 904 Connection originalConnection, ConnectionRequest request, String numberToDial, 905 Uri handle, int originalPhoneType, Phone phone) { 906 // Make sure the Call has not already been canceled by the user. 907 if (originalConnection.getState() == Connection.STATE_DISCONNECTED) { 908 Log.i(this, "Call disconnected before the outgoing call was placed. Skipping call " 909 + "placement."); 910 if (isEmergencyNumber) { 911 // If call is already canceled by the user, notify modem to exit emergency call 912 // mode by sending radio on with forEmergencyCall=false. 913 for (Phone curPhone : mPhoneFactoryProxy.getPhones()) { 914 curPhone.setRadioPower(true, false, false, true); 915 } 916 mIsEmergencyCallPending = false; 917 } 918 return; 919 } 920 if (isRadioReady) { 921 if (!isEmergencyNumber) { 922 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial, 923 handle, originalPhoneType, false); 924 } else { 925 delayDialForDdsSwitch(phone, result -> { 926 Log.i(this, "handleOnComplete - delayDialForDdsSwitch " 927 + "result = " + result); 928 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, 929 numberToDial, handle, originalPhoneType, true); 930 mIsEmergencyCallPending = false; 931 }); 932 } 933 } else { 934 Log.w(this, "onCreateOutgoingConnection, failed to turn on radio"); 935 closeOrDestroyConnection(originalConnection, 936 mDisconnectCauseFactory.toTelecomDisconnectCause( 937 android.telephony.DisconnectCause.POWER_OFF, 938 "Failed to turn on radio.")); 939 mIsEmergencyCallPending = false; 940 } 941 } 942 adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, boolean isEmergencyNumber)943 private void adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, 944 ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, 945 boolean isEmergencyNumber) { 946 // If the PhoneType of the Phone being used is different than the Default Phone, then we 947 // need to create a new Connection using that PhoneType and replace it in Telecom. 948 if (phone.getPhoneType() != originalPhoneType) { 949 Connection repConnection = getTelephonyConnection(request, numberToDial, 950 isEmergencyNumber, handle, phone); 951 // If there was a failure, the resulting connection will not be a TelephonyConnection, 952 // so don't place the call, just return! 953 if (repConnection instanceof TelephonyConnection) { 954 placeOutgoingConnection((TelephonyConnection) repConnection, phone, request); 955 } 956 // Notify Telecom of the new Connection type. 957 // TODO: Switch out the underlying connection instead of creating a new 958 // one and causing UI Jank. 959 boolean noActiveSimCard = SubscriptionController.getInstance() 960 .getActiveSubInfoCount(phone.getContext().getOpPackageName(), 961 phone.getContext().getAttributionTag()) == 0; 962 // If there's no active sim card and the device is in emergency mode, use E account. 963 addExistingConnection(mPhoneUtilsProxy.makePstnPhoneAccountHandleWithPrefix( 964 phone, "", isEmergencyNumber && noActiveSimCard), repConnection); 965 // Remove the old connection from Telecom after. 966 closeOrDestroyConnection(connectionToEvaluate, 967 mDisconnectCauseFactory.toTelecomDisconnectCause( 968 android.telephony.DisconnectCause.OUTGOING_CANCELED, 969 "Reconnecting outgoing Emergency Call.", 970 phone.getPhoneId())); 971 } else { 972 placeOutgoingConnection((TelephonyConnection) connectionToEvaluate, phone, request); 973 } 974 } 975 976 /** 977 * @return {@code true} if any other call is disabling the ability to add calls, {@code false} 978 * otherwise. 979 */ canAddCall()980 private boolean canAddCall() { 981 Collection<Connection> connections = getAllConnections(); 982 for (Connection connection : connections) { 983 if (connection.getExtras() != null && 984 connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) { 985 return false; 986 } 987 } 988 return true; 989 } 990 getTelephonyConnection(final ConnectionRequest request, final String number, boolean isEmergencyNumber, final Uri handle, Phone phone)991 private Connection getTelephonyConnection(final ConnectionRequest request, final String number, 992 boolean isEmergencyNumber, final Uri handle, Phone phone) { 993 994 if (phone == null) { 995 final Context context = getApplicationContext(); 996 if (mDeviceState.shouldCheckSimStateBeforeOutgoingCall(this)) { 997 // Check SIM card state before the outgoing call. 998 // Start the SIM unlock activity if PIN_REQUIRED. 999 final Phone defaultPhone = mPhoneFactoryProxy.getDefaultPhone(); 1000 final IccCard icc = defaultPhone.getIccCard(); 1001 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN; 1002 if (icc != null) { 1003 simState = icc.getState(); 1004 } 1005 if (simState == IccCardConstants.State.PIN_REQUIRED) { 1006 final String simUnlockUiPackage = context.getResources().getString( 1007 R.string.config_simUnlockUiPackage); 1008 final String simUnlockUiClass = context.getResources().getString( 1009 R.string.config_simUnlockUiClass); 1010 if (simUnlockUiPackage != null && simUnlockUiClass != null) { 1011 Intent simUnlockIntent = new Intent().setComponent(new ComponentName( 1012 simUnlockUiPackage, simUnlockUiClass)); 1013 simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1014 try { 1015 context.startActivity(simUnlockIntent); 1016 } catch (ActivityNotFoundException exception) { 1017 Log.e(this, exception, "Unable to find SIM unlock UI activity."); 1018 } 1019 } 1020 return Connection.createFailedConnection( 1021 mDisconnectCauseFactory.toTelecomDisconnectCause( 1022 android.telephony.DisconnectCause.OUT_OF_SERVICE, 1023 "SIM_STATE_PIN_REQUIRED")); 1024 } 1025 } 1026 1027 Log.d(this, "onCreateOutgoingConnection, phone is null"); 1028 return Connection.createFailedConnection( 1029 mDisconnectCauseFactory.toTelecomDisconnectCause( 1030 android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null")); 1031 } 1032 1033 // Check both voice & data RAT to enable normal CS call, 1034 // when voice RAT is OOS but Data RAT is present. 1035 int state = phone.getServiceState().getState(); 1036 if (state == ServiceState.STATE_OUT_OF_SERVICE) { 1037 int dataNetType = phone.getServiceState().getDataNetworkType(); 1038 if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE || 1039 dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA || 1040 dataNetType == TelephonyManager.NETWORK_TYPE_NR) { 1041 state = phone.getServiceState().getDataRegistrationState(); 1042 } 1043 } 1044 1045 // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if 1046 // carrier configuration specifies that we cannot make non-emergency calls in ECM mode. 1047 if (!isEmergencyNumber && phone.isInEcm()) { 1048 boolean allowNonEmergencyCalls = true; 1049 CarrierConfigManager cfgManager = (CarrierConfigManager) 1050 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1051 if (cfgManager != null) { 1052 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId()) 1053 .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL); 1054 } 1055 1056 if (!allowNonEmergencyCalls) { 1057 return Connection.createFailedConnection( 1058 mDisconnectCauseFactory.toTelecomDisconnectCause( 1059 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY, 1060 "Cannot make non-emergency call in ECM mode.", 1061 phone.getPhoneId())); 1062 } 1063 } 1064 1065 if (!isEmergencyNumber) { 1066 switch (state) { 1067 case ServiceState.STATE_IN_SERVICE: 1068 case ServiceState.STATE_EMERGENCY_ONLY: 1069 break; 1070 case ServiceState.STATE_OUT_OF_SERVICE: 1071 if (phone.isUtEnabled() && number.endsWith("#")) { 1072 Log.d(this, "onCreateOutgoingConnection dial for UT"); 1073 break; 1074 } else { 1075 return Connection.createFailedConnection( 1076 mDisconnectCauseFactory.toTelecomDisconnectCause( 1077 android.telephony.DisconnectCause.OUT_OF_SERVICE, 1078 "ServiceState.STATE_OUT_OF_SERVICE", 1079 phone.getPhoneId())); 1080 } 1081 case ServiceState.STATE_POWER_OFF: 1082 // Don't disconnect if radio is power off because the device is on Bluetooth. 1083 if (isRadioPowerDownOnBluetooth()) { 1084 break; 1085 } 1086 return Connection.createFailedConnection( 1087 mDisconnectCauseFactory.toTelecomDisconnectCause( 1088 android.telephony.DisconnectCause.POWER_OFF, 1089 "ServiceState.STATE_POWER_OFF", 1090 phone.getPhoneId())); 1091 default: 1092 Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state); 1093 return Connection.createFailedConnection( 1094 mDisconnectCauseFactory.toTelecomDisconnectCause( 1095 android.telephony.DisconnectCause.OUTGOING_FAILURE, 1096 "Unknown service state " + state, 1097 phone.getPhoneId())); 1098 } 1099 } 1100 1101 final boolean isTtyModeEnabled = mDeviceState.isTtyModeEnabled(this); 1102 if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled 1103 && !isEmergencyNumber) { 1104 return Connection.createFailedConnection(mDisconnectCauseFactory.toTelecomDisconnectCause( 1105 android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED, 1106 null, phone.getPhoneId())); 1107 } 1108 1109 // Check for additional limits on CDMA phones. 1110 final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone); 1111 if (failedConnection != null) { 1112 return failedConnection; 1113 } 1114 1115 // Check roaming status to see if we should block custom call forwarding codes 1116 if (blockCallForwardingNumberWhileRoaming(phone, number)) { 1117 return Connection.createFailedConnection( 1118 mDisconnectCauseFactory.toTelecomDisconnectCause( 1119 android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING, 1120 "Call forwarding while roaming", 1121 phone.getPhoneId())); 1122 } 1123 1124 PhoneAccountHandle accountHandle = adjustAccountHandle(phone, request.getAccountHandle()); 1125 final TelephonyConnection connection = 1126 createConnectionFor(phone, null, true /* isOutgoing */, accountHandle, 1127 request.getTelecomCallId(), request.isAdhocConferenceCall()); 1128 if (connection == null) { 1129 return Connection.createFailedConnection( 1130 mDisconnectCauseFactory.toTelecomDisconnectCause( 1131 android.telephony.DisconnectCause.OUTGOING_FAILURE, 1132 "Invalid phone type", 1133 phone.getPhoneId())); 1134 } 1135 connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED); 1136 connection.setTelephonyConnectionInitializing(); 1137 connection.setTelephonyVideoState(request.getVideoState()); 1138 connection.setRttTextStream(request.getRttTextStream()); 1139 connection.setTtyEnabled(isTtyModeEnabled); 1140 connection.setIsAdhocConferenceCall(request.isAdhocConferenceCall()); 1141 connection.setParticipants(request.getParticipants()); 1142 return connection; 1143 } 1144 1145 @Override onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1146 public Connection onCreateIncomingConnection( 1147 PhoneAccountHandle connectionManagerPhoneAccount, 1148 ConnectionRequest request) { 1149 Log.i(this, "onCreateIncomingConnection, request: " + request); 1150 // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM), 1151 // make sure the PhoneAccount lookup retrieves the default Emergency Phone. 1152 PhoneAccountHandle accountHandle = request.getAccountHandle(); 1153 boolean isEmergency = false; 1154 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 1155 accountHandle.getId())) { 1156 Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " + 1157 "Treat as an Emergency Call."); 1158 isEmergency = true; 1159 } 1160 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 1161 /* Note: when not an emergency, handle can be null for unknown callers */ 1162 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 1163 if (phone == null) { 1164 return Connection.createFailedConnection( 1165 mDisconnectCauseFactory.toTelecomDisconnectCause( 1166 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 1167 "Phone is null")); 1168 } 1169 1170 Bundle extras = request.getExtras(); 1171 String disconnectMessage = null; 1172 if (extras.containsKey(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE)) { 1173 disconnectMessage = extras.getString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE); 1174 Log.i(this, "onCreateIncomingConnection Disconnect message " + disconnectMessage); 1175 } 1176 1177 Call call = phone.getRingingCall(); 1178 if (!call.getState().isRinging() 1179 || (disconnectMessage != null 1180 && disconnectMessage.equals(TelecomManager.CALL_AUTO_DISCONNECT_MESSAGE_STRING))) { 1181 Log.i(this, "onCreateIncomingConnection, no ringing call"); 1182 Connection connection = Connection.createFailedConnection( 1183 mDisconnectCauseFactory.toTelecomDisconnectCause( 1184 android.telephony.DisconnectCause.INCOMING_MISSED, 1185 "Found no ringing call", 1186 phone.getPhoneId())); 1187 1188 long time = extras.getLong(TelecomManager.EXTRA_CALL_CREATED_EPOCH_TIME_MILLIS); 1189 if (time != 0) { 1190 Log.i(this, "onCreateIncomingConnection. Set connect time info."); 1191 connection.setConnectTimeMillis(time); 1192 } 1193 1194 Uri address = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS); 1195 if (address != null) { 1196 Log.i(this, "onCreateIncomingConnection. Set caller id info."); 1197 connection.setAddress(address, TelecomManager.PRESENTATION_ALLOWED); 1198 } 1199 1200 return connection; 1201 } 1202 1203 // If there are multiple Connections tracked in a call, grab the latest, since it is most 1204 // likely to be the incoming call. 1205 com.android.internal.telephony.Connection originalConnection = call.getLatestConnection(); 1206 if (isOriginalConnectionKnown(originalConnection)) { 1207 Log.i(this, "onCreateIncomingConnection, original connection already registered"); 1208 return Connection.createCanceledConnection(); 1209 } 1210 1211 TelephonyConnection connection = 1212 createConnectionFor(phone, originalConnection, false /* isOutgoing */, 1213 request.getAccountHandle(), request.getTelecomCallId(), 1214 request.isAdhocConferenceCall()); 1215 1216 handleIncomingRtt(request, originalConnection); 1217 if (connection == null) { 1218 return Connection.createCanceledConnection(); 1219 } else { 1220 // Add extra to call if answering this incoming call would cause an in progress call on 1221 // another subscription to be disconnected. 1222 maybeIndicateAnsweringWillDisconnect(connection, request.getAccountHandle()); 1223 1224 connection.setTtyEnabled(mDeviceState.isTtyModeEnabled(getApplicationContext())); 1225 return connection; 1226 } 1227 } 1228 handleIncomingRtt(ConnectionRequest request, com.android.internal.telephony.Connection originalConnection)1229 private void handleIncomingRtt(ConnectionRequest request, 1230 com.android.internal.telephony.Connection originalConnection) { 1231 if (originalConnection == null 1232 || originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) { 1233 if (request.isRequestingRtt()) { 1234 Log.w(this, "Requesting RTT on non-IMS call, ignoring"); 1235 } 1236 return; 1237 } 1238 1239 ImsPhoneConnection imsOriginalConnection = (ImsPhoneConnection) originalConnection; 1240 if (!request.isRequestingRtt()) { 1241 if (imsOriginalConnection.isRttEnabledForCall()) { 1242 Log.w(this, "Incoming call requested RTT but we did not get a RttTextStream"); 1243 } 1244 return; 1245 } 1246 1247 Log.i(this, "Setting RTT stream on ImsPhoneConnection in case we need it later"); 1248 imsOriginalConnection.setCurrentRttTextStream(request.getRttTextStream()); 1249 1250 if (!imsOriginalConnection.isRttEnabledForCall()) { 1251 if (request.isRequestingRtt()) { 1252 Log.w(this, "Incoming call processed as RTT but did not come in as one. Ignoring"); 1253 } 1254 return; 1255 } 1256 1257 Log.i(this, "Setting the call to be answered with RTT on."); 1258 imsOriginalConnection.getImsCall().setAnswerWithRtt(); 1259 } 1260 1261 /** 1262 * Called by the {@link ConnectionService} when a newly created {@link Connection} has been 1263 * added to the {@link ConnectionService} and sent to Telecom. Here it is safe to send 1264 * connection events. 1265 * 1266 * @param connection the {@link Connection}. 1267 */ 1268 @Override onCreateConnectionComplete(Connection connection)1269 public void onCreateConnectionComplete(Connection connection) { 1270 if (connection instanceof TelephonyConnection) { 1271 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 1272 maybeSendInternationalCallEvent(telephonyConnection); 1273 } 1274 } 1275 1276 @Override onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1277 public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, 1278 ConnectionRequest request) { 1279 Log.i(this, "onCreateIncomingConnectionFailed, request: " + request); 1280 // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM), 1281 // make sure the PhoneAccount lookup retrieves the default Emergency Phone. 1282 PhoneAccountHandle accountHandle = request.getAccountHandle(); 1283 boolean isEmergency = false; 1284 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 1285 accountHandle.getId())) { 1286 Log.w(this, "onCreateIncomingConnectionFailed:Emergency call failed... "); 1287 isEmergency = true; 1288 } 1289 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 1290 /* Note: when not an emergency, handle can be null for unknown callers */ 1291 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 1292 if (phone == null) { 1293 Log.w(this, "onCreateIncomingConnectionFailed: can not find corresponding phone."); 1294 return; 1295 } 1296 1297 Call call = phone.getRingingCall(); 1298 if (!call.getState().isRinging()) { 1299 Log.w(this, "onCreateIncomingConnectionFailed, no ringing call found for failed call"); 1300 return; 1301 } 1302 1303 com.android.internal.telephony.Connection originalConnection = 1304 call.getState() == Call.State.WAITING 1305 ? call.getLatestConnection() : call.getEarliestConnection(); 1306 TelephonyConnection knownConnection = 1307 getConnectionForOriginalConnection(originalConnection); 1308 if (knownConnection != null) { 1309 Log.w(this, "onCreateIncomingConnectionFailed, original connection already registered." 1310 + " Hanging it up."); 1311 knownConnection.onAbort(); 1312 return; 1313 } 1314 1315 TelephonyConnection connection = 1316 createConnectionFor(phone, originalConnection, false /* isOutgoing */, 1317 request.getAccountHandle(), request.getTelecomCallId()); 1318 if (connection == null) { 1319 Log.w(this, "onCreateIncomingConnectionFailed, TelephonyConnection created as null, " 1320 + "ignoring."); 1321 return; 1322 } 1323 1324 // We have to do all of this work because in some cases, hanging up the call maps to 1325 // different underlying signaling (CDMA), which is already encapsulated in 1326 // TelephonyConnection. 1327 connection.onReject(); 1328 connection.close(); 1329 } 1330 1331 /** 1332 * Called by the {@link ConnectionService} when a newly created {@link Conference} has been 1333 * added to the {@link ConnectionService} and sent to Telecom. Here it is safe to send 1334 * connection events. 1335 * 1336 * @param conference the {@link Conference}. 1337 */ 1338 @Override onCreateConferenceComplete(Conference conference)1339 public void onCreateConferenceComplete(Conference conference) { 1340 if (conference instanceof ImsConference) { 1341 ImsConference imsConference = (ImsConference)conference; 1342 TelephonyConnection telephonyConnection = 1343 (TelephonyConnection)(imsConference.getConferenceHost()); 1344 maybeSendInternationalCallEvent(telephonyConnection); 1345 } 1346 } 1347 onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1348 public void onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount, 1349 ConnectionRequest request) { 1350 Log.i(this, "onCreateIncomingConferenceFailed, request: " + request); 1351 onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request); 1352 } 1353 1354 @Override triggerConferenceRecalculate()1355 public void triggerConferenceRecalculate() { 1356 if (mTelephonyConferenceController.shouldRecalculate()) { 1357 mTelephonyConferenceController.recalculate(); 1358 } 1359 } 1360 1361 @Override onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1362 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 1363 ConnectionRequest request) { 1364 Log.i(this, "onCreateUnknownConnection, request: " + request); 1365 // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's 1366 // Emergency PhoneAccount 1367 PhoneAccountHandle accountHandle = request.getAccountHandle(); 1368 boolean isEmergency = false; 1369 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 1370 accountHandle.getId())) { 1371 Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " + 1372 "Treat as an Emergency Call."); 1373 isEmergency = true; 1374 } 1375 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 1376 /* Note: when not an emergency, handle can be null for unknown callers */ 1377 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 1378 if (phone == null) { 1379 return Connection.createFailedConnection( 1380 mDisconnectCauseFactory.toTelecomDisconnectCause( 1381 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 1382 "Phone is null")); 1383 } 1384 Bundle extras = request.getExtras(); 1385 1386 final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>(); 1387 1388 // Handle the case where an unknown connection has an IMS external call ID specified; we can 1389 // skip the rest of the guesswork and just grad that unknown call now. 1390 if (phone.getImsPhone() != null && extras != null && 1391 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) { 1392 1393 ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); 1394 ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker(); 1395 int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, 1396 -1); 1397 1398 if (externalCallTracker != null) { 1399 com.android.internal.telephony.Connection connection = 1400 externalCallTracker.getConnectionById(externalCallId); 1401 1402 if (connection != null) { 1403 allConnections.add(connection); 1404 } 1405 } 1406 } 1407 1408 if (allConnections.isEmpty()) { 1409 final Call ringingCall = phone.getRingingCall(); 1410 if (ringingCall.hasConnections()) { 1411 allConnections.addAll(ringingCall.getConnections()); 1412 } 1413 final Call foregroundCall = phone.getForegroundCall(); 1414 if ((foregroundCall.getState() != Call.State.DISCONNECTED) 1415 && (foregroundCall.hasConnections())) { 1416 allConnections.addAll(foregroundCall.getConnections()); 1417 } 1418 if (phone.getImsPhone() != null) { 1419 final Call imsFgCall = phone.getImsPhone().getForegroundCall(); 1420 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall 1421 .hasConnections()) { 1422 allConnections.addAll(imsFgCall.getConnections()); 1423 } 1424 } 1425 final Call backgroundCall = phone.getBackgroundCall(); 1426 if (backgroundCall.hasConnections()) { 1427 allConnections.addAll(phone.getBackgroundCall().getConnections()); 1428 } 1429 } 1430 1431 com.android.internal.telephony.Connection unknownConnection = null; 1432 for (com.android.internal.telephony.Connection telephonyConnection : allConnections) { 1433 if (!isOriginalConnectionKnown(telephonyConnection)) { 1434 unknownConnection = telephonyConnection; 1435 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection); 1436 break; 1437 } 1438 } 1439 1440 if (unknownConnection == null) { 1441 Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection."); 1442 return Connection.createCanceledConnection(); 1443 } 1444 1445 // We should rely on the originalConnection to get the video state. The request coming 1446 // from Telecom does not know the video state of the unknown call. 1447 int videoState = unknownConnection != null ? unknownConnection.getVideoState() : 1448 VideoProfile.STATE_AUDIO_ONLY; 1449 1450 TelephonyConnection connection = 1451 createConnectionFor(phone, unknownConnection, 1452 !unknownConnection.isIncoming() /* isOutgoing */, 1453 request.getAccountHandle(), request.getTelecomCallId() 1454 ); 1455 1456 if (connection == null) { 1457 return Connection.createCanceledConnection(); 1458 } else { 1459 connection.updateState(); 1460 return connection; 1461 } 1462 } 1463 1464 /** 1465 * Conferences two connections. 1466 * 1467 * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has 1468 * a limitation in that it can only specify conferenceables which are instances of 1469 * {@link android.telecom.RemoteConnection}. In the case of an {@link ImsConference}, the 1470 * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge 1471 * a {@link Conference} and a {@link Connection}. As a result when, merging a 1472 * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference} 1473 * require merging a {@link ConferenceParticipantConnection} which is a child of the 1474 * {@link Conference} with a {@link TelephonyConnection}. The 1475 * {@link ConferenceParticipantConnection} class does not have the capability to initiate a 1476 * conference merge, so we need to call 1477 * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or 1478 * {@code connection2}, one of which is an instance of {@link TelephonyConnection}. 1479 * 1480 * @param connection1 A connection to merge into a conference call. 1481 * @param connection2 A connection to merge into a conference call. 1482 */ 1483 @Override onConference(Connection connection1, Connection connection2)1484 public void onConference(Connection connection1, Connection connection2) { 1485 if (connection1 instanceof TelephonyConnection) { 1486 ((TelephonyConnection) connection1).performConference(connection2); 1487 } else if (connection2 instanceof TelephonyConnection) { 1488 ((TelephonyConnection) connection2).performConference(connection1); 1489 } else { 1490 Log.w(this, "onConference - cannot merge connections " + 1491 "Connection1: %s, Connection2: %2", connection1, connection2); 1492 } 1493 } 1494 1495 @Override onConnectionAdded(Connection connection)1496 public void onConnectionAdded(Connection connection) { 1497 if (connection instanceof Holdable && !isExternalConnection(connection)) { 1498 mHoldTracker.addHoldable( 1499 connection.getPhoneAccountHandle(), (Holdable) connection); 1500 } 1501 } 1502 1503 @Override onConnectionRemoved(Connection connection)1504 public void onConnectionRemoved(Connection connection) { 1505 if (connection instanceof Holdable && !isExternalConnection(connection)) { 1506 mHoldTracker.removeHoldable(connection.getPhoneAccountHandle(), (Holdable) connection); 1507 } 1508 } 1509 1510 @Override onConferenceAdded(Conference conference)1511 public void onConferenceAdded(Conference conference) { 1512 if (conference instanceof Holdable) { 1513 mHoldTracker.addHoldable(conference.getPhoneAccountHandle(), (Holdable) conference); 1514 } 1515 } 1516 1517 @Override onConferenceRemoved(Conference conference)1518 public void onConferenceRemoved(Conference conference) { 1519 if (conference instanceof Holdable) { 1520 mHoldTracker.removeHoldable(conference.getPhoneAccountHandle(), (Holdable) conference); 1521 } 1522 } 1523 isExternalConnection(Connection connection)1524 private boolean isExternalConnection(Connection connection) { 1525 return (connection.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) 1526 == Connection.PROPERTY_IS_EXTERNAL_CALL; 1527 } 1528 blockCallForwardingNumberWhileRoaming(Phone phone, String number)1529 private boolean blockCallForwardingNumberWhileRoaming(Phone phone, String number) { 1530 if (phone == null || TextUtils.isEmpty(number) || !phone.getServiceState().getRoaming()) { 1531 return false; 1532 } 1533 boolean allowPrefixIms = true; 1534 String[] blockPrefixes = null; 1535 CarrierConfigManager cfgManager = (CarrierConfigManager) 1536 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1537 if (cfgManager != null) { 1538 allowPrefixIms = cfgManager.getConfigForSubId(phone.getSubId()).getBoolean( 1539 CarrierConfigManager.KEY_SUPPORT_IMS_CALL_FORWARDING_WHILE_ROAMING_BOOL, 1540 true); 1541 if (allowPrefixIms && useImsForAudioOnlyCall(phone)) { 1542 return false; 1543 } 1544 blockPrefixes = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray( 1545 CarrierConfigManager.KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY); 1546 } 1547 1548 if (blockPrefixes != null) { 1549 for (String prefix : blockPrefixes) { 1550 if (number.startsWith(prefix)) { 1551 return true; 1552 } 1553 } 1554 } 1555 return false; 1556 } 1557 useImsForAudioOnlyCall(Phone phone)1558 private boolean useImsForAudioOnlyCall(Phone phone) { 1559 Phone imsPhone = phone.getImsPhone(); 1560 1561 return imsPhone != null 1562 && (imsPhone.isVoiceOverCellularImsEnabled() || imsPhone.isWifiCallingEnabled()) 1563 && (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE); 1564 } 1565 isRadioOn()1566 private boolean isRadioOn() { 1567 boolean result = false; 1568 for (Phone phone : mPhoneFactoryProxy.getPhones()) { 1569 result |= phone.isRadioOn(); 1570 } 1571 return result; 1572 } 1573 makeCachedConnectionPhonePair( TelephonyConnection c)1574 private Pair<WeakReference<TelephonyConnection>, Queue<Phone>> makeCachedConnectionPhonePair( 1575 TelephonyConnection c) { 1576 Queue<Phone> phones = new LinkedList<>(Arrays.asList(mPhoneFactoryProxy.getPhones())); 1577 return new Pair<>(new WeakReference<>(c), phones); 1578 } 1579 1580 // Update the mEmergencyRetryCache by removing the Phone used to call the last failed emergency 1581 // number and then moving it to the back of the queue if it is not a permanent failure cause 1582 // from the modem. updateCachedConnectionPhonePair(TelephonyConnection c, boolean isPermanentFailure)1583 private void updateCachedConnectionPhonePair(TelephonyConnection c, 1584 boolean isPermanentFailure) { 1585 // No cache exists, create a new one. 1586 if (mEmergencyRetryCache == null) { 1587 Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache"); 1588 mEmergencyRetryCache = makeCachedConnectionPhonePair(c); 1589 // Cache is stale, create a new one with the new TelephonyConnection. 1590 } else if (mEmergencyRetryCache.first.get() != c) { 1591 Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating."); 1592 mEmergencyRetryCache = makeCachedConnectionPhonePair(c); 1593 } 1594 1595 Queue<Phone> cachedPhones = mEmergencyRetryCache.second; 1596 // Need to refer default phone considering ImsPhone because 1597 // cachedPhones is a list that contains default phones. 1598 Phone phoneUsed = c.getPhone().getDefaultPhone(); 1599 if (phoneUsed == null) { 1600 return; 1601 } 1602 // Remove phone used from the list, but for temporary fail cause, it will be added 1603 // back to list further in this method. However in case of permanent failure, the 1604 // phone shouldn't be reused, hence it will not be added back again. 1605 cachedPhones.remove(phoneUsed); 1606 Log.i(this, "updateCachedConnectionPhonePair, isPermanentFailure:" + isPermanentFailure); 1607 if (!isPermanentFailure) { 1608 // In case of temporary failure, add the phone back, this will result adding it 1609 // to tail of list mEmergencyRetryCache.second, giving other phone more 1610 // priority and that is what we want. 1611 cachedPhones.offer(phoneUsed); 1612 } 1613 } 1614 1615 /** 1616 * Updates a cache containing all of the slots that are available for redial at any point. 1617 * 1618 * - If a Connection returns with the disconnect cause EMERGENCY_TEMP_FAILURE, keep that phone 1619 * in the cache, but move it to the lowest priority in the list. Then, place the emergency call 1620 * on the next phone in the list. 1621 * - If a Connection returns with the disconnect cause EMERGENCY_PERM_FAILURE, remove that phone 1622 * from the cache and pull another phone from the cache to place the emergency call. 1623 * 1624 * This will continue until there are no more slots to dial on. 1625 */ 1626 @VisibleForTesting retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure)1627 public void retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure) { 1628 int phoneId = (c.getPhone() == null) ? -1 : c.getPhone().getPhoneId(); 1629 updateCachedConnectionPhonePair(c, isPermanentFailure); 1630 // Pull next phone to use from the cache or null if it is empty 1631 Phone newPhoneToUse = (mEmergencyRetryCache.second != null) 1632 ? mEmergencyRetryCache.second.peek() : null; 1633 if (newPhoneToUse != null) { 1634 int videoState = c.getVideoState(); 1635 Bundle connExtras = c.getExtras(); 1636 Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse); 1637 c.clearOriginalConnection(); 1638 if (phoneId != newPhoneToUse.getPhoneId()) updatePhoneAccount(c, newPhoneToUse); 1639 placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras); 1640 } else { 1641 // We have run out of Phones to use. Disconnect the call and destroy the connection. 1642 Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting."); 1643 closeOrDestroyConnection(c, new DisconnectCause(DisconnectCause.ERROR)); 1644 } 1645 } 1646 updatePhoneAccount(TelephonyConnection connection, Phone phone)1647 private void updatePhoneAccount(TelephonyConnection connection, Phone phone) { 1648 PhoneAccountHandle pHandle = mPhoneUtilsProxy.makePstnPhoneAccountHandle(phone); 1649 // For ECall handling on MSIM, until the request reaches here (i.e PhoneApp), we don't know 1650 // on which phone account ECall can be placed. After deciding, we should notify Telecom of 1651 // the change so that the proper PhoneAccount can be displayed. 1652 Log.i(this, "updatePhoneAccount setPhoneAccountHandle, account = " + pHandle); 1653 connection.setPhoneAccountHandle(pHandle); 1654 } 1655 placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)1656 private void placeOutgoingConnection( 1657 TelephonyConnection connection, Phone phone, ConnectionRequest request) { 1658 placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras()); 1659 } 1660 placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras)1661 private void placeOutgoingConnection( 1662 TelephonyConnection connection, Phone phone, int videoState, Bundle extras) { 1663 1664 String number = (connection.getAddress() != null) 1665 ? connection.getAddress().getSchemeSpecificPart() 1666 : ""; 1667 1668 if (showDataDialog(phone, number)) { 1669 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1670 android.telephony.DisconnectCause.DIALED_MMI, "UT is not available")); 1671 return; 1672 } 1673 1674 if (extras != null && extras.containsKey(TelecomManager.EXTRA_OUTGOING_PICTURE)) { 1675 ParcelUuid uuid = extras.getParcelable(TelecomManager.EXTRA_OUTGOING_PICTURE); 1676 CallComposerPictureManager.getInstance(phone.getContext(), phone.getSubId()) 1677 .storeUploadedPictureToCallLog(uuid.getUuid(), (uri) -> { 1678 if (uri != null) { 1679 try { 1680 Bundle b = new Bundle(); 1681 b.putParcelable(TelecomManager.EXTRA_PICTURE_URI, uri); 1682 connection.putTelephonyExtras(b); 1683 } catch (Exception e) { 1684 Log.e(this, e, "Couldn't set picture extra on outgoing call"); 1685 } 1686 } 1687 }); 1688 } 1689 1690 final com.android.internal.telephony.Connection originalConnection; 1691 try { 1692 if (phone != null) { 1693 EmergencyNumber emergencyNumber = 1694 phone.getEmergencyNumberTracker().getEmergencyNumber(number); 1695 if (emergencyNumber != null) { 1696 if (!getAllConnections().isEmpty()) { 1697 if (!shouldHoldForEmergencyCall(phone)) { 1698 // If we do not support holding ongoing calls for an outgoing 1699 // emergency call, disconnect the ongoing calls. 1700 for (Connection c : getAllConnections()) { 1701 if (!c.equals(connection) 1702 && c.getState() != Connection.STATE_DISCONNECTED 1703 && c instanceof TelephonyConnection) { 1704 ((TelephonyConnection) c).hangup( 1705 android.telephony.DisconnectCause 1706 .OUTGOING_EMERGENCY_CALL_PLACED); 1707 } 1708 } 1709 for (Conference c : getAllConferences()) { 1710 if (c.getState() != Connection.STATE_DISCONNECTED 1711 && c instanceof Conference) { 1712 ((Conference) c).onDisconnect(); 1713 } 1714 } 1715 } else if (!isVideoCallHoldAllowed(phone)) { 1716 // If we do not support holding ongoing video call for an outgoing 1717 // emergency call, disconnect the ongoing video call. 1718 for (Connection c : getAllConnections()) { 1719 if (!c.equals(connection) 1720 && c.getState() == Connection.STATE_ACTIVE 1721 && VideoProfile.isVideo(c.getVideoState()) 1722 && c instanceof TelephonyConnection) { 1723 ((TelephonyConnection) c).hangup( 1724 android.telephony.DisconnectCause 1725 .OUTGOING_EMERGENCY_CALL_PLACED); 1726 break; 1727 } 1728 } 1729 } 1730 } 1731 } 1732 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder() 1733 .setVideoState(videoState) 1734 .setIntentExtras(extras) 1735 .setRttTextStream(connection.getRttTextStream()) 1736 .build(), 1737 // We need to wait until the phone has been chosen in GsmCdmaPhone to 1738 // register for the associated TelephonyConnection call event listeners. 1739 connection::registerForCallEvents); 1740 } else { 1741 originalConnection = null; 1742 } 1743 } catch (CallStateException e) { 1744 Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e); 1745 connection.unregisterForCallEvents(); 1746 handleCallStateException(e, connection, phone); 1747 return; 1748 } 1749 1750 if (originalConnection == null) { 1751 int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 1752 // On GSM phones, null connection means that we dialed an MMI code 1753 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM || 1754 phone.isUtEnabled()) { 1755 Log.d(this, "dialed MMI code"); 1756 int subId = phone.getSubId(); 1757 Log.d(this, "subId: "+subId); 1758 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; 1759 final Intent intent = new Intent(this, MMIDialogActivity.class); 1760 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 1761 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 1762 if (SubscriptionManager.isValidSubscriptionId(subId)) { 1763 SubscriptionManager.putSubscriptionIdExtra(intent, subId); 1764 } 1765 startActivity(intent); 1766 } 1767 Log.d(this, "placeOutgoingConnection, phone.dial returned null"); 1768 connection.setTelephonyConnectionDisconnected( 1769 mDisconnectCauseFactory.toTelecomDisconnectCause(telephonyDisconnectCause, 1770 "Connection is null", phone.getPhoneId())); 1771 connection.close(); 1772 } else { 1773 if (!getMainThreadHandler().getLooper().isCurrentThread()) { 1774 Log.w(this, "placeOriginalConnection - Unexpected, this call " 1775 + "should always be on the main thread."); 1776 getMainThreadHandler().post(() -> { 1777 if (connection.getOriginalConnection() == null) { 1778 connection.setOriginalConnection(originalConnection); 1779 } 1780 }); 1781 } else { 1782 connection.setOriginalConnection(originalConnection); 1783 } 1784 } 1785 } 1786 isVideoCallHoldAllowed(Phone phone)1787 private boolean isVideoCallHoldAllowed(Phone phone) { 1788 CarrierConfigManager cfgManager = (CarrierConfigManager) 1789 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1790 if (cfgManager == null) { 1791 // For some reason CarrierConfigManager is unavailable, return default 1792 Log.w(this, "isVideoCallHoldAllowed: couldn't get CarrierConfigManager"); 1793 return true; 1794 } 1795 return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean( 1796 CarrierConfigManager.KEY_ALLOW_HOLD_VIDEO_CALL_BOOL, true); 1797 } 1798 shouldHoldForEmergencyCall(Phone phone)1799 private boolean shouldHoldForEmergencyCall(Phone phone) { 1800 CarrierConfigManager cfgManager = (CarrierConfigManager) 1801 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1802 if (cfgManager == null) { 1803 // For some reason CarrierConfigManager is unavailable, return default 1804 Log.w(this, "shouldHoldForEmergencyCall: couldn't get CarrierConfigManager"); 1805 return true; 1806 } 1807 return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean( 1808 CarrierConfigManager.KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL, true); 1809 } 1810 handleCallStateException(CallStateException e, TelephonyConnection connection, Phone phone)1811 private void handleCallStateException(CallStateException e, TelephonyConnection connection, 1812 Phone phone) { 1813 int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 1814 switch (e.getError()) { 1815 case CallStateException.ERROR_OUT_OF_SERVICE: 1816 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE; 1817 break; 1818 case CallStateException.ERROR_POWER_OFF: 1819 cause = android.telephony.DisconnectCause.POWER_OFF; 1820 break; 1821 case CallStateException.ERROR_ALREADY_DIALING: 1822 cause = android.telephony.DisconnectCause.ALREADY_DIALING; 1823 break; 1824 case CallStateException.ERROR_CALL_RINGING: 1825 cause = android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING; 1826 break; 1827 case CallStateException.ERROR_CALLING_DISABLED: 1828 cause = android.telephony.DisconnectCause.CALLING_DISABLED; 1829 break; 1830 case CallStateException.ERROR_TOO_MANY_CALLS: 1831 cause = android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS; 1832 break; 1833 case CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS: 1834 cause = android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS; 1835 break; 1836 } 1837 connection.setTelephonyConnectionDisconnected( 1838 DisconnectCauseUtil.toTelecomDisconnectCause(cause, e.getMessage(), 1839 phone.getPhoneId())); 1840 connection.close(); 1841 } 1842 createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId)1843 private TelephonyConnection createConnectionFor( 1844 Phone phone, 1845 com.android.internal.telephony.Connection originalConnection, 1846 boolean isOutgoing, 1847 PhoneAccountHandle phoneAccountHandle, 1848 String telecomCallId) { 1849 return createConnectionFor(phone, originalConnection, isOutgoing, phoneAccountHandle, 1850 telecomCallId, false); 1851 } 1852 createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, boolean isAdhocConference)1853 private TelephonyConnection createConnectionFor( 1854 Phone phone, 1855 com.android.internal.telephony.Connection originalConnection, 1856 boolean isOutgoing, 1857 PhoneAccountHandle phoneAccountHandle, 1858 String telecomCallId, 1859 boolean isAdhocConference) { 1860 TelephonyConnection returnConnection = null; 1861 int phoneType = phone.getPhoneType(); 1862 int callDirection = isOutgoing ? android.telecom.Call.Details.DIRECTION_OUTGOING 1863 : android.telecom.Call.Details.DIRECTION_INCOMING; 1864 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 1865 returnConnection = new GsmConnection(originalConnection, telecomCallId, callDirection); 1866 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 1867 boolean allowsMute = allowsMute(phone); 1868 returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer, 1869 allowsMute, callDirection, telecomCallId); 1870 } 1871 if (returnConnection != null) { 1872 if (!isAdhocConference) { 1873 // Listen to Telephony specific callbacks from the connection 1874 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener); 1875 } 1876 returnConnection.setVideoPauseSupported( 1877 TelecomAccountRegistry.getInstance(this).isVideoPauseSupported( 1878 phoneAccountHandle)); 1879 returnConnection.setManageImsConferenceCallSupported( 1880 TelecomAccountRegistry.getInstance(this).isManageImsConferenceCallSupported( 1881 phoneAccountHandle)); 1882 returnConnection.setShowPreciseFailedCause( 1883 TelecomAccountRegistry.getInstance(this).isShowPreciseFailedCause( 1884 phoneAccountHandle)); 1885 returnConnection.setTelephonyConnectionService(this); 1886 } 1887 return returnConnection; 1888 } 1889 isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)1890 private boolean isOriginalConnectionKnown( 1891 com.android.internal.telephony.Connection originalConnection) { 1892 return (getConnectionForOriginalConnection(originalConnection) != null); 1893 } 1894 getConnectionForOriginalConnection( com.android.internal.telephony.Connection originalConnection)1895 private TelephonyConnection getConnectionForOriginalConnection( 1896 com.android.internal.telephony.Connection originalConnection) { 1897 for (Connection connection : getAllConnections()) { 1898 if (connection instanceof TelephonyConnection) { 1899 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 1900 if (telephonyConnection.getOriginalConnection() == originalConnection) { 1901 return telephonyConnection; 1902 } 1903 } 1904 } 1905 return null; 1906 } 1907 1908 /** 1909 * Determines which {@link Phone} will be used to place the call. 1910 * @param accountHandle The {@link PhoneAccountHandle} which was sent from Telecom to place the 1911 * call on. 1912 * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise. 1913 * @param emergencyNumberAddress When {@code isEmergency} is {@code true}, will be the phone 1914 * of the emergency call. Otherwise, this can be {@code null} . 1915 * @return 1916 */ getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, @Nullable String emergencyNumberAddress)1917 private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, 1918 @Nullable String emergencyNumberAddress) { 1919 Phone chosenPhone = null; 1920 int subId = mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(accountHandle); 1921 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 1922 int phoneId = mSubscriptionManagerProxy.getPhoneId(subId); 1923 chosenPhone = mPhoneFactoryProxy.getPhone(phoneId); 1924 } 1925 // If this is an emergency call and the phone we originally planned to make this call 1926 // with is not in service or was invalid, try to find one that is in service, using the 1927 // default as a last chance backup. 1928 if (isEmergency && (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone))) { 1929 Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service " 1930 + "or invalid for emergency call.", accountHandle); 1931 chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress); 1932 Log.d(this, "getPhoneForAccount: using subId: " + 1933 (chosenPhone == null ? "null" : chosenPhone.getSubId())); 1934 } 1935 return chosenPhone; 1936 } 1937 1938 /** 1939 * If needed, block until the the default data is is switched for outgoing emergency call, or 1940 * timeout expires. 1941 * @param phone The Phone to switch the DDS on. 1942 * @param completeConsumer The consumer to call once the default data subscription has been 1943 * switched, provides {@code true} result if the switch happened 1944 * successfully or {@code false} if the operation timed out/failed. 1945 */ delayDialForDdsSwitch(Phone phone, Consumer<Boolean> completeConsumer)1946 private void delayDialForDdsSwitch(Phone phone, Consumer<Boolean> completeConsumer) { 1947 if (phone == null) { 1948 // Do not block indefinitely. 1949 completeConsumer.accept(false); 1950 } 1951 try { 1952 // Waiting for PhoneSwitcher to complete the operation. 1953 CompletableFuture<Boolean> future = possiblyOverrideDefaultDataForEmergencyCall(phone); 1954 // In the case that there is an issue or bug in PhoneSwitcher logic, do not wait 1955 // indefinitely for the future to complete. Instead, set a timeout that will complete 1956 // the future as to not block the outgoing call indefinitely. 1957 CompletableFuture<Boolean> timeout = new CompletableFuture<>(); 1958 phone.getContext().getMainThreadHandler().postDelayed( 1959 () -> timeout.complete(false), DEFAULT_DATA_SWITCH_TIMEOUT_MS); 1960 // Also ensure that the Consumer is completed on the main thread. 1961 future.acceptEitherAsync(timeout, completeConsumer, 1962 phone.getContext().getMainExecutor()); 1963 } catch (Exception e) { 1964 Log.w(this, "delayDialForDdsSwitch - exception= " 1965 + e.getMessage()); 1966 1967 } 1968 } 1969 1970 /** 1971 * If needed, block until Default Data subscription is switched for outgoing emergency call. 1972 * 1973 * In some cases, we need to try to switch the Default Data subscription before placing the 1974 * emergency call on DSDS devices. This includes the following situation: 1975 * - The modem does not support processing GNSS SUPL requests on the non-default data 1976 * subscription. For some carriers that do not provide a control plane fallback mechanism, the 1977 * SUPL request will be dropped and we will not be able to get the user's location for the 1978 * emergency call. In this case, we need to swap default data temporarily. 1979 * @param phone Evaluates whether or not the default data should be moved to the phone 1980 * specified. Should not be null. 1981 */ possiblyOverrideDefaultDataForEmergencyCall( @onNull Phone phone)1982 private CompletableFuture<Boolean> possiblyOverrideDefaultDataForEmergencyCall( 1983 @NonNull Phone phone) { 1984 int phoneCount = mTelephonyManagerProxy.getPhoneCount(); 1985 // Do not override DDS if this is a single SIM device. 1986 if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) { 1987 return CompletableFuture.completedFuture(Boolean.TRUE); 1988 } 1989 1990 // Do not switch Default data if this device supports emergency SUPL on non-DDS. 1991 final boolean gnssSuplRequiresDefaultData = 1992 mDeviceState.isSuplDdsSwitchRequiredForEmergencyCall(this); 1993 if (!gnssSuplRequiresDefaultData) { 1994 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not " 1995 + "require DDS switch."); 1996 return CompletableFuture.completedFuture(Boolean.TRUE); 1997 } 1998 1999 CarrierConfigManager cfgManager = (CarrierConfigManager) 2000 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 2001 if (cfgManager == null) { 2002 // For some reason CarrierConfigManager is unavailable. Do not block emergency call. 2003 Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: couldn't get" 2004 + "CarrierConfigManager"); 2005 return CompletableFuture.completedFuture(Boolean.TRUE); 2006 } 2007 2008 // Only override default data if we are IN_SERVICE already. 2009 if (!isAvailableForEmergencyCalls(phone)) { 2010 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS"); 2011 return CompletableFuture.completedFuture(Boolean.TRUE); 2012 } 2013 2014 // Only override default data if we are not roaming, we do not want to switch onto a network 2015 // that only supports data plane only (if we do not know). 2016 boolean isRoaming = phone.getServiceState().getVoiceRoaming(); 2017 // In some roaming conditions, we know the roaming network doesn't support control plane 2018 // fallback even though the home operator does. For these operators we will need to do a DDS 2019 // switch anyway to make sure the SUPL request doesn't fail. 2020 boolean roamingNetworkSupportsControlPlaneFallback = true; 2021 String[] dataPlaneRoamPlmns = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray( 2022 CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY); 2023 if (dataPlaneRoamPlmns != null && Arrays.asList(dataPlaneRoamPlmns).contains( 2024 phone.getServiceState().getOperatorNumeric())) { 2025 roamingNetworkSupportsControlPlaneFallback = false; 2026 } 2027 if (isRoaming && roamingNetworkSupportsControlPlaneFallback) { 2028 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: roaming network is assumed " 2029 + "to support CP fallback, not switching DDS."); 2030 return CompletableFuture.completedFuture(Boolean.TRUE); 2031 } 2032 // Do not try to swap default data if we support CS fallback or it is assumed that the 2033 // roaming network supports control plane fallback, we do not want to introduce 2034 // a lag in emergency call setup time if possible. 2035 final boolean supportsCpFallback = cfgManager.getConfigForSubId(phone.getSubId()) 2036 .getInt(CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, 2037 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY) 2038 != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY; 2039 if (supportsCpFallback && roamingNetworkSupportsControlPlaneFallback) { 2040 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier " 2041 + "supports CP fallback."); 2042 return CompletableFuture.completedFuture(Boolean.TRUE); 2043 } 2044 2045 // Get extension time, may be 0 for some carriers that support ECBM as well. Use 2046 // CarrierConfig default if format fails. 2047 int extensionTime = 0; 2048 try { 2049 extensionTime = Integer.parseInt(cfgManager.getConfigForSubId(phone.getSubId()) 2050 .getString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0")); 2051 } catch (NumberFormatException e) { 2052 // Just use default. 2053 } 2054 CompletableFuture<Boolean> modemResultFuture = new CompletableFuture<>(); 2055 try { 2056 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for " 2057 + extensionTime + "seconds"); 2058 mPhoneSwitcherProxy.getPhoneSwitcher().overrideDefaultDataForEmergency( 2059 phone.getPhoneId(), extensionTime, modemResultFuture); 2060 // Catch all exceptions, we want to continue with emergency call if possible. 2061 } catch (Exception e) { 2062 Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: exception = " 2063 + e.getMessage()); 2064 modemResultFuture = CompletableFuture.completedFuture(Boolean.FALSE); 2065 } 2066 return modemResultFuture; 2067 } 2068 2069 /** 2070 * Get the Phone to use for an emergency call of the given emergency number address: 2071 * a) If there are multiple Phones with the Subscriptions that support the emergency number 2072 * address, and one of them is the default voice Phone, consider the default voice phone 2073 * if 1.4 HAL is supported, or if it is available for emergency call. 2074 * b) If there are multiple Phones with the Subscriptions that support the emergency number 2075 * address, and none of them is the default voice Phone, use one of these Phones if 1.4 HAL 2076 * is supported, or if it is available for emergency call. 2077 * c) If there is no Phone that supports the emergency call for the address, use the defined 2078 * Priority list to select the Phone via {@link #getFirstPhoneForEmergencyCall}. 2079 */ getPhoneForEmergencyCall(String emergencyNumberAddress)2080 public Phone getPhoneForEmergencyCall(String emergencyNumberAddress) { 2081 // Find the list of available Phones for the given emergency number address 2082 List<Phone> potentialEmergencyPhones = new ArrayList<>(); 2083 int defaultVoicePhoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId(); 2084 for (Phone phone : mPhoneFactoryProxy.getPhones()) { 2085 if (phone.getEmergencyNumberTracker() != null) { 2086 if (phone.getEmergencyNumberTracker().isEmergencyNumber( 2087 emergencyNumberAddress, true)) { 2088 if (isAvailableForEmergencyCalls(phone)) { 2089 // a) 2090 if (phone.getPhoneId() == defaultVoicePhoneId) { 2091 Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports" 2092 + " emergency number: " + phone.getPhoneId()); 2093 return phone; 2094 } 2095 potentialEmergencyPhones.add(phone); 2096 } 2097 } 2098 } 2099 } 2100 // b) 2101 if (potentialEmergencyPhones.size() > 0) { 2102 Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports emergency number:" 2103 + potentialEmergencyPhones.get(0).getPhoneId()); 2104 return getFirstPhoneForEmergencyCall(potentialEmergencyPhones); 2105 } 2106 // c) 2107 return getFirstPhoneForEmergencyCall(); 2108 } 2109 2110 @VisibleForTesting getFirstPhoneForEmergencyCall()2111 public Phone getFirstPhoneForEmergencyCall() { 2112 return getFirstPhoneForEmergencyCall(null); 2113 } 2114 2115 /** 2116 * Retrieves the most sensible Phone to use for an emergency call using the following Priority 2117 * list (for multi-SIM devices): 2118 * 1) The User's SIM preference for Voice calling 2119 * 2) The First Phone that is currently IN_SERVICE or is available for emergency calling 2120 * 3) Prioritize phones that have the dialed emergency number as part of their emergency 2121 * number list 2122 * 4) If there is a PUK locked SIM, compare the SIMs that are not PUK locked. If all the SIMs 2123 * are locked, skip to condition 5). 2124 * 5) The Phone with more Capabilities. 2125 * 6) The First Phone that has a SIM card in it (Starting from Slot 0...N) 2126 * 7) The Default Phone (Currently set as Slot 0) 2127 */ 2128 @VisibleForTesting getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber)2129 public Phone getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber) { 2130 // 1) 2131 int phoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId(); 2132 if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) { 2133 Phone defaultPhone = mPhoneFactoryProxy.getPhone(phoneId); 2134 if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) { 2135 if (phonesWithEmergencyNumber == null 2136 || phonesWithEmergencyNumber.contains(defaultPhone)) { 2137 return defaultPhone; 2138 } 2139 } 2140 } 2141 2142 Phone firstPhoneWithSim = null; 2143 int phoneCount = mTelephonyManagerProxy.getPhoneCount(); 2144 List<SlotStatus> phoneSlotStatus = new ArrayList<>(phoneCount); 2145 for (int i = 0; i < phoneCount; i++) { 2146 Phone phone = mPhoneFactoryProxy.getPhone(i); 2147 if (phone == null) { 2148 continue; 2149 } 2150 // 2) 2151 if (isAvailableForEmergencyCalls(phone)) { 2152 if (phonesWithEmergencyNumber == null 2153 || phonesWithEmergencyNumber.contains(phone)) { 2154 // the slot has the radio on & state is in service. 2155 Log.i(this, 2156 "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i); 2157 return phone; 2158 } 2159 } 2160 // 5) 2161 // Store the RAF Capabilities for sorting later. 2162 int radioAccessFamily = phone.getRadioAccessFamily(); 2163 SlotStatus status = new SlotStatus(i, radioAccessFamily); 2164 phoneSlotStatus.add(status); 2165 Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" + 2166 Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i); 2167 // 4) 2168 // Report Slot's PIN/PUK lock status for sorting later. 2169 int simState = mSubscriptionManagerProxy.getSimStateForSlotIdx(i); 2170 // Record SimState. 2171 status.simState = simState; 2172 if (simState == TelephonyManager.SIM_STATE_PIN_REQUIRED || 2173 simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) { 2174 status.isLocked = true; 2175 } 2176 // 3) Store if the Phone has the corresponding emergency number 2177 if (phonesWithEmergencyNumber != null) { 2178 for (Phone phoneWithEmergencyNumber : phonesWithEmergencyNumber) { 2179 if (phoneWithEmergencyNumber != null 2180 && phoneWithEmergencyNumber.getPhoneId() == i) { 2181 status.hasDialedEmergencyNumber = true; 2182 } 2183 } 2184 } 2185 // 6) 2186 if (firstPhoneWithSim == null && mTelephonyManagerProxy.hasIccCard(i)) { 2187 // The slot has a SIM card inserted, but is not in service, so keep track of this 2188 // Phone. Do not return because we want to make sure that none of the other Phones 2189 // are in service (because that is always faster). 2190 firstPhoneWithSim = phone; 2191 Log.i(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" + 2192 firstPhoneWithSim.getPhoneId()); 2193 } 2194 } 2195 // 7) 2196 if (firstPhoneWithSim == null && phoneSlotStatus.isEmpty()) { 2197 if (phonesWithEmergencyNumber == null || phonesWithEmergencyNumber.isEmpty()) { 2198 // No Phones available, get the default 2199 Log.i(this, "getFirstPhoneForEmergencyCall, return default phone"); 2200 return mPhoneFactoryProxy.getDefaultPhone(); 2201 } 2202 return phonesWithEmergencyNumber.get(0); 2203 } else { 2204 // 5) 2205 final int defaultPhoneId = mPhoneFactoryProxy.getDefaultPhone().getPhoneId(); 2206 final Phone firstOccupiedSlot = firstPhoneWithSim; 2207 if (!phoneSlotStatus.isEmpty()) { 2208 // Only sort if there are enough elements to do so. 2209 if (phoneSlotStatus.size() > 1) { 2210 Collections.sort(phoneSlotStatus, (o1, o2) -> { 2211 // Sort by non-absent SIM. 2212 if (o1.simState == TelephonyManager.SIM_STATE_ABSENT 2213 && o2.simState != TelephonyManager.SIM_STATE_ABSENT) { 2214 return -1; 2215 } 2216 if (o2.simState == TelephonyManager.SIM_STATE_ABSENT 2217 && o1.simState != TelephonyManager.SIM_STATE_ABSENT) { 2218 return 1; 2219 } 2220 // First start by seeing if either of the phone slots are locked. If they 2221 // are, then sort by non-locked SIM first. If they are both locked, sort 2222 // by capability instead. 2223 if (o1.isLocked && !o2.isLocked) { 2224 return -1; 2225 } 2226 if (o2.isLocked && !o1.isLocked) { 2227 return 1; 2228 } 2229 // Prefer slots where the number is considered emergency. 2230 if (!o1.hasDialedEmergencyNumber && o2.hasDialedEmergencyNumber) { 2231 return -1; 2232 } 2233 if (o1.hasDialedEmergencyNumber && !o2.hasDialedEmergencyNumber) { 2234 return 1; 2235 } 2236 // sort by number of RadioAccessFamily Capabilities. 2237 int compare = RadioAccessFamily.compare(o1.capabilities, o2.capabilities); 2238 if (compare == 0) { 2239 if (firstOccupiedSlot != null) { 2240 // If the RAF capability is the same, choose based on whether or 2241 // not any of the slots are occupied with a SIM card (if both 2242 // are, always choose the first). 2243 if (o1.slotId == firstOccupiedSlot.getPhoneId()) { 2244 return 1; 2245 } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) { 2246 return -1; 2247 } 2248 } else { 2249 // No slots have SIMs detected in them, so weight the default 2250 // Phone Id greater than the others. 2251 if (o1.slotId == defaultPhoneId) { 2252 return 1; 2253 } else if (o2.slotId == defaultPhoneId) { 2254 return -1; 2255 } 2256 } 2257 } 2258 return compare; 2259 }); 2260 } 2261 int mostCapablePhoneId = phoneSlotStatus.get(phoneSlotStatus.size() - 1).slotId; 2262 Log.i(this, "getFirstPhoneForEmergencyCall, Using Phone Id: " + mostCapablePhoneId + 2263 "with highest capability"); 2264 return mPhoneFactoryProxy.getPhone(mostCapablePhoneId); 2265 } else { 2266 // 6) 2267 return firstPhoneWithSim; 2268 } 2269 } 2270 } 2271 2272 /** 2273 * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only. 2274 */ isAvailableForEmergencyCalls(Phone phone)2275 private boolean isAvailableForEmergencyCalls(Phone phone) { 2276 return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() || 2277 phone.getServiceState().isEmergencyOnly(); 2278 } 2279 2280 /** 2281 * Determines if the connection should allow mute. 2282 * 2283 * @param phone The current phone. 2284 * @return {@code True} if the connection should allow mute. 2285 */ allowsMute(Phone phone)2286 private boolean allowsMute(Phone phone) { 2287 // For CDMA phones, check if we are in Emergency Callback Mode (ECM). Mute is disallowed 2288 // in ECM mode. 2289 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 2290 if (phone.isInEcm()) { 2291 return false; 2292 } 2293 } 2294 2295 return true; 2296 } 2297 getTelephonyConnectionListener()2298 TelephonyConnection.TelephonyConnectionListener getTelephonyConnectionListener() { 2299 return mTelephonyConnectionListener; 2300 } 2301 2302 /** 2303 * When a {@link TelephonyConnection} has its underlying original connection configured, 2304 * we need to add it to the correct conference controller. 2305 * 2306 * @param connection The connection to be added to the controller 2307 */ addConnectionToConferenceController(TelephonyConnection connection)2308 public void addConnectionToConferenceController(TelephonyConnection connection) { 2309 // TODO: Need to revisit what happens when the original connection for the 2310 // TelephonyConnection changes. If going from CDMA --> GSM (for example), the 2311 // instance of TelephonyConnection will still be a CdmaConnection, not a GsmConnection. 2312 // The CDMA conference controller makes the assumption that it will only have CDMA 2313 // connections in it, while the other conference controllers aren't as restrictive. Really, 2314 // when we go between CDMA and GSM we should replace the TelephonyConnection. 2315 if (connection.isImsConnection()) { 2316 Log.d(this, "Adding IMS connection to conference controller: " + connection); 2317 mImsConferenceController.add(connection); 2318 mTelephonyConferenceController.remove(connection); 2319 if (connection instanceof CdmaConnection) { 2320 mCdmaConferenceController.remove((CdmaConnection) connection); 2321 } 2322 } else { 2323 int phoneType = connection.getCall().getPhone().getPhoneType(); 2324 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 2325 Log.d(this, "Adding GSM connection to conference controller: " + connection); 2326 mTelephonyConferenceController.add(connection); 2327 if (connection instanceof CdmaConnection) { 2328 mCdmaConferenceController.remove((CdmaConnection) connection); 2329 } 2330 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA && 2331 connection instanceof CdmaConnection) { 2332 Log.d(this, "Adding CDMA connection to conference controller: " + connection); 2333 mCdmaConferenceController.add((CdmaConnection) connection); 2334 mTelephonyConferenceController.remove(connection); 2335 } 2336 Log.d(this, "Removing connection from IMS conference controller: " + connection); 2337 mImsConferenceController.remove(connection); 2338 } 2339 } 2340 2341 /** 2342 * Create a new CDMA connection. CDMA connections have additional limitations when creating 2343 * additional calls which are handled in this method. Specifically, CDMA has a "FLASH" command 2344 * that can be used for three purposes: merging a call, swapping unmerged calls, and adding 2345 * a new outgoing call. The function of the flash command depends on the context of the current 2346 * set of calls. This method will prevent an outgoing call from being made if it is not within 2347 * the right circumstances to support adding a call. 2348 */ checkAdditionalOutgoingCallLimits(Phone phone)2349 private Connection checkAdditionalOutgoingCallLimits(Phone phone) { 2350 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 2351 // Check to see if any CDMA conference calls exist, and if they do, check them for 2352 // limitations. 2353 for (Conference conference : getAllConferences()) { 2354 if (conference instanceof CdmaConference) { 2355 CdmaConference cdmaConf = (CdmaConference) conference; 2356 2357 // If the CDMA conference has not been merged, add-call will not work, so fail 2358 // this request to add a call. 2359 if ((cdmaConf.getConnectionCapabilities() 2360 & Connection.CAPABILITY_MERGE_CONFERENCE) != 0) { 2361 return Connection.createFailedConnection(new DisconnectCause( 2362 DisconnectCause.RESTRICTED, 2363 null, 2364 getResources().getString(R.string.callFailed_cdma_call_limit), 2365 "merge-capable call exists, prevent flash command.")); 2366 } 2367 } 2368 } 2369 } 2370 2371 return null; // null means nothing went wrong, and call should continue. 2372 } 2373 2374 /** 2375 * For outgoing dialed calls, potentially send a ConnectionEvent if the user is on WFC and is 2376 * dialing an international number. 2377 * @param telephonyConnection The connection. 2378 */ maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection)2379 private void maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection) { 2380 if (telephonyConnection == null || telephonyConnection.getPhone() == null || 2381 telephonyConnection.getPhone().getDefaultPhone() == null) { 2382 return; 2383 } 2384 Phone phone = telephonyConnection.getPhone().getDefaultPhone(); 2385 if (phone instanceof GsmCdmaPhone) { 2386 GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone; 2387 if (telephonyConnection.isOutgoingCall() && 2388 gsmCdmaPhone.isNotificationOfWfcCallRequired( 2389 telephonyConnection.getOriginalConnection().getOrigDialString())) { 2390 // Send connection event to InCall UI to inform the user of the fact they 2391 // are potentially placing an international call on WFC. 2392 Log.i(this, "placeOutgoingConnection - sending international call on WFC " + 2393 "confirmation event"); 2394 telephonyConnection.sendTelephonyConnectionEvent( 2395 TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null); 2396 } 2397 } 2398 } 2399 handleTtyModeChange(boolean isTtyEnabled)2400 private void handleTtyModeChange(boolean isTtyEnabled) { 2401 Log.i(this, "handleTtyModeChange; isTtyEnabled=%b", isTtyEnabled); 2402 mIsTtyEnabled = isTtyEnabled; 2403 for (Connection connection : getAllConnections()) { 2404 if (connection instanceof TelephonyConnection) { 2405 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 2406 telephonyConnection.setTtyEnabled(isTtyEnabled); 2407 } 2408 } 2409 } 2410 closeOrDestroyConnection(Connection connection, DisconnectCause cause)2411 private void closeOrDestroyConnection(Connection connection, DisconnectCause cause) { 2412 if (connection instanceof TelephonyConnection) { 2413 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 2414 telephonyConnection.setTelephonyConnectionDisconnected(cause); 2415 // Close destroys the connection and notifies TelephonyConnection listeners. 2416 telephonyConnection.close(); 2417 } else { 2418 connection.setDisconnected(cause); 2419 connection.destroy(); 2420 } 2421 } 2422 showDataDialog(Phone phone, String number)2423 private boolean showDataDialog(Phone phone, String number) { 2424 boolean ret = false; 2425 final Context context = getApplicationContext(); 2426 String suppKey = MmiCodeUtil.getSuppServiceKey(number); 2427 if (suppKey != null) { 2428 boolean clirOverUtPrecautions = false; 2429 boolean cfOverUtPrecautions = false; 2430 boolean cbOverUtPrecautions = false; 2431 boolean cwOverUtPrecautions = false; 2432 2433 CarrierConfigManager cfgManager = (CarrierConfigManager) 2434 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 2435 if (cfgManager != null) { 2436 clirOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 2437 .getBoolean(CarrierConfigManager.KEY_CALLER_ID_OVER_UT_WARNING_BOOL); 2438 cfOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 2439 .getBoolean(CarrierConfigManager.KEY_CALL_FORWARDING_OVER_UT_WARNING_BOOL); 2440 cbOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 2441 .getBoolean(CarrierConfigManager.KEY_CALL_BARRING_OVER_UT_WARNING_BOOL); 2442 cwOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId()) 2443 .getBoolean(CarrierConfigManager.KEY_CALL_WAITING_OVER_UT_WARNING_BOOL); 2444 } 2445 2446 boolean isSsOverUtPrecautions = SuppServicesUiUtil 2447 .isSsOverUtPrecautions(context, phone); 2448 if (isSsOverUtPrecautions) { 2449 boolean showDialog = false; 2450 if (suppKey == MmiCodeUtil.BUTTON_CLIR_KEY && clirOverUtPrecautions) { 2451 showDialog = true; 2452 } else if (suppKey == MmiCodeUtil.CALL_FORWARDING_KEY && cfOverUtPrecautions) { 2453 showDialog = true; 2454 } else if (suppKey == MmiCodeUtil.CALL_BARRING_KEY && cbOverUtPrecautions) { 2455 showDialog = true; 2456 } else if (suppKey == MmiCodeUtil.BUTTON_CW_KEY && cwOverUtPrecautions) { 2457 showDialog = true; 2458 } 2459 2460 if (showDialog) { 2461 Log.d(this, "Creating UT Data enable dialog"); 2462 String message = SuppServicesUiUtil.makeMessage(context, suppKey, phone); 2463 AlertDialog.Builder builder = new AlertDialog.Builder(context); 2464 DialogInterface.OnClickListener networkSettingsClickListener = 2465 new Dialog.OnClickListener() { 2466 @Override 2467 public void onClick(DialogInterface dialog, int which) { 2468 Intent intent = new Intent(Intent.ACTION_MAIN); 2469 ComponentName mobileNetworkSettingsComponent 2470 = new ComponentName( 2471 context.getString( 2472 R.string.mobile_network_settings_package), 2473 context.getString( 2474 R.string.mobile_network_settings_class)); 2475 intent.setComponent(mobileNetworkSettingsComponent); 2476 context.startActivity(intent); 2477 } 2478 }; 2479 Dialog dialog = builder.setMessage(message) 2480 .setNeutralButton(context.getResources().getString( 2481 R.string.settings_label), 2482 networkSettingsClickListener) 2483 .setPositiveButton(context.getResources().getString( 2484 R.string.supp_service_over_ut_precautions_dialog_dismiss), null) 2485 .create(); 2486 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 2487 dialog.show(); 2488 ret = true; 2489 } 2490 } 2491 } 2492 return ret; 2493 } 2494 2495 /** 2496 * Adds a {@link Conference} to the telephony ConnectionService and registers a listener for 2497 * changes to the conference. Should be used instead of {@link #addConference(Conference)}. 2498 * @param conference The conference. 2499 */ addTelephonyConference(@onNull TelephonyConferenceBase conference)2500 public void addTelephonyConference(@NonNull TelephonyConferenceBase conference) { 2501 addConference(conference); 2502 conference.addTelephonyConferenceListener(mTelephonyConferenceListener); 2503 } 2504 2505 /** 2506 * Sends a test device to device message on the active call which supports it. 2507 * Used exclusively from the telephony shell command to send a test message. 2508 * 2509 * @param message the message 2510 * @param value the value 2511 */ sendTestDeviceToDeviceMessage(int message, int value)2512 public void sendTestDeviceToDeviceMessage(int message, int value) { 2513 getAllConnections().stream() 2514 .filter(f -> f instanceof TelephonyConnection) 2515 .forEach(t -> { 2516 TelephonyConnection tc = (TelephonyConnection) t; 2517 if (!tc.isImsConnection()) { 2518 Log.w(this, "sendTestDeviceToDeviceMessage: not an IMS connection"); 2519 return; 2520 } 2521 Communicator c = tc.getCommunicator(); 2522 if (c == null) { 2523 Log.w(this, "sendTestDeviceToDeviceMessage: D2D not enabled"); 2524 return; 2525 } 2526 2527 c.sendMessages(new HashSet<Communicator.Message>() {{ 2528 add(new Communicator.Message(message, value)); 2529 }}); 2530 2531 }); 2532 } 2533 2534 /** 2535 * Overrides the current D2D transport, forcing the specified one to be active. Used for test. 2536 * @param transport The class simple name of the transport to make active. 2537 */ setActiveDeviceToDeviceTransport(@onNull String transport)2538 public void setActiveDeviceToDeviceTransport(@NonNull String transport) { 2539 getAllConnections().stream() 2540 .filter(f -> f instanceof TelephonyConnection) 2541 .forEach(t -> { 2542 TelephonyConnection tc = (TelephonyConnection) t; 2543 Communicator c = tc.getCommunicator(); 2544 if (c == null) { 2545 Log.w(this, "setActiveDeviceToDeviceTransport: D2D not enabled"); 2546 return; 2547 } 2548 Log.i(this, "setActiveDeviceToDeviceTransport: callId=%s, set to: %s", 2549 tc.getTelecomCallId(), transport); 2550 c.setTransportActive(transport); 2551 }); 2552 } 2553 adjustAccountHandle(Phone phone, PhoneAccountHandle origAccountHandle)2554 private PhoneAccountHandle adjustAccountHandle(Phone phone, 2555 PhoneAccountHandle origAccountHandle) { 2556 int origSubId = PhoneUtils.getSubIdForPhoneAccountHandle(origAccountHandle); 2557 int subId = phone.getSubId(); 2558 if (origSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID 2559 && subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID 2560 && origSubId != subId) { 2561 PhoneAccountHandle handle = TelecomAccountRegistry.getInstance(this) 2562 .getPhoneAccountHandleForSubId(subId); 2563 if (handle != null) { 2564 return handle; 2565 } 2566 } 2567 return origAccountHandle; 2568 } 2569 2570 /** 2571 * For the passed in incoming {@link TelephonyConnection}, add 2572 * {@link Connection#EXTRA_ANSWERING_DROPS_FG_CALL} if there are ongoing calls on another 2573 * subscription (ie phone account handle) than the one passed in. 2574 * @param connection The connection. 2575 * @param phoneAccountHandle The {@link PhoneAccountHandle} the incoming call originated on; 2576 * this is passed in because 2577 * {@link Connection#getPhoneAccountHandle()} is not set until after 2578 * {@link ConnectionService#onCreateIncomingConnection( 2579 * PhoneAccountHandle, ConnectionRequest)} returns. 2580 */ maybeIndicateAnsweringWillDisconnect(@onNull TelephonyConnection connection, @NonNull PhoneAccountHandle phoneAccountHandle)2581 public void maybeIndicateAnsweringWillDisconnect(@NonNull TelephonyConnection connection, 2582 @NonNull PhoneAccountHandle phoneAccountHandle) { 2583 if (isCallPresentOnOtherSub(phoneAccountHandle)) { 2584 Log.i(this, "maybeIndicateAnsweringWillDisconnect; answering call %s will cause a call " 2585 + "on another subscription to drop.", connection.getTelecomCallId()); 2586 Bundle extras = new Bundle(); 2587 extras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true); 2588 connection.putExtras(extras); 2589 } 2590 } 2591 2592 /** 2593 * Checks to see if there are calls present on a sub other than the one passed in. 2594 * @param incomingHandle The new incoming connection {@link PhoneAccountHandle} 2595 */ isCallPresentOnOtherSub(@onNull PhoneAccountHandle incomingHandle)2596 private boolean isCallPresentOnOtherSub(@NonNull PhoneAccountHandle incomingHandle) { 2597 return getAllConnections().stream() 2598 .filter(c -> 2599 // Exclude multiendpoint calls as they're not on this device. 2600 (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0 2601 // Include any calls not on same sub as current connection. 2602 && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle)) 2603 .count() > 0; 2604 } 2605 2606 /** 2607 * Where there are ongoing calls on another subscription other than the one specified, 2608 * disconnect these calls. This is used where there is an incoming call on one sub, but there 2609 * are ongoing calls on another sub which need to be disconnected. 2610 * @param incomingHandle The incoming {@link PhoneAccountHandle}. 2611 */ maybeDisconnectCallsOnOtherSubs(@onNull PhoneAccountHandle incomingHandle)2612 public void maybeDisconnectCallsOnOtherSubs(@NonNull PhoneAccountHandle incomingHandle) { 2613 Log.i(this, "maybeDisconnectCallsOnOtherSubs: check for calls not on %s", incomingHandle); 2614 maybeDisconnectCallsOnOtherSubs(getAllConnections(), incomingHandle); 2615 } 2616 2617 /** 2618 * Used by {@link #maybeDisconnectCallsOnOtherSubs(PhoneAccountHandle)} to perform call 2619 * disconnection. This method exists as a convenience so that it is possible to unit test 2620 * the core functionality. 2621 * @param connections the calls to check. 2622 * @param incomingHandle the incoming handle. 2623 */ 2624 @VisibleForTesting maybeDisconnectCallsOnOtherSubs(@onNull Collection<Connection> connections, @NonNull PhoneAccountHandle incomingHandle)2625 public static void maybeDisconnectCallsOnOtherSubs(@NonNull Collection<Connection> connections, 2626 @NonNull PhoneAccountHandle incomingHandle) { 2627 connections.stream() 2628 .filter(c -> 2629 // Exclude multiendpoint calls as they're not on this device. 2630 (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0 2631 // Include any calls not on same sub as current connection. 2632 && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle)) 2633 .forEach(c -> { 2634 if (c instanceof TelephonyConnection) { 2635 TelephonyConnection tc = (TelephonyConnection) c; 2636 if (!tc.shouldTreatAsEmergencyCall()) { 2637 Log.i(LOG_TAG, "maybeDisconnectCallsOnOtherSubs: disconnect %s due to " 2638 + "incoming call on other sub.", tc.getTelecomCallId()); 2639 // Note: intentionally calling hangup instead of onDisconnect. 2640 // onDisconnect posts the disconnection to a handle which means that the 2641 // disconnection will take place AFTER we answer the incoming call. 2642 tc.hangup(android.telephony.DisconnectCause.LOCAL); 2643 } 2644 } 2645 }); 2646 } 2647 } 2648