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 SlotStatus(int slotId, int capabilities)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 { getDefaultVoicePhoneId()149 int getDefaultVoicePhoneId(); getSimStateForSlotIdx(int slotId)150 int getSimStateForSlotIdx(int slotId); getPhoneId(int subId)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 { getPhoneCount()173 int getPhoneCount(); hasIccCard(int slotId)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 { getPhone(int index)193 Phone getPhone(int index); getDefaultPhone()194 Phone getDefaultPhone(); getPhones()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 setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)216 public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) { 217 mSubscriptionManagerProxy = proxy; 218 } 219 220 @VisibleForTesting setTelephonyManagerProxy(TelephonyManagerProxy proxy)221 public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) { 222 mTelephonyManagerProxy = proxy; 223 } 224 225 @VisibleForTesting setPhoneFactoryProxy(PhoneFactoryProxy proxy)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 onCreate()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 onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)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 */ canAddCall()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 getTelephonyConnection(final ConnectionRequest request, final String number, boolean isEmergencyNumber, final Uri handle, Phone phone)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 onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)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 onCreateConnectionComplete(Connection connection)670 public void onCreateConnectionComplete(Connection connection) { 671 if (connection instanceof TelephonyConnection) { 672 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 673 maybeSendInternationalCallEvent(telephonyConnection); 674 } 675 } 676 677 @Override triggerConferenceRecalculate()678 public void triggerConferenceRecalculate() { 679 if (mTelephonyConferenceController.shouldRecalculate()) { 680 mTelephonyConferenceController.recalculate(); 681 } 682 } 683 684 @Override onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)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 onConference(Connection connection1, Connection connection2)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 blockCallForwardingNumberWhileRoaming(Phone phone, String number)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 isRadioOn()838 private boolean isRadioOn() { 839 boolean result = false; 840 for (Phone phone : mPhoneFactoryProxy.getPhones()) { 841 result |= phone.isRadioOn(); 842 } 843 return result; 844 } 845 makeCachedConnectionPhonePair( TelephonyConnection c)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! updateCachedConnectionPhonePair(TelephonyConnection c)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 */ getPhoneForRedial(Phone phoneToExclude)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 retryOutgoingOriginalConnection(TelephonyConnection c)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 placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)904 private void placeOutgoingConnection( 905 TelephonyConnection connection, Phone phone, ConnectionRequest request) { 906 placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras()); 907 } 908 placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras)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 createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, Uri address, int videoState)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 isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)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 getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency)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 getFirstPhoneForEmergencyCall()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 */ isAvailableForEmergencyCalls(Phone phone)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 */ allowsMute(Phone phone)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 removeConnection(Connection connection)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 */ addConnectionToConferenceController(TelephonyConnection connection)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 */ checkAdditionalOutgoingCallLimits(Phone phone)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 isTtyModeEnabled(Context context)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 */ maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection)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