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.content.ActivityNotFoundException; 21 import android.content.BroadcastReceiver; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.provider.Settings; 29 import android.telecom.Conference; 30 import android.telecom.Connection; 31 import android.telecom.ConnectionRequest; 32 import android.telecom.ConnectionService; 33 import android.telecom.DisconnectCause; 34 import android.telecom.PhoneAccount; 35 import android.telecom.PhoneAccountHandle; 36 import android.telecom.TelecomManager; 37 import android.telecom.VideoProfile; 38 import android.telephony.CarrierConfigManager; 39 import android.telephony.PhoneNumberUtils; 40 import android.telephony.RadioAccessFamily; 41 import android.telephony.ServiceState; 42 import android.telephony.SubscriptionManager; 43 import android.telephony.TelephonyManager; 44 import android.telephony.emergency.EmergencyNumber; 45 import android.text.TextUtils; 46 import android.util.Pair; 47 48 import com.android.internal.annotations.VisibleForTesting; 49 import com.android.internal.telephony.Call; 50 import com.android.internal.telephony.CallStateException; 51 import com.android.internal.telephony.GsmCdmaPhone; 52 import com.android.internal.telephony.IccCard; 53 import com.android.internal.telephony.IccCardConstants; 54 import com.android.internal.telephony.Phone; 55 import com.android.internal.telephony.PhoneConstants; 56 import com.android.internal.telephony.PhoneFactory; 57 import com.android.internal.telephony.PhoneSwitcher; 58 import com.android.internal.telephony.RIL; 59 import com.android.internal.telephony.SubscriptionController; 60 import com.android.internal.telephony.imsphone.ImsExternalCallTracker; 61 import com.android.internal.telephony.imsphone.ImsPhone; 62 import com.android.internal.telephony.imsphone.ImsPhoneConnection; 63 import com.android.phone.MMIDialogActivity; 64 import com.android.phone.PhoneUtils; 65 import com.android.phone.R; 66 67 import java.lang.ref.WeakReference; 68 import java.util.ArrayList; 69 import java.util.Arrays; 70 import java.util.Collection; 71 import java.util.Collections; 72 import java.util.LinkedList; 73 import java.util.List; 74 import java.util.Map; 75 import java.util.Queue; 76 import java.util.concurrent.CompletableFuture; 77 import java.util.concurrent.TimeUnit; 78 import java.util.regex.Pattern; 79 80 import javax.annotation.Nullable; 81 82 /** 83 * Service for making GSM and CDMA connections. 84 */ 85 public class TelephonyConnectionService extends ConnectionService { 86 87 // Timeout before we continue with the emergency call without waiting for DDS switch response 88 // from the modem. 89 private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000; 90 91 // If configured, reject attempts to dial numbers matching this pattern. 92 private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN = 93 Pattern.compile("\\*228[0-9]{0,2}"); 94 95 private final TelephonyConnectionServiceProxy mTelephonyConnectionServiceProxy = 96 new TelephonyConnectionServiceProxy() { 97 @Override 98 public Collection<Connection> getAllConnections() { 99 return TelephonyConnectionService.this.getAllConnections(); 100 } 101 @Override 102 public void addConference(TelephonyConference mTelephonyConference) { 103 TelephonyConnectionService.this.addConference(mTelephonyConference); 104 } 105 @Override 106 public void addConference(ImsConference mImsConference) { 107 TelephonyConnectionService.this.addConference(mImsConference); 108 } 109 @Override 110 public void removeConnection(Connection connection) { 111 TelephonyConnectionService.this.removeConnection(connection); 112 } 113 @Override 114 public void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 115 Connection connection) { 116 TelephonyConnectionService.this 117 .addExistingConnection(phoneAccountHandle, connection); 118 } 119 @Override 120 public void addExistingConnection(PhoneAccountHandle phoneAccountHandle, 121 Connection connection, Conference conference) { 122 TelephonyConnectionService.this 123 .addExistingConnection(phoneAccountHandle, connection, conference); 124 } 125 @Override 126 public void addConnectionToConferenceController(TelephonyConnection connection) { 127 TelephonyConnectionService.this.addConnectionToConferenceController(connection); 128 } 129 }; 130 131 private final Connection.Listener mConnectionListener = new Connection.Listener() { 132 @Override 133 public void onConferenceChanged(Connection connection, Conference conference) { 134 mHoldTracker.updateHoldCapability(connection.getPhoneAccountHandle()); 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 final ImsConferenceController mImsConferenceController = 160 new ImsConferenceController(TelecomAccountRegistry.getInstance(this), 161 mTelephonyConnectionServiceProxy, 162 // FeatureFlagProxy; used to determine if standalone call emulation is enabled. 163 // TODO: Move to carrier config 164 () -> true); 165 166 private ComponentName mExpectedComponentName = null; 167 private RadioOnHelper mRadioOnHelper; 168 private EmergencyTonePlayer mEmergencyTonePlayer; 169 private HoldTracker mHoldTracker; 170 private boolean mIsTtyEnabled; 171 172 // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has 173 // already tried to connect with. There should be only one TelephonyConnection trying to place a 174 // call at one time. We also only access this cache from a TelephonyConnection that wishes to 175 // redial, so we use a WeakReference that will become stale once the TelephonyConnection is 176 // destroyed. 177 @VisibleForTesting 178 public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache; 179 180 /** 181 * Keeps track of the status of a SIM slot. 182 */ 183 private static class SlotStatus { 184 public int slotId; 185 // RAT capabilities 186 public int capabilities; 187 // By default, we will assume that the slots are not locked. 188 public boolean isLocked = false; 189 // Is the emergency number associated with the slot 190 public boolean hasDialedEmergencyNumber = false; 191 SlotStatus(int slotId, int capabilities)192 public SlotStatus(int slotId, int capabilities) { 193 this.slotId = slotId; 194 this.capabilities = capabilities; 195 } 196 } 197 198 // SubscriptionManager Proxy interface for testing 199 public interface SubscriptionManagerProxy { getDefaultVoicePhoneId()200 int getDefaultVoicePhoneId(); getSimStateForSlotIdx(int slotId)201 int getSimStateForSlotIdx(int slotId); getPhoneId(int subId)202 int getPhoneId(int subId); 203 } 204 205 private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() { 206 @Override 207 public int getDefaultVoicePhoneId() { 208 return SubscriptionManager.getDefaultVoicePhoneId(); 209 } 210 211 @Override 212 public int getSimStateForSlotIdx(int slotId) { 213 return SubscriptionManager.getSimStateForSlotIndex(slotId); 214 } 215 216 @Override 217 public int getPhoneId(int subId) { 218 return SubscriptionManager.getPhoneId(subId); 219 } 220 }; 221 222 // TelephonyManager Proxy interface for testing 223 @VisibleForTesting 224 public interface TelephonyManagerProxy { getPhoneCount()225 int getPhoneCount(); hasIccCard(int slotId)226 boolean hasIccCard(int slotId); isCurrentEmergencyNumber(String number)227 boolean isCurrentEmergencyNumber(String number); getCurrentEmergencyNumberList()228 Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList(); 229 } 230 231 private TelephonyManagerProxy mTelephonyManagerProxy; 232 233 private class TelephonyManagerProxyImpl implements TelephonyManagerProxy { 234 private final TelephonyManager mTelephonyManager; 235 236 TelephonyManagerProxyImpl(Context context)237 TelephonyManagerProxyImpl(Context context) { 238 mTelephonyManager = new TelephonyManager(context); 239 } 240 241 @Override getPhoneCount()242 public int getPhoneCount() { 243 return mTelephonyManager.getPhoneCount(); 244 } 245 246 @Override hasIccCard(int slotId)247 public boolean hasIccCard(int slotId) { 248 return mTelephonyManager.hasIccCard(slotId); 249 } 250 251 @Override isCurrentEmergencyNumber(String number)252 public boolean isCurrentEmergencyNumber(String number) { 253 return mTelephonyManager.isEmergencyNumber(number); 254 } 255 256 @Override getCurrentEmergencyNumberList()257 public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList() { 258 return mTelephonyManager.getEmergencyNumberList(); 259 } 260 } 261 262 //PhoneFactory proxy interface for testing 263 @VisibleForTesting 264 public interface PhoneFactoryProxy { getPhone(int index)265 Phone getPhone(int index); getDefaultPhone()266 Phone getDefaultPhone(); getPhones()267 Phone[] getPhones(); 268 } 269 270 private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() { 271 @Override 272 public Phone getPhone(int index) { 273 return PhoneFactory.getPhone(index); 274 } 275 276 @Override 277 public Phone getDefaultPhone() { 278 return PhoneFactory.getDefaultPhone(); 279 } 280 281 @Override 282 public Phone[] getPhones() { 283 return PhoneFactory.getPhones(); 284 } 285 }; 286 287 @VisibleForTesting setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)288 public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) { 289 mSubscriptionManagerProxy = proxy; 290 } 291 292 @VisibleForTesting setTelephonyManagerProxy(TelephonyManagerProxy proxy)293 public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) { 294 mTelephonyManagerProxy = proxy; 295 } 296 297 @VisibleForTesting setPhoneFactoryProxy(PhoneFactoryProxy proxy)298 public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) { 299 mPhoneFactoryProxy = proxy; 300 } 301 302 /** 303 * A listener to actionable events specific to the TelephonyConnection. 304 */ 305 private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener = 306 new TelephonyConnection.TelephonyConnectionListener() { 307 @Override 308 public void onOriginalConnectionConfigured(TelephonyConnection c) { 309 addConnectionToConferenceController(c); 310 } 311 312 @Override 313 public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) { 314 retryOutgoingOriginalConnection(c, isPermanentFailure); 315 } 316 }; 317 318 @Override onCreate()319 public void onCreate() { 320 super.onCreate(); 321 Log.initLogging(this); 322 setTelephonyManagerProxy(new TelephonyManagerProxyImpl(getApplicationContext())); 323 mExpectedComponentName = new ComponentName(this, this.getClass()); 324 mEmergencyTonePlayer = new EmergencyTonePlayer(this); 325 TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this); 326 mHoldTracker = new HoldTracker(); 327 mIsTtyEnabled = isTtyModeEnabled(getApplicationContext()); 328 329 IntentFilter intentFilter = new IntentFilter( 330 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED); 331 registerReceiver(mTtyBroadcastReceiver, intentFilter); 332 } 333 334 @Override onUnbind(Intent intent)335 public boolean onUnbind(Intent intent) { 336 unregisterReceiver(mTtyBroadcastReceiver); 337 return super.onUnbind(intent); 338 } 339 340 @Override onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)341 public Connection onCreateOutgoingConnection( 342 PhoneAccountHandle connectionManagerPhoneAccount, 343 final ConnectionRequest request) { 344 Log.i(this, "onCreateOutgoingConnection, request: " + request); 345 346 Uri handle = request.getAddress(); 347 if (handle == null) { 348 Log.d(this, "onCreateOutgoingConnection, handle is null"); 349 return Connection.createFailedConnection( 350 DisconnectCauseUtil.toTelecomDisconnectCause( 351 android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, 352 "No phone number supplied")); 353 } 354 355 String scheme = handle.getScheme(); 356 String number; 357 if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) { 358 // TODO: We don't check for SecurityException here (requires 359 // CALL_PRIVILEGED permission). 360 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 361 false /* isEmergencyCall */, null /* not an emergency call */); 362 if (phone == null) { 363 Log.d(this, "onCreateOutgoingConnection, phone is null"); 364 return Connection.createFailedConnection( 365 DisconnectCauseUtil.toTelecomDisconnectCause( 366 android.telephony.DisconnectCause.OUT_OF_SERVICE, 367 "Phone is null")); 368 } 369 number = phone.getVoiceMailNumber(); 370 if (TextUtils.isEmpty(number)) { 371 Log.d(this, "onCreateOutgoingConnection, no voicemail number set."); 372 return Connection.createFailedConnection( 373 DisconnectCauseUtil.toTelecomDisconnectCause( 374 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING, 375 "Voicemail scheme provided but no voicemail number set.", 376 phone.getPhoneId())); 377 } 378 379 // Convert voicemail: to tel: 380 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 381 } else { 382 if (!PhoneAccount.SCHEME_TEL.equals(scheme)) { 383 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme); 384 return Connection.createFailedConnection( 385 DisconnectCauseUtil.toTelecomDisconnectCause( 386 android.telephony.DisconnectCause.INVALID_NUMBER, 387 "Handle scheme is not type tel")); 388 } 389 390 number = handle.getSchemeSpecificPart(); 391 if (TextUtils.isEmpty(number)) { 392 Log.d(this, "onCreateOutgoingConnection, unable to parse number"); 393 return Connection.createFailedConnection( 394 DisconnectCauseUtil.toTelecomDisconnectCause( 395 android.telephony.DisconnectCause.INVALID_NUMBER, 396 "Unable to parse number")); 397 } 398 399 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 400 false /* isEmergencyCall*/, null /* not an emergency call */); 401 if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) { 402 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number 403 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and 404 // when dialed could lock LTE SIMs to 3G if not prohibited.. 405 boolean disableActivation = false; 406 CarrierConfigManager cfgManager = (CarrierConfigManager) 407 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 408 if (cfgManager != null) { 409 disableActivation = cfgManager.getConfigForSubId(phone.getSubId()) 410 .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL); 411 } 412 413 if (disableActivation) { 414 return Connection.createFailedConnection( 415 DisconnectCauseUtil.toTelecomDisconnectCause( 416 android.telephony.DisconnectCause 417 .CDMA_ALREADY_ACTIVATED, 418 "Tried to dial *228", 419 phone.getPhoneId())); 420 } 421 } 422 } 423 424 final boolean isEmergencyNumber = mTelephonyManagerProxy.isCurrentEmergencyNumber(number); 425 // Find out if this is a test emergency number 426 final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number); 427 428 // Convert into emergency number if necessary 429 // This is required in some regions (e.g. Taiwan). 430 if (isEmergencyNumber) { 431 final Phone phone = getPhoneForAccount(request.getAccountHandle(), false, 432 handle.getSchemeSpecificPart()); 433 // We only do the conversion if the phone is not in service. The un-converted 434 // emergency numbers will go to the correct destination when the phone is in-service, 435 // so they will only need the special emergency call setup when the phone is out of 436 // service. 437 if (phone == null || phone.getServiceState().getState() 438 != ServiceState.STATE_IN_SERVICE) { 439 String convertedNumber = PhoneNumberUtils.convertToEmergencyNumber(this, number); 440 if (!TextUtils.equals(convertedNumber, number)) { 441 Log.i(this, "onCreateOutgoingConnection, converted to emergency number"); 442 number = convertedNumber; 443 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 444 } 445 } 446 } 447 final String numberToDial = number; 448 449 450 final boolean isAirplaneModeOn = Settings.Global.getInt(getContentResolver(), 451 Settings.Global.AIRPLANE_MODE_ON, 0) > 0; 452 453 boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn)) 454 || isRadioPowerDownOnBluetooth(); 455 456 if (needToTurnOnRadio) { 457 final Uri resultHandle = handle; 458 // By default, Connection based on the default Phone, since we need to return to Telecom 459 // now. 460 final int originalPhoneType = PhoneFactory.getDefaultPhone().getPhoneType(); 461 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 462 isEmergencyNumber, resultHandle, PhoneFactory.getDefaultPhone()); 463 if (mRadioOnHelper == null) { 464 mRadioOnHelper = new RadioOnHelper(this); 465 } 466 mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() { 467 @Override 468 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) { 469 handleOnComplete(isRadioReady, isEmergencyNumber, resultConnection, request, 470 numberToDial, resultHandle, originalPhoneType); 471 } 472 473 @Override 474 public boolean isOkToCall(Phone phone, int serviceState) { 475 // HAL 1.4 introduced a new variant of dial for emergency calls, which includes 476 // an isTesting parameter. For HAL 1.4+, do not wait for IN_SERVICE, this will 477 // be handled at the RIL/vendor level by emergencyDial(...). 478 boolean waitForInServiceToDialEmergency = isTestEmergencyNumber 479 && phone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_4); 480 if (isEmergencyNumber && !waitForInServiceToDialEmergency) { 481 // We currently only look to make sure that the radio is on before dialing. 482 // We should be able to make emergency calls at any time after the radio has 483 // been powered on and isn't in the UNAVAILABLE state, even if it is 484 // reporting the OUT_OF_SERVICE state. 485 return (phone.getState() == PhoneConstants.State.OFFHOOK) 486 || phone.getServiceState().getState() != ServiceState.STATE_POWER_OFF; 487 } else { 488 // Wait until we are in service and ready to make calls. This can happen 489 // when we power down the radio on bluetooth to save power on watches or if 490 // it is a test emergency number and we have to wait for the device to move 491 // IN_SERVICE before the call can take place over normal routing. 492 return (phone.getState() == PhoneConstants.State.OFFHOOK) 493 || serviceState == ServiceState.STATE_IN_SERVICE; 494 } 495 } 496 }); 497 // Return the still unconnected GsmConnection and wait for the Radios to boot before 498 // connecting it to the underlying Phone. 499 return resultConnection; 500 } else { 501 if (!canAddCall() && !isEmergencyNumber) { 502 Log.d(this, "onCreateOutgoingConnection, cannot add call ."); 503 return Connection.createFailedConnection( 504 new DisconnectCause(DisconnectCause.ERROR, 505 getApplicationContext().getText( 506 R.string.incall_error_cannot_add_call), 507 getApplicationContext().getText( 508 R.string.incall_error_cannot_add_call), 509 "Add call restricted due to ongoing video call")); 510 } 511 512 // Get the right phone object from the account data passed in. 513 final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber, 514 /* Note: when not an emergency, handle can be null for unknown callers */ 515 handle == null ? null : handle.getSchemeSpecificPart()); 516 if (!isEmergencyNumber) { 517 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 518 false, handle, phone); 519 return placeOutgoingConnection(request, resultConnection, phone); 520 } else { 521 final Connection resultConnection = getTelephonyConnection(request, numberToDial, 522 true, handle, phone); 523 CompletableFuture<Boolean> phoneFuture = delayDialForDdsSwitch(phone); 524 phoneFuture.whenComplete((result, error) -> { 525 if (error != null) { 526 Log.w(this, "onCreateOutgoingConn - delayDialForDdsSwitch exception= " 527 + error.getMessage()); 528 } 529 Log.i(this, "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result); 530 placeOutgoingConnection(request, resultConnection, phone); 531 }); 532 return resultConnection; 533 } 534 } 535 } 536 placeOutgoingConnection(ConnectionRequest request, Connection resultConnection, Phone phone)537 private Connection placeOutgoingConnection(ConnectionRequest request, 538 Connection resultConnection, Phone phone) { 539 // If there was a failure, the resulting connection will not be a TelephonyConnection, 540 // so don't place the call! 541 if (resultConnection instanceof TelephonyConnection) { 542 if (request.getExtras() != null && request.getExtras().getBoolean( 543 TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) { 544 ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true); 545 } 546 placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request); 547 } 548 return resultConnection; 549 } 550 isEmergencyNumberTestNumber(String number)551 private boolean isEmergencyNumberTestNumber(String number) { 552 number = PhoneNumberUtils.stripSeparators(number); 553 Map<Integer, List<EmergencyNumber>> list = 554 mTelephonyManagerProxy.getCurrentEmergencyNumberList(); 555 // Do not worry about which subscription the test emergency call is on yet, only detect that 556 // it is an emergency. 557 for (Integer sub : list.keySet()) { 558 for (EmergencyNumber eNumber : list.get(sub)) { 559 if (number.equals(eNumber.getNumber()) 560 && eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)) { 561 Log.i(this, "isEmergencyNumberTestNumber: " + number + " has been detected as " 562 + "a test emergency number.,"); 563 return true; 564 } 565 } 566 } 567 return false; 568 } 569 570 /** 571 * Whether the cellular radio is power off because the device is on Bluetooth. 572 */ isRadioPowerDownOnBluetooth()573 private boolean isRadioPowerDownOnBluetooth() { 574 final Context context = getApplicationContext(); 575 final boolean allowed = context.getResources().getBoolean( 576 R.bool.config_allowRadioPowerDownOnBluetooth); 577 final int cellOn = Settings.Global.getInt(context.getContentResolver(), 578 Settings.Global.CELL_ON, 579 PhoneConstants.CELL_ON_FLAG); 580 return (allowed && cellOn == PhoneConstants.CELL_ON_FLAG && !isRadioOn()); 581 } 582 583 /** 584 * Handle the onComplete callback of RadioOnStateListener. 585 */ handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, Connection originalConnection, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType)586 private void handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, 587 Connection originalConnection, ConnectionRequest request, String numberToDial, 588 Uri handle, int originalPhoneType) { 589 // Make sure the Call has not already been canceled by the user. 590 if (originalConnection.getState() == Connection.STATE_DISCONNECTED) { 591 Log.i(this, "Call disconnected before the outgoing call was placed. Skipping call " 592 + "placement."); 593 return; 594 } 595 // Get the right phone object since the radio has been turned on successfully. 596 if (isRadioReady) { 597 final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber, 598 /* Note: when not an emergency, handle can be null for unknown callers */ 599 handle == null ? null : handle.getSchemeSpecificPart()); 600 if (!isEmergencyNumber) { 601 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial, 602 handle, originalPhoneType, false); 603 } else { 604 delayDialForDdsSwitch(phone).whenComplete((result, error) -> { 605 if (error != null) { 606 Log.w(this, "handleOnComplete - delayDialForDdsSwitch exception= " 607 + error.getMessage()); 608 } 609 Log.i(this, "handleOnComplete - delayDialForDdsSwitch result = " + result); 610 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, 611 numberToDial, handle, originalPhoneType, true); 612 }); 613 } 614 615 } else { 616 Log.w(this, "onCreateOutgoingConnection, failed to turn on radio"); 617 originalConnection.setDisconnected( 618 DisconnectCauseUtil.toTelecomDisconnectCause( 619 android.telephony.DisconnectCause.POWER_OFF, 620 "Failed to turn on radio.")); 621 originalConnection.destroy(); 622 } 623 } 624 adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, boolean isEmergencyNumber)625 private void adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, 626 ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, 627 boolean isEmergencyNumber) { 628 // If the PhoneType of the Phone being used is different than the Default Phone, then we 629 // need to create a new Connection using that PhoneType and replace it in Telecom. 630 if (phone.getPhoneType() != originalPhoneType) { 631 Connection repConnection = getTelephonyConnection(request, numberToDial, 632 isEmergencyNumber, handle, phone); 633 // If there was a failure, the resulting connection will not be a TelephonyConnection, 634 // so don't place the call, just return! 635 if (repConnection instanceof TelephonyConnection) { 636 placeOutgoingConnection((TelephonyConnection) repConnection, phone, request); 637 } 638 // Notify Telecom of the new Connection type. 639 // TODO: Switch out the underlying connection instead of creating a new 640 // one and causing UI Jank. 641 boolean noActiveSimCard = SubscriptionController.getInstance() 642 .getActiveSubInfoCount(phone.getContext().getOpPackageName()) == 0; 643 // If there's no active sim card and the device is in emergency mode, use E account. 644 addExistingConnection(PhoneUtils.makePstnPhoneAccountHandleWithPrefix( 645 phone, "", isEmergencyNumber && noActiveSimCard), repConnection); 646 // Remove the old connection from Telecom after. 647 connectionToEvaluate.setDisconnected( 648 DisconnectCauseUtil.toTelecomDisconnectCause( 649 android.telephony.DisconnectCause.OUTGOING_CANCELED, 650 "Reconnecting outgoing Emergency Call.", 651 phone.getPhoneId())); 652 connectionToEvaluate.destroy(); 653 } else { 654 placeOutgoingConnection((TelephonyConnection) connectionToEvaluate, phone, request); 655 } 656 } 657 658 /** 659 * @return {@code true} if any other call is disabling the ability to add calls, {@code false} 660 * otherwise. 661 */ canAddCall()662 private boolean canAddCall() { 663 Collection<Connection> connections = getAllConnections(); 664 for (Connection connection : connections) { 665 if (connection.getExtras() != null && 666 connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) { 667 return false; 668 } 669 } 670 return true; 671 } 672 getTelephonyConnection(final ConnectionRequest request, final String number, boolean isEmergencyNumber, final Uri handle, Phone phone)673 private Connection getTelephonyConnection(final ConnectionRequest request, final String number, 674 boolean isEmergencyNumber, final Uri handle, Phone phone) { 675 676 if (phone == null) { 677 final Context context = getApplicationContext(); 678 if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) { 679 // Check SIM card state before the outgoing call. 680 // Start the SIM unlock activity if PIN_REQUIRED. 681 final Phone defaultPhone = mPhoneFactoryProxy.getDefaultPhone(); 682 final IccCard icc = defaultPhone.getIccCard(); 683 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN; 684 if (icc != null) { 685 simState = icc.getState(); 686 } 687 if (simState == IccCardConstants.State.PIN_REQUIRED) { 688 final String simUnlockUiPackage = context.getResources().getString( 689 R.string.config_simUnlockUiPackage); 690 final String simUnlockUiClass = context.getResources().getString( 691 R.string.config_simUnlockUiClass); 692 if (simUnlockUiPackage != null && simUnlockUiClass != null) { 693 Intent simUnlockIntent = new Intent().setComponent(new ComponentName( 694 simUnlockUiPackage, simUnlockUiClass)); 695 simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 696 try { 697 context.startActivity(simUnlockIntent); 698 } catch (ActivityNotFoundException exception) { 699 Log.e(this, exception, "Unable to find SIM unlock UI activity."); 700 } 701 } 702 return Connection.createFailedConnection( 703 DisconnectCauseUtil.toTelecomDisconnectCause( 704 android.telephony.DisconnectCause.OUT_OF_SERVICE, 705 "SIM_STATE_PIN_REQUIRED")); 706 } 707 } 708 709 Log.d(this, "onCreateOutgoingConnection, phone is null"); 710 return Connection.createFailedConnection( 711 DisconnectCauseUtil.toTelecomDisconnectCause( 712 android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null")); 713 } 714 715 // Check both voice & data RAT to enable normal CS call, 716 // when voice RAT is OOS but Data RAT is present. 717 int state = phone.getServiceState().getState(); 718 if (state == ServiceState.STATE_OUT_OF_SERVICE) { 719 int dataNetType = phone.getServiceState().getDataNetworkType(); 720 if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE || 721 dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) { 722 state = phone.getServiceState().getDataRegState(); 723 } 724 } 725 726 // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if 727 // carrier configuration specifies that we cannot make non-emergency calls in ECM mode. 728 if (!isEmergencyNumber && phone.isInEcm()) { 729 boolean allowNonEmergencyCalls = true; 730 CarrierConfigManager cfgManager = (CarrierConfigManager) 731 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 732 if (cfgManager != null) { 733 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId()) 734 .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL); 735 } 736 737 if (!allowNonEmergencyCalls) { 738 return Connection.createFailedConnection( 739 DisconnectCauseUtil.toTelecomDisconnectCause( 740 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY, 741 "Cannot make non-emergency call in ECM mode.", 742 phone.getPhoneId())); 743 } 744 } 745 746 if (!isEmergencyNumber) { 747 switch (state) { 748 case ServiceState.STATE_IN_SERVICE: 749 case ServiceState.STATE_EMERGENCY_ONLY: 750 break; 751 case ServiceState.STATE_OUT_OF_SERVICE: 752 if (phone.isUtEnabled() && number.endsWith("#")) { 753 Log.d(this, "onCreateOutgoingConnection dial for UT"); 754 break; 755 } else { 756 return Connection.createFailedConnection( 757 DisconnectCauseUtil.toTelecomDisconnectCause( 758 android.telephony.DisconnectCause.OUT_OF_SERVICE, 759 "ServiceState.STATE_OUT_OF_SERVICE", 760 phone.getPhoneId())); 761 } 762 case ServiceState.STATE_POWER_OFF: 763 // Don't disconnect if radio is power off because the device is on Bluetooth. 764 if (isRadioPowerDownOnBluetooth()) { 765 break; 766 } 767 return Connection.createFailedConnection( 768 DisconnectCauseUtil.toTelecomDisconnectCause( 769 android.telephony.DisconnectCause.POWER_OFF, 770 "ServiceState.STATE_POWER_OFF", 771 phone.getPhoneId())); 772 default: 773 Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state); 774 return Connection.createFailedConnection( 775 DisconnectCauseUtil.toTelecomDisconnectCause( 776 android.telephony.DisconnectCause.OUTGOING_FAILURE, 777 "Unknown service state " + state, 778 phone.getPhoneId())); 779 } 780 } 781 782 final Context context = getApplicationContext(); 783 final boolean isTtyModeEnabled = isTtyModeEnabled(context); 784 if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled 785 && !isEmergencyNumber) { 786 return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause( 787 android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED, 788 null, phone.getPhoneId())); 789 } 790 791 // Check for additional limits on CDMA phones. 792 final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone); 793 if (failedConnection != null) { 794 return failedConnection; 795 } 796 797 // Check roaming status to see if we should block custom call forwarding codes 798 if (blockCallForwardingNumberWhileRoaming(phone, number)) { 799 return Connection.createFailedConnection( 800 DisconnectCauseUtil.toTelecomDisconnectCause( 801 android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING, 802 "Call forwarding while roaming", 803 phone.getPhoneId())); 804 } 805 806 807 final TelephonyConnection connection = 808 createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(), 809 request.getTelecomCallId(), request.getAddress(), request.getVideoState()); 810 if (connection == null) { 811 return Connection.createFailedConnection( 812 DisconnectCauseUtil.toTelecomDisconnectCause( 813 android.telephony.DisconnectCause.OUTGOING_FAILURE, 814 "Invalid phone type", 815 phone.getPhoneId())); 816 } 817 connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED); 818 connection.setInitializing(); 819 connection.setVideoState(request.getVideoState()); 820 connection.setRttTextStream(request.getRttTextStream()); 821 connection.setTtyEnabled(isTtyModeEnabled); 822 return connection; 823 } 824 825 @Override onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)826 public Connection onCreateIncomingConnection( 827 PhoneAccountHandle connectionManagerPhoneAccount, 828 ConnectionRequest request) { 829 Log.i(this, "onCreateIncomingConnection, request: " + request); 830 // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM), 831 // make sure the PhoneAccount lookup retrieves the default Emergency Phone. 832 PhoneAccountHandle accountHandle = request.getAccountHandle(); 833 boolean isEmergency = false; 834 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 835 accountHandle.getId())) { 836 Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " + 837 "Treat as an Emergency Call."); 838 isEmergency = true; 839 } 840 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 841 /* Note: when not an emergency, handle can be null for unknown callers */ 842 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 843 if (phone == null) { 844 return Connection.createFailedConnection( 845 DisconnectCauseUtil.toTelecomDisconnectCause( 846 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 847 "Phone is null")); 848 } 849 850 Call call = phone.getRingingCall(); 851 if (!call.getState().isRinging()) { 852 Log.i(this, "onCreateIncomingConnection, no ringing call"); 853 return Connection.createFailedConnection( 854 DisconnectCauseUtil.toTelecomDisconnectCause( 855 android.telephony.DisconnectCause.INCOMING_MISSED, 856 "Found no ringing call", 857 phone.getPhoneId())); 858 } 859 860 com.android.internal.telephony.Connection originalConnection = 861 call.getState() == Call.State.WAITING ? 862 call.getLatestConnection() : call.getEarliestConnection(); 863 if (isOriginalConnectionKnown(originalConnection)) { 864 Log.i(this, "onCreateIncomingConnection, original connection already registered"); 865 return Connection.createCanceledConnection(); 866 } 867 868 // We should rely on the originalConnection to get the video state. The request coming 869 // from Telecom does not know the video state of the incoming call. 870 int videoState = originalConnection != null ? originalConnection.getVideoState() : 871 VideoProfile.STATE_AUDIO_ONLY; 872 873 TelephonyConnection connection = 874 createConnectionFor(phone, originalConnection, false /* isOutgoing */, 875 request.getAccountHandle(), request.getTelecomCallId(), 876 request.getAddress(), videoState); 877 handleIncomingRtt(request, originalConnection); 878 if (connection == null) { 879 return Connection.createCanceledConnection(); 880 } else { 881 return connection; 882 } 883 } 884 handleIncomingRtt(ConnectionRequest request, com.android.internal.telephony.Connection originalConnection)885 private void handleIncomingRtt(ConnectionRequest request, 886 com.android.internal.telephony.Connection originalConnection) { 887 if (originalConnection == null 888 || originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) { 889 if (request.isRequestingRtt()) { 890 Log.w(this, "Requesting RTT on non-IMS call, ignoring"); 891 } 892 return; 893 } 894 895 ImsPhoneConnection imsOriginalConnection = (ImsPhoneConnection) originalConnection; 896 if (!request.isRequestingRtt()) { 897 if (imsOriginalConnection.isRttEnabledForCall()) { 898 Log.w(this, "Incoming call requested RTT but we did not get a RttTextStream"); 899 } 900 return; 901 } 902 903 Log.i(this, "Setting RTT stream on ImsPhoneConnection in case we need it later"); 904 imsOriginalConnection.setCurrentRttTextStream(request.getRttTextStream()); 905 906 if (!imsOriginalConnection.isRttEnabledForCall()) { 907 if (request.isRequestingRtt()) { 908 Log.w(this, "Incoming call processed as RTT but did not come in as one. Ignoring"); 909 } 910 return; 911 } 912 913 Log.i(this, "Setting the call to be answered with RTT on."); 914 imsOriginalConnection.getImsCall().setAnswerWithRtt(); 915 } 916 917 /** 918 * Called by the {@link ConnectionService} when a newly created {@link Connection} has been 919 * added to the {@link ConnectionService} and sent to Telecom. Here it is safe to send 920 * connection events. 921 * 922 * @param connection the {@link Connection}. 923 */ 924 @Override onCreateConnectionComplete(Connection connection)925 public void onCreateConnectionComplete(Connection connection) { 926 if (connection instanceof TelephonyConnection) { 927 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 928 maybeSendInternationalCallEvent(telephonyConnection); 929 } 930 } 931 932 @Override triggerConferenceRecalculate()933 public void triggerConferenceRecalculate() { 934 if (mTelephonyConferenceController.shouldRecalculate()) { 935 mTelephonyConferenceController.recalculate(); 936 } 937 } 938 939 @Override onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)940 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 941 ConnectionRequest request) { 942 Log.i(this, "onCreateUnknownConnection, request: " + request); 943 // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's 944 // Emergency PhoneAccount 945 PhoneAccountHandle accountHandle = request.getAccountHandle(); 946 boolean isEmergency = false; 947 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 948 accountHandle.getId())) { 949 Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " + 950 "Treat as an Emergency Call."); 951 isEmergency = true; 952 } 953 Phone phone = getPhoneForAccount(accountHandle, isEmergency, 954 /* Note: when not an emergency, handle can be null for unknown callers */ 955 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart()); 956 if (phone == null) { 957 return Connection.createFailedConnection( 958 DisconnectCauseUtil.toTelecomDisconnectCause( 959 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 960 "Phone is null")); 961 } 962 Bundle extras = request.getExtras(); 963 964 final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>(); 965 966 // Handle the case where an unknown connection has an IMS external call ID specified; we can 967 // skip the rest of the guesswork and just grad that unknown call now. 968 if (phone.getImsPhone() != null && extras != null && 969 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) { 970 971 ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); 972 ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker(); 973 int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, 974 -1); 975 976 if (externalCallTracker != null) { 977 com.android.internal.telephony.Connection connection = 978 externalCallTracker.getConnectionById(externalCallId); 979 980 if (connection != null) { 981 allConnections.add(connection); 982 } 983 } 984 } 985 986 if (allConnections.isEmpty()) { 987 final Call ringingCall = phone.getRingingCall(); 988 if (ringingCall.hasConnections()) { 989 allConnections.addAll(ringingCall.getConnections()); 990 } 991 final Call foregroundCall = phone.getForegroundCall(); 992 if ((foregroundCall.getState() != Call.State.DISCONNECTED) 993 && (foregroundCall.hasConnections())) { 994 allConnections.addAll(foregroundCall.getConnections()); 995 } 996 if (phone.getImsPhone() != null) { 997 final Call imsFgCall = phone.getImsPhone().getForegroundCall(); 998 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall 999 .hasConnections()) { 1000 allConnections.addAll(imsFgCall.getConnections()); 1001 } 1002 } 1003 final Call backgroundCall = phone.getBackgroundCall(); 1004 if (backgroundCall.hasConnections()) { 1005 allConnections.addAll(phone.getBackgroundCall().getConnections()); 1006 } 1007 } 1008 1009 com.android.internal.telephony.Connection unknownConnection = null; 1010 for (com.android.internal.telephony.Connection telephonyConnection : allConnections) { 1011 if (!isOriginalConnectionKnown(telephonyConnection)) { 1012 unknownConnection = telephonyConnection; 1013 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection); 1014 break; 1015 } 1016 } 1017 1018 if (unknownConnection == null) { 1019 Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection."); 1020 return Connection.createCanceledConnection(); 1021 } 1022 1023 // We should rely on the originalConnection to get the video state. The request coming 1024 // from Telecom does not know the video state of the unknown call. 1025 int videoState = unknownConnection != null ? unknownConnection.getVideoState() : 1026 VideoProfile.STATE_AUDIO_ONLY; 1027 1028 TelephonyConnection connection = 1029 createConnectionFor(phone, unknownConnection, 1030 !unknownConnection.isIncoming() /* isOutgoing */, 1031 request.getAccountHandle(), request.getTelecomCallId(), 1032 request.getAddress(), videoState); 1033 1034 if (connection == null) { 1035 return Connection.createCanceledConnection(); 1036 } else { 1037 connection.updateState(); 1038 return connection; 1039 } 1040 } 1041 1042 /** 1043 * Conferences two connections. 1044 * 1045 * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has 1046 * a limitation in that it can only specify conferenceables which are instances of 1047 * {@link android.telecom.RemoteConnection}. In the case of an {@link ImsConference}, the 1048 * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge 1049 * a {@link Conference} and a {@link Connection}. As a result when, merging a 1050 * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference} 1051 * require merging a {@link ConferenceParticipantConnection} which is a child of the 1052 * {@link Conference} with a {@link TelephonyConnection}. The 1053 * {@link ConferenceParticipantConnection} class does not have the capability to initiate a 1054 * conference merge, so we need to call 1055 * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or 1056 * {@code connection2}, one of which is an instance of {@link TelephonyConnection}. 1057 * 1058 * @param connection1 A connection to merge into a conference call. 1059 * @param connection2 A connection to merge into a conference call. 1060 */ 1061 @Override onConference(Connection connection1, Connection connection2)1062 public void onConference(Connection connection1, Connection connection2) { 1063 if (connection1 instanceof TelephonyConnection) { 1064 ((TelephonyConnection) connection1).performConference(connection2); 1065 } else if (connection2 instanceof TelephonyConnection) { 1066 ((TelephonyConnection) connection2).performConference(connection1); 1067 } else { 1068 Log.w(this, "onConference - cannot merge connections " + 1069 "Connection1: %s, Connection2: %2", connection1, connection2); 1070 } 1071 } 1072 1073 @Override onConnectionAdded(Connection connection)1074 public void onConnectionAdded(Connection connection) { 1075 if (connection instanceof Holdable && !isExternalConnection(connection)) { 1076 connection.addConnectionListener(mConnectionListener); 1077 mHoldTracker.addHoldable( 1078 connection.getPhoneAccountHandle(), (Holdable) connection); 1079 } 1080 } 1081 1082 @Override onConnectionRemoved(Connection connection)1083 public void onConnectionRemoved(Connection connection) { 1084 if (connection instanceof Holdable && !isExternalConnection(connection)) { 1085 mHoldTracker.removeHoldable(connection.getPhoneAccountHandle(), (Holdable) connection); 1086 } 1087 } 1088 1089 @Override onConferenceAdded(Conference conference)1090 public void onConferenceAdded(Conference conference) { 1091 if (conference instanceof Holdable) { 1092 mHoldTracker.addHoldable(conference.getPhoneAccountHandle(), (Holdable) conference); 1093 } 1094 } 1095 1096 @Override onConferenceRemoved(Conference conference)1097 public void onConferenceRemoved(Conference conference) { 1098 if (conference instanceof Holdable) { 1099 mHoldTracker.removeHoldable(conference.getPhoneAccountHandle(), (Holdable) conference); 1100 } 1101 } 1102 isExternalConnection(Connection connection)1103 private boolean isExternalConnection(Connection connection) { 1104 return (connection.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) 1105 == Connection.PROPERTY_IS_EXTERNAL_CALL; 1106 } 1107 blockCallForwardingNumberWhileRoaming(Phone phone, String number)1108 private boolean blockCallForwardingNumberWhileRoaming(Phone phone, String number) { 1109 if (phone == null || TextUtils.isEmpty(number) || !phone.getServiceState().getRoaming()) { 1110 return false; 1111 } 1112 String[] blockPrefixes = null; 1113 CarrierConfigManager cfgManager = (CarrierConfigManager) 1114 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1115 if (cfgManager != null) { 1116 blockPrefixes = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray( 1117 CarrierConfigManager.KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY); 1118 } 1119 1120 if (blockPrefixes != null) { 1121 for (String prefix : blockPrefixes) { 1122 if (number.startsWith(prefix)) { 1123 return true; 1124 } 1125 } 1126 } 1127 return false; 1128 } 1129 isRadioOn()1130 private boolean isRadioOn() { 1131 boolean result = false; 1132 for (Phone phone : mPhoneFactoryProxy.getPhones()) { 1133 result |= phone.isRadioOn(); 1134 } 1135 return result; 1136 } 1137 makeCachedConnectionPhonePair( TelephonyConnection c)1138 private Pair<WeakReference<TelephonyConnection>, Queue<Phone>> makeCachedConnectionPhonePair( 1139 TelephonyConnection c) { 1140 Queue<Phone> phones = new LinkedList<>(Arrays.asList(mPhoneFactoryProxy.getPhones())); 1141 return new Pair<>(new WeakReference<>(c), phones); 1142 } 1143 1144 // Update the mEmergencyRetryCache by removing the Phone used to call the last failed emergency 1145 // number and then moving it to the back of the queue if it is not a permanent failure cause 1146 // from the modem. updateCachedConnectionPhonePair(TelephonyConnection c, boolean isPermanentFailure)1147 private void updateCachedConnectionPhonePair(TelephonyConnection c, 1148 boolean isPermanentFailure) { 1149 // No cache exists, create a new one. 1150 if (mEmergencyRetryCache == null) { 1151 Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache"); 1152 mEmergencyRetryCache = makeCachedConnectionPhonePair(c); 1153 // Cache is stale, create a new one with the new TelephonyConnection. 1154 } else if (mEmergencyRetryCache.first.get() != c) { 1155 Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating."); 1156 mEmergencyRetryCache = makeCachedConnectionPhonePair(c); 1157 } 1158 1159 Queue<Phone> cachedPhones = mEmergencyRetryCache.second; 1160 // Need to refer default phone considering ImsPhone because 1161 // cachedPhones is a list that contains default phones. 1162 Phone phoneUsed = c.getPhone().getDefaultPhone(); 1163 if (phoneUsed == null) { 1164 return; 1165 } 1166 // Remove phone used from the list, but for temporary fail cause, it will be added 1167 // back to list further in this method. However in case of permanent failure, the 1168 // phone shouldn't be reused, hence it will not be added back again. 1169 cachedPhones.remove(phoneUsed); 1170 Log.i(this, "updateCachedConnectionPhonePair, isPermanentFailure:" + isPermanentFailure); 1171 if (!isPermanentFailure) { 1172 // In case of temporary failure, add the phone back, this will result adding it 1173 // to tail of list mEmergencyRetryCache.second, giving other phone more 1174 // priority and that is what we want. 1175 cachedPhones.offer(phoneUsed); 1176 } 1177 } 1178 1179 /** 1180 * Updates a cache containing all of the slots that are available for redial at any point. 1181 * 1182 * - If a Connection returns with the disconnect cause EMERGENCY_TEMP_FAILURE, keep that phone 1183 * in the cache, but move it to the lowest priority in the list. Then, place the emergency call 1184 * on the next phone in the list. 1185 * - If a Connection returns with the disconnect cause EMERGENCY_PERM_FAILURE, remove that phone 1186 * from the cache and pull another phone from the cache to place the emergency call. 1187 * 1188 * This will continue until there are no more slots to dial on. 1189 */ 1190 @VisibleForTesting retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure)1191 public void retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure) { 1192 int phoneId = (c.getPhone() == null) ? -1 : c.getPhone().getPhoneId(); 1193 updateCachedConnectionPhonePair(c, isPermanentFailure); 1194 // Pull next phone to use from the cache or null if it is empty 1195 Phone newPhoneToUse = (mEmergencyRetryCache.second != null) 1196 ? mEmergencyRetryCache.second.peek() : null; 1197 if (newPhoneToUse != null) { 1198 int videoState = c.getVideoState(); 1199 Bundle connExtras = c.getExtras(); 1200 Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse); 1201 c.clearOriginalConnection(); 1202 if (phoneId != newPhoneToUse.getPhoneId()) updatePhoneAccount(c, newPhoneToUse); 1203 placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras); 1204 } else { 1205 // We have run out of Phones to use. Disconnect the call and destroy the connection. 1206 Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting."); 1207 c.setDisconnected(new DisconnectCause(DisconnectCause.ERROR)); 1208 c.clearOriginalConnection(); 1209 c.destroy(); 1210 } 1211 } 1212 updatePhoneAccount(TelephonyConnection connection, Phone phone)1213 private void updatePhoneAccount(TelephonyConnection connection, Phone phone) { 1214 PhoneAccountHandle pHandle = PhoneUtils.makePstnPhoneAccountHandle(phone); 1215 // For ECall handling on MSIM, until the request reaches here (i.e PhoneApp), we don't know 1216 // on which phone account ECall can be placed. After deciding, we should notify Telecom of 1217 // the change so that the proper PhoneAccount can be displayed. 1218 Log.i(this, "updatePhoneAccount setPhoneAccountHandle, account = " + pHandle); 1219 connection.setPhoneAccountHandle(pHandle); 1220 } 1221 placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)1222 private void placeOutgoingConnection( 1223 TelephonyConnection connection, Phone phone, ConnectionRequest request) { 1224 placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras()); 1225 } 1226 placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras)1227 private void placeOutgoingConnection( 1228 TelephonyConnection connection, Phone phone, int videoState, Bundle extras) { 1229 String number = connection.getAddress().getSchemeSpecificPart(); 1230 1231 com.android.internal.telephony.Connection originalConnection = null; 1232 try { 1233 if (phone != null) { 1234 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder() 1235 .setVideoState(videoState) 1236 .setIntentExtras(extras) 1237 .setRttTextStream(connection.getRttTextStream()) 1238 .build()); 1239 } 1240 } catch (CallStateException e) { 1241 Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e); 1242 int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 1243 switch (e.getError()) { 1244 case CallStateException.ERROR_OUT_OF_SERVICE: 1245 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE; 1246 break; 1247 case CallStateException.ERROR_POWER_OFF: 1248 cause = android.telephony.DisconnectCause.POWER_OFF; 1249 break; 1250 case CallStateException.ERROR_ALREADY_DIALING: 1251 cause = android.telephony.DisconnectCause.ALREADY_DIALING; 1252 break; 1253 case CallStateException.ERROR_CALL_RINGING: 1254 cause = android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING; 1255 break; 1256 case CallStateException.ERROR_CALLING_DISABLED: 1257 cause = android.telephony.DisconnectCause.CALLING_DISABLED; 1258 break; 1259 case CallStateException.ERROR_TOO_MANY_CALLS: 1260 cause = android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS; 1261 break; 1262 case CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS: 1263 cause = android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS; 1264 break; 1265 } 1266 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1267 cause, e.getMessage(), phone.getPhoneId())); 1268 connection.clearOriginalConnection(); 1269 connection.destroy(); 1270 return; 1271 } 1272 1273 if (originalConnection == null) { 1274 int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 1275 // On GSM phones, null connection means that we dialed an MMI code 1276 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 1277 Log.d(this, "dialed MMI code"); 1278 int subId = phone.getSubId(); 1279 Log.d(this, "subId: "+subId); 1280 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; 1281 final Intent intent = new Intent(this, MMIDialogActivity.class); 1282 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 1283 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 1284 if (SubscriptionManager.isValidSubscriptionId(subId)) { 1285 intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId); 1286 } 1287 startActivity(intent); 1288 } 1289 Log.d(this, "placeOutgoingConnection, phone.dial returned null"); 1290 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 1291 telephonyDisconnectCause, "Connection is null", phone.getPhoneId())); 1292 connection.clearOriginalConnection(); 1293 connection.destroy(); 1294 } else { 1295 connection.setOriginalConnection(originalConnection); 1296 } 1297 } 1298 createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, Uri address, int videoState)1299 private TelephonyConnection createConnectionFor( 1300 Phone phone, 1301 com.android.internal.telephony.Connection originalConnection, 1302 boolean isOutgoing, 1303 PhoneAccountHandle phoneAccountHandle, 1304 String telecomCallId, 1305 Uri address, 1306 int videoState) { 1307 TelephonyConnection returnConnection = null; 1308 int phoneType = phone.getPhoneType(); 1309 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 1310 returnConnection = new GsmConnection(originalConnection, telecomCallId, isOutgoing); 1311 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 1312 boolean allowsMute = allowsMute(phone); 1313 returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer, 1314 allowsMute, isOutgoing, telecomCallId); 1315 } 1316 if (returnConnection != null) { 1317 // Listen to Telephony specific callbacks from the connection 1318 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener); 1319 returnConnection.setVideoPauseSupported( 1320 TelecomAccountRegistry.getInstance(this).isVideoPauseSupported( 1321 phoneAccountHandle)); 1322 returnConnection.setManageImsConferenceCallSupported( 1323 TelecomAccountRegistry.getInstance(this).isManageImsConferenceCallSupported( 1324 phoneAccountHandle)); 1325 returnConnection.setShowPreciseFailedCause( 1326 TelecomAccountRegistry.getInstance(this).isShowPreciseFailedCause( 1327 phoneAccountHandle)); 1328 } 1329 return returnConnection; 1330 } 1331 isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)1332 private boolean isOriginalConnectionKnown( 1333 com.android.internal.telephony.Connection originalConnection) { 1334 for (Connection connection : getAllConnections()) { 1335 if (connection instanceof TelephonyConnection) { 1336 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 1337 if (telephonyConnection.getOriginalConnection() == originalConnection) { 1338 return true; 1339 } 1340 } 1341 } 1342 return false; 1343 } 1344 1345 /** 1346 * Determines which {@link Phone} will be used to place the call. 1347 * @param accountHandle The {@link PhoneAccountHandle} which was sent from Telecom to place the 1348 * call on. 1349 * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise. 1350 * @param emergencyNumberAddress When {@code isEmergency} is {@code true}, will be the phone 1351 * of the emergency call. Otherwise, this can be {@code null} . 1352 * @return 1353 */ getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, @Nullable String emergencyNumberAddress)1354 private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, 1355 @Nullable String emergencyNumberAddress) { 1356 Phone chosenPhone = null; 1357 int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle); 1358 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 1359 int phoneId = mSubscriptionManagerProxy.getPhoneId(subId); 1360 chosenPhone = mPhoneFactoryProxy.getPhone(phoneId); 1361 } 1362 // If this is an emergency call and the phone we originally planned to make this call 1363 // with is not in service or was invalid, try to find one that is in service, using the 1364 // default as a last chance backup. 1365 if (isEmergency && (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone))) { 1366 Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service " 1367 + "or invalid for emergency call.", accountHandle); 1368 chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress); 1369 Log.d(this, "getPhoneForAccount: using subId: " + 1370 (chosenPhone == null ? "null" : chosenPhone.getSubId())); 1371 } 1372 return chosenPhone; 1373 } 1374 delayDialForDdsSwitch(Phone phone)1375 private CompletableFuture<Boolean> delayDialForDdsSwitch(Phone phone) { 1376 if (phone == null) { 1377 return CompletableFuture.completedFuture(Boolean.TRUE); 1378 } 1379 return possiblyOverrideDefaultDataForEmergencyCall(phone) 1380 .completeOnTimeout(false, DEFAULT_DATA_SWITCH_TIMEOUT_MS, 1381 TimeUnit.MILLISECONDS); 1382 } 1383 1384 /** 1385 * If needed, block until Default Data subscription is switched for outgoing emergency call. 1386 * 1387 * In some cases, we need to try to switch the Default Data subscription before placing the 1388 * emergency call on DSDS devices. This includes the following situation: 1389 * - The modem does not support processing GNSS SUPL requests on the non-default data 1390 * subscription. For some carriers that do not provide a control plane fallback mechanism, the 1391 * SUPL request will be dropped and we will not be able to get the user's location for the 1392 * emergency call. In this case, we need to swap default data temporarily. 1393 * @param phone Evaluates whether or not the default data should be moved to the phone 1394 * specified. Should not be null. 1395 */ possiblyOverrideDefaultDataForEmergencyCall( @onNull Phone phone)1396 private CompletableFuture<Boolean> possiblyOverrideDefaultDataForEmergencyCall( 1397 @NonNull Phone phone) { 1398 TelephonyManager telephony = TelephonyManager.from(phone.getContext()); 1399 int phoneCount = telephony.getPhoneCount(); 1400 // Do not override DDS if this is a single SIM device. 1401 if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) { 1402 return CompletableFuture.completedFuture(Boolean.TRUE); 1403 } 1404 1405 CarrierConfigManager cfgManager = (CarrierConfigManager) 1406 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1407 if (cfgManager == null) { 1408 // For some reason CarrierConfigManager is unavailable. Do not block emergency call. 1409 Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: couldn't get" 1410 + "CarrierConfigManager"); 1411 return CompletableFuture.completedFuture(Boolean.TRUE); 1412 } 1413 // Only override default data if we are IN_SERVICE and on a home network. We don't want to 1414 // perform a DDS switch of we are on a roaming network, where SUPL may not be available. 1415 boolean isPhoneAvailableForEmergency = isAvailableForEmergencyCalls(phone); 1416 boolean isRoaming = phone.getServiceState().getVoiceRoaming(); 1417 if (!isPhoneAvailableForEmergency || isRoaming) { 1418 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, avail = " 1419 + isPhoneAvailableForEmergency + ", roaming = " + isRoaming); 1420 return CompletableFuture.completedFuture(Boolean.TRUE); 1421 } 1422 1423 // Do not switch Default data if this device supports emergency SUPL on non-DDS. 1424 final boolean gnssSuplRequiresDefaultData = phone.getContext().getResources().getBoolean( 1425 R.bool.config_gnss_supl_requires_default_data_for_emergency); 1426 if (!gnssSuplRequiresDefaultData) { 1427 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not " 1428 + "require DDS switch."); 1429 return CompletableFuture.completedFuture(Boolean.TRUE); 1430 } 1431 1432 final boolean supportsCpFallback = cfgManager.getConfigForSubId(phone.getSubId()) 1433 .getInt(CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, 1434 CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY) 1435 != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY; 1436 if (supportsCpFallback) { 1437 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier " 1438 + "supports CP fallback."); 1439 // Do not try to swap default data if we support CS fallback, do not want to introduce 1440 // a lag in emergency call setup time if possible. 1441 return CompletableFuture.completedFuture(Boolean.TRUE); 1442 } 1443 1444 // Get extension time, may be 0 for some carriers that support ECBM as well. Use 1445 // CarrierConfig default if format fails. 1446 int extensionTime = 0; 1447 try { 1448 extensionTime = Integer.valueOf(cfgManager.getConfigForSubId(phone.getSubId()) 1449 .getString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0")); 1450 } catch (NumberFormatException e) { 1451 // Just use default. 1452 } 1453 CompletableFuture<Boolean> modemResultFuture = new CompletableFuture<>(); 1454 try { 1455 Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for " 1456 + extensionTime + "seconds"); 1457 PhoneSwitcher.getInstance().overrideDefaultDataForEmergency(phone.getPhoneId(), 1458 extensionTime, modemResultFuture); 1459 // Catch all exceptions, we want to continue with emergency call if possible. 1460 } catch (Exception e) { 1461 Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: exception = " 1462 + e.getMessage()); 1463 } 1464 return modemResultFuture; 1465 } 1466 1467 /** 1468 * Get the Phone to use for an emergency call of the given emergency number address: 1469 * a) If there are multiple Phones with the Subscriptions that support the emergency number 1470 * address, and one of them is the default voice Phone, consider the default voice phone 1471 * if 1.4 HAL is supported, or if it is available for emergency call. 1472 * b) If there are multiple Phones with the Subscriptions that support the emergency number 1473 * address, and none of them is the default voice Phone, use one of these Phones if 1.4 HAL 1474 * is supported, or if it is available for emergency call. 1475 * c) If there is no Phone that supports the emergency call for the address, use the defined 1476 * Priority list to select the Phone via {@link #getFirstPhoneForEmergencyCall}. 1477 */ getPhoneForEmergencyCall(String emergencyNumberAddress)1478 public Phone getPhoneForEmergencyCall(String emergencyNumberAddress) { 1479 // Find the list of available Phones for the given emergency number address 1480 List<Phone> potentialEmergencyPhones = new ArrayList<>(); 1481 int defaultVoicePhoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId(); 1482 for (Phone phone : mPhoneFactoryProxy.getPhones()) { 1483 if (phone.getEmergencyNumberTracker() != null) { 1484 if (phone.getEmergencyNumberTracker().isEmergencyNumber( 1485 emergencyNumberAddress, true)) { 1486 if (phone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4) 1487 || isAvailableForEmergencyCalls(phone)) { 1488 // a) 1489 if (phone.getPhoneId() == defaultVoicePhoneId) { 1490 Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports" 1491 + " emergency number: " + phone.getPhoneId()); 1492 return phone; 1493 } 1494 potentialEmergencyPhones.add(phone); 1495 } 1496 } 1497 } 1498 } 1499 // b) 1500 if (potentialEmergencyPhones.size() > 0) { 1501 Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports emergency number:" 1502 + potentialEmergencyPhones.get(0).getPhoneId()); 1503 return getFirstPhoneForEmergencyCall(potentialEmergencyPhones); 1504 } 1505 // c) 1506 return getFirstPhoneForEmergencyCall(); 1507 } 1508 1509 @VisibleForTesting getFirstPhoneForEmergencyCall()1510 public Phone getFirstPhoneForEmergencyCall() { 1511 return getFirstPhoneForEmergencyCall(null); 1512 } 1513 1514 /** 1515 * Retrieves the most sensible Phone to use for an emergency call using the following Priority 1516 * list (for multi-SIM devices): 1517 * 1) The User's SIM preference for Voice calling 1518 * 2) The First Phone that is currently IN_SERVICE or is available for emergency calling 1519 * 3) Prioritize phones that have the dialed emergency number as part of their emergency 1520 * number list 1521 * 4) If there is a PUK locked SIM, compare the SIMs that are not PUK locked. If all the SIMs 1522 * are locked, skip to condition 5). 1523 * 5) The Phone with more Capabilities. 1524 * 6) The First Phone that has a SIM card in it (Starting from Slot 0...N) 1525 * 7) The Default Phone (Currently set as Slot 0) 1526 */ 1527 @VisibleForTesting getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber)1528 public Phone getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber) { 1529 // 1) 1530 int phoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId(); 1531 if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) { 1532 Phone defaultPhone = mPhoneFactoryProxy.getPhone(phoneId); 1533 if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) { 1534 if (phonesWithEmergencyNumber == null 1535 || phonesWithEmergencyNumber.contains(defaultPhone)) { 1536 return defaultPhone; 1537 } 1538 } 1539 } 1540 1541 Phone firstPhoneWithSim = null; 1542 int phoneCount = mTelephonyManagerProxy.getPhoneCount(); 1543 List<SlotStatus> phoneSlotStatus = new ArrayList<>(phoneCount); 1544 for (int i = 0; i < phoneCount; i++) { 1545 Phone phone = mPhoneFactoryProxy.getPhone(i); 1546 if (phone == null) { 1547 continue; 1548 } 1549 // 2) 1550 if (isAvailableForEmergencyCalls(phone)) { 1551 if (phonesWithEmergencyNumber == null 1552 || phonesWithEmergencyNumber.contains(phone)) { 1553 // the slot has the radio on & state is in service. 1554 Log.i(this, 1555 "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i); 1556 return phone; 1557 } 1558 } 1559 // 5) 1560 // Store the RAF Capabilities for sorting later. 1561 int radioAccessFamily = phone.getRadioAccessFamily(); 1562 SlotStatus status = new SlotStatus(i, radioAccessFamily); 1563 phoneSlotStatus.add(status); 1564 Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" + 1565 Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i); 1566 // 4) 1567 // Report Slot's PIN/PUK lock status for sorting later. 1568 int simState = mSubscriptionManagerProxy.getSimStateForSlotIdx(i); 1569 if (simState == TelephonyManager.SIM_STATE_PIN_REQUIRED || 1570 simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) { 1571 status.isLocked = true; 1572 } 1573 // 3) Store if the Phone has the corresponding emergency number 1574 if (phonesWithEmergencyNumber != null) { 1575 for (Phone phoneWithEmergencyNumber : phonesWithEmergencyNumber) { 1576 if (phoneWithEmergencyNumber != null 1577 && phoneWithEmergencyNumber.getPhoneId() == i) { 1578 status.hasDialedEmergencyNumber = true; 1579 } 1580 } 1581 } 1582 // 6) 1583 if (firstPhoneWithSim == null && mTelephonyManagerProxy.hasIccCard(i)) { 1584 // The slot has a SIM card inserted, but is not in service, so keep track of this 1585 // Phone. Do not return because we want to make sure that none of the other Phones 1586 // are in service (because that is always faster). 1587 firstPhoneWithSim = phone; 1588 Log.i(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" + 1589 firstPhoneWithSim.getPhoneId()); 1590 } 1591 } 1592 // 7) 1593 if (firstPhoneWithSim == null && phoneSlotStatus.isEmpty()) { 1594 if (phonesWithEmergencyNumber == null || phonesWithEmergencyNumber.isEmpty()) { 1595 // No Phones available, get the default 1596 Log.i(this, "getFirstPhoneForEmergencyCall, return default phone"); 1597 return mPhoneFactoryProxy.getDefaultPhone(); 1598 } 1599 return phonesWithEmergencyNumber.get(0); 1600 } else { 1601 // 5) 1602 final int defaultPhoneId = mPhoneFactoryProxy.getDefaultPhone().getPhoneId(); 1603 final Phone firstOccupiedSlot = firstPhoneWithSim; 1604 if (!phoneSlotStatus.isEmpty()) { 1605 // Only sort if there are enough elements to do so. 1606 if (phoneSlotStatus.size() > 1) { 1607 Collections.sort(phoneSlotStatus, (o1, o2) -> { 1608 if (!o1.hasDialedEmergencyNumber && o2.hasDialedEmergencyNumber) { 1609 return -1; 1610 } 1611 if (o1.hasDialedEmergencyNumber && !o2.hasDialedEmergencyNumber) { 1612 return 1; 1613 } 1614 // First start by seeing if either of the phone slots are locked. If they 1615 // are, then sort by non-locked SIM first. If they are both locked, sort 1616 // by capability instead. 1617 if (o1.isLocked && !o2.isLocked) { 1618 return -1; 1619 } 1620 if (o2.isLocked && !o1.isLocked) { 1621 return 1; 1622 } 1623 // sort by number of RadioAccessFamily Capabilities. 1624 int compare = Integer.bitCount(o1.capabilities) - 1625 Integer.bitCount(o2.capabilities); 1626 if (compare == 0) { 1627 // Sort by highest RAF Capability if the number is the same. 1628 compare = RadioAccessFamily.getHighestRafCapability(o1.capabilities) - 1629 RadioAccessFamily.getHighestRafCapability(o2.capabilities); 1630 if (compare == 0) { 1631 if (firstOccupiedSlot != null) { 1632 // If the RAF capability is the same, choose based on whether or 1633 // not any of the slots are occupied with a SIM card (if both 1634 // are, always choose the first). 1635 if (o1.slotId == firstOccupiedSlot.getPhoneId()) { 1636 return 1; 1637 } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) { 1638 return -1; 1639 } 1640 } else { 1641 // No slots have SIMs detected in them, so weight the default 1642 // Phone Id greater than the others. 1643 if (o1.slotId == defaultPhoneId) { 1644 return 1; 1645 } else if (o2.slotId == defaultPhoneId) { 1646 return -1; 1647 } 1648 } 1649 } 1650 } 1651 return compare; 1652 }); 1653 } 1654 int mostCapablePhoneId = phoneSlotStatus.get(phoneSlotStatus.size() - 1).slotId; 1655 Log.i(this, "getFirstPhoneForEmergencyCall, Using Phone Id: " + mostCapablePhoneId + 1656 "with highest capability"); 1657 return mPhoneFactoryProxy.getPhone(mostCapablePhoneId); 1658 } else { 1659 // 6) 1660 return firstPhoneWithSim; 1661 } 1662 } 1663 } 1664 1665 /** 1666 * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only. 1667 */ isAvailableForEmergencyCalls(Phone phone)1668 private boolean isAvailableForEmergencyCalls(Phone phone) { 1669 return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() || 1670 phone.getServiceState().isEmergencyOnly(); 1671 } 1672 1673 /** 1674 * Determines if the connection should allow mute. 1675 * 1676 * @param phone The current phone. 1677 * @return {@code True} if the connection should allow mute. 1678 */ allowsMute(Phone phone)1679 private boolean allowsMute(Phone phone) { 1680 // For CDMA phones, check if we are in Emergency Callback Mode (ECM). Mute is disallowed 1681 // in ECM mode. 1682 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 1683 if (phone.isInEcm()) { 1684 return false; 1685 } 1686 } 1687 1688 return true; 1689 } 1690 1691 @Override removeConnection(Connection connection)1692 public void removeConnection(Connection connection) { 1693 super.removeConnection(connection); 1694 if (connection instanceof TelephonyConnection) { 1695 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 1696 telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener); 1697 } 1698 } 1699 1700 /** 1701 * When a {@link TelephonyConnection} has its underlying original connection configured, 1702 * we need to add it to the correct conference controller. 1703 * 1704 * @param connection The connection to be added to the controller 1705 */ addConnectionToConferenceController(TelephonyConnection connection)1706 public void addConnectionToConferenceController(TelephonyConnection connection) { 1707 // TODO: Need to revisit what happens when the original connection for the 1708 // TelephonyConnection changes. If going from CDMA --> GSM (for example), the 1709 // instance of TelephonyConnection will still be a CdmaConnection, not a GsmConnection. 1710 // The CDMA conference controller makes the assumption that it will only have CDMA 1711 // connections in it, while the other conference controllers aren't as restrictive. Really, 1712 // when we go between CDMA and GSM we should replace the TelephonyConnection. 1713 if (connection.isImsConnection()) { 1714 Log.d(this, "Adding IMS connection to conference controller: " + connection); 1715 mImsConferenceController.add(connection); 1716 mTelephonyConferenceController.remove(connection); 1717 if (connection instanceof CdmaConnection) { 1718 mCdmaConferenceController.remove((CdmaConnection) connection); 1719 } 1720 } else { 1721 int phoneType = connection.getCall().getPhone().getPhoneType(); 1722 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 1723 Log.d(this, "Adding GSM connection to conference controller: " + connection); 1724 mTelephonyConferenceController.add(connection); 1725 if (connection instanceof CdmaConnection) { 1726 mCdmaConferenceController.remove((CdmaConnection) connection); 1727 } 1728 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA && 1729 connection instanceof CdmaConnection) { 1730 Log.d(this, "Adding CDMA connection to conference controller: " + connection); 1731 mCdmaConferenceController.add((CdmaConnection) connection); 1732 mTelephonyConferenceController.remove(connection); 1733 } 1734 Log.d(this, "Removing connection from IMS conference controller: " + connection); 1735 mImsConferenceController.remove(connection); 1736 } 1737 } 1738 1739 /** 1740 * Create a new CDMA connection. CDMA connections have additional limitations when creating 1741 * additional calls which are handled in this method. Specifically, CDMA has a "FLASH" command 1742 * that can be used for three purposes: merging a call, swapping unmerged calls, and adding 1743 * a new outgoing call. The function of the flash command depends on the context of the current 1744 * set of calls. This method will prevent an outgoing call from being made if it is not within 1745 * the right circumstances to support adding a call. 1746 */ checkAdditionalOutgoingCallLimits(Phone phone)1747 private Connection checkAdditionalOutgoingCallLimits(Phone phone) { 1748 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 1749 // Check to see if any CDMA conference calls exist, and if they do, check them for 1750 // limitations. 1751 for (Conference conference : getAllConferences()) { 1752 if (conference instanceof CdmaConference) { 1753 CdmaConference cdmaConf = (CdmaConference) conference; 1754 1755 // If the CDMA conference has not been merged, add-call will not work, so fail 1756 // this request to add a call. 1757 if (cdmaConf.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 1758 return Connection.createFailedConnection(new DisconnectCause( 1759 DisconnectCause.RESTRICTED, 1760 null, 1761 getResources().getString(R.string.callFailed_cdma_call_limit), 1762 "merge-capable call exists, prevent flash command.")); 1763 } 1764 } 1765 } 1766 } 1767 1768 return null; // null means nothing went wrong, and call should continue. 1769 } 1770 isTtyModeEnabled(Context context)1771 private boolean isTtyModeEnabled(Context context) { 1772 return (android.provider.Settings.Secure.getInt( 1773 context.getContentResolver(), 1774 android.provider.Settings.Secure.PREFERRED_TTY_MODE, 1775 TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF); 1776 } 1777 1778 /** 1779 * For outgoing dialed calls, potentially send a ConnectionEvent if the user is on WFC and is 1780 * dialing an international number. 1781 * @param telephonyConnection The connection. 1782 */ maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection)1783 private void maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection) { 1784 if (telephonyConnection == null || telephonyConnection.getPhone() == null || 1785 telephonyConnection.getPhone().getDefaultPhone() == null) { 1786 return; 1787 } 1788 Phone phone = telephonyConnection.getPhone().getDefaultPhone(); 1789 if (phone instanceof GsmCdmaPhone) { 1790 GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone; 1791 if (telephonyConnection.isOutgoingCall() && 1792 gsmCdmaPhone.isNotificationOfWfcCallRequired( 1793 telephonyConnection.getOriginalConnection().getOrigDialString())) { 1794 // Send connection event to InCall UI to inform the user of the fact they 1795 // are potentially placing an international call on WFC. 1796 Log.i(this, "placeOutgoingConnection - sending international call on WFC " + 1797 "confirmation event"); 1798 telephonyConnection.sendConnectionEvent( 1799 TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null); 1800 } 1801 } 1802 } 1803 handleTtyModeChange(boolean isTtyEnabled)1804 private void handleTtyModeChange(boolean isTtyEnabled) { 1805 Log.i(this, "handleTtyModeChange; isTtyEnabled=%b", isTtyEnabled); 1806 mIsTtyEnabled = isTtyEnabled; 1807 for (Connection connection : getAllConnections()) { 1808 if (connection instanceof TelephonyConnection) { 1809 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 1810 telephonyConnection.setTtyEnabled(isTtyEnabled); 1811 } 1812 } 1813 } 1814 } 1815