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