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