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