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