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