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.telecom.Conference; 26 import android.telecom.Connection; 27 import android.telecom.ConnectionRequest; 28 import android.telecom.ConnectionService; 29 import android.telecom.DisconnectCause; 30 import android.telecom.PhoneAccount; 31 import android.telecom.PhoneAccountHandle; 32 import android.telecom.TelecomManager; 33 import android.telecom.VideoProfile; 34 import android.telephony.CarrierConfigManager; 35 import android.telephony.PhoneNumberUtils; 36 import android.telephony.ServiceState; 37 import android.telephony.SubscriptionManager; 38 import android.telephony.TelephonyManager; 39 import android.text.TextUtils; 40 41 import com.android.internal.telephony.Call; 42 import com.android.internal.telephony.CallStateException; 43 import com.android.internal.telephony.IccCard; 44 import com.android.internal.telephony.IccCardConstants; 45 import com.android.internal.telephony.Phone; 46 import com.android.internal.telephony.PhoneConstants; 47 import com.android.internal.telephony.PhoneFactory; 48 import com.android.internal.telephony.SubscriptionController; 49 import com.android.internal.telephony.imsphone.ImsExternalCallTracker; 50 import com.android.internal.telephony.imsphone.ImsPhone; 51 import com.android.phone.MMIDialogActivity; 52 import com.android.phone.PhoneUtils; 53 import com.android.phone.R; 54 55 import java.util.ArrayList; 56 import java.util.List; 57 import java.util.regex.Pattern; 58 59 /** 60 * Service for making GSM and CDMA connections. 61 */ 62 public class TelephonyConnectionService extends ConnectionService { 63 64 // If configured, reject attempts to dial numbers matching this pattern. 65 private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN = 66 Pattern.compile("\\*228[0-9]{0,2}"); 67 68 private final TelephonyConferenceController mTelephonyConferenceController = 69 new TelephonyConferenceController(this); 70 private final CdmaConferenceController mCdmaConferenceController = 71 new CdmaConferenceController(this); 72 private final ImsConferenceController mImsConferenceController = 73 new ImsConferenceController(this); 74 75 private ComponentName mExpectedComponentName = null; 76 private EmergencyCallHelper mEmergencyCallHelper; 77 private EmergencyTonePlayer mEmergencyTonePlayer; 78 79 /** 80 * A listener to actionable events specific to the TelephonyConnection. 81 */ 82 private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener = 83 new TelephonyConnection.TelephonyConnectionListener() { 84 @Override 85 public void onOriginalConnectionConfigured(TelephonyConnection c) { 86 addConnectionToConferenceController(c); 87 } 88 }; 89 90 @Override onCreate()91 public void onCreate() { 92 super.onCreate(); 93 mExpectedComponentName = new ComponentName(this, this.getClass()); 94 mEmergencyTonePlayer = new EmergencyTonePlayer(this); 95 TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this); 96 } 97 98 @Override onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)99 public Connection onCreateOutgoingConnection( 100 PhoneAccountHandle connectionManagerPhoneAccount, 101 final ConnectionRequest request) { 102 Log.i(this, "onCreateOutgoingConnection, request: " + request); 103 104 Uri handle = request.getAddress(); 105 if (handle == null) { 106 Log.d(this, "onCreateOutgoingConnection, handle is null"); 107 return Connection.createFailedConnection( 108 DisconnectCauseUtil.toTelecomDisconnectCause( 109 android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, 110 "No phone number supplied")); 111 } 112 113 String scheme = handle.getScheme(); 114 final String number; 115 if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) { 116 // TODO: We don't check for SecurityException here (requires 117 // CALL_PRIVILEGED permission). 118 final Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 119 if (phone == null) { 120 Log.d(this, "onCreateOutgoingConnection, phone is null"); 121 return Connection.createFailedConnection( 122 DisconnectCauseUtil.toTelecomDisconnectCause( 123 android.telephony.DisconnectCause.OUT_OF_SERVICE, 124 "Phone is null")); 125 } 126 number = phone.getVoiceMailNumber(); 127 if (TextUtils.isEmpty(number)) { 128 Log.d(this, "onCreateOutgoingConnection, no voicemail number set."); 129 return Connection.createFailedConnection( 130 DisconnectCauseUtil.toTelecomDisconnectCause( 131 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING, 132 "Voicemail scheme provided but no voicemail number set.")); 133 } 134 135 // Convert voicemail: to tel: 136 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 137 } else { 138 if (!PhoneAccount.SCHEME_TEL.equals(scheme)) { 139 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme); 140 return Connection.createFailedConnection( 141 DisconnectCauseUtil.toTelecomDisconnectCause( 142 android.telephony.DisconnectCause.INVALID_NUMBER, 143 "Handle scheme is not type tel")); 144 } 145 146 number = handle.getSchemeSpecificPart(); 147 if (TextUtils.isEmpty(number)) { 148 Log.d(this, "onCreateOutgoingConnection, unable to parse number"); 149 return Connection.createFailedConnection( 150 DisconnectCauseUtil.toTelecomDisconnectCause( 151 android.telephony.DisconnectCause.INVALID_NUMBER, 152 "Unable to parse number")); 153 } 154 155 final Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 156 if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) { 157 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number 158 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and 159 // when dialed could lock LTE SIMs to 3G if not prohibited.. 160 boolean disableActivation = false; 161 CarrierConfigManager cfgManager = (CarrierConfigManager) 162 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 163 if (cfgManager != null) { 164 disableActivation = cfgManager.getConfigForSubId(phone.getSubId()) 165 .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL); 166 } 167 168 if (disableActivation) { 169 return Connection.createFailedConnection( 170 DisconnectCauseUtil.toTelecomDisconnectCause( 171 android.telephony.DisconnectCause 172 .CDMA_ALREADY_ACTIVATED, 173 "Tried to dial *228")); 174 } 175 } 176 } 177 178 boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(this, number); 179 180 // Get the right phone object from the account data passed in. 181 final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber); 182 if (phone == null) { 183 final Context context = getApplicationContext(); 184 if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) { 185 // Check SIM card state before the outgoing call. 186 // Start the SIM unlock activity if PIN_REQUIRED. 187 final Phone defaultPhone = PhoneFactory.getDefaultPhone(); 188 final IccCard icc = defaultPhone.getIccCard(); 189 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN; 190 if (icc != null) { 191 simState = icc.getState(); 192 } 193 if (simState == IccCardConstants.State.PIN_REQUIRED) { 194 final String simUnlockUiPackage = context.getResources().getString( 195 R.string.config_simUnlockUiPackage); 196 final String simUnlockUiClass = context.getResources().getString( 197 R.string.config_simUnlockUiClass); 198 if (simUnlockUiPackage != null && simUnlockUiClass != null) { 199 Intent simUnlockIntent = new Intent().setComponent(new ComponentName( 200 simUnlockUiPackage, simUnlockUiClass)); 201 simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 202 try { 203 context.startActivity(simUnlockIntent); 204 } catch (ActivityNotFoundException exception) { 205 Log.e(this, exception, "Unable to find SIM unlock UI activity."); 206 } 207 } 208 return Connection.createFailedConnection( 209 DisconnectCauseUtil.toTelecomDisconnectCause( 210 android.telephony.DisconnectCause.OUT_OF_SERVICE, 211 "SIM_STATE_PIN_REQUIRED")); 212 } 213 } 214 215 Log.d(this, "onCreateOutgoingConnection, phone is null"); 216 return Connection.createFailedConnection( 217 DisconnectCauseUtil.toTelecomDisconnectCause( 218 android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null")); 219 } 220 221 // Check both voice & data RAT to enable normal CS call, 222 // when voice RAT is OOS but Data RAT is present. 223 int state = phone.getServiceState().getState(); 224 if (state == ServiceState.STATE_OUT_OF_SERVICE) { 225 if (phone.getServiceState().getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE) { 226 state = phone.getServiceState().getDataRegState(); 227 } 228 } 229 boolean useEmergencyCallHelper = false; 230 231 // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if 232 // carrier configuration specifies that we cannot make non-emergency calls in ECM mode. 233 if (!isEmergencyNumber && phone.isInEcm()) { 234 boolean allowNonEmergencyCalls = true; 235 CarrierConfigManager cfgManager = (CarrierConfigManager) 236 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 237 if (cfgManager != null) { 238 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId()) 239 .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL); 240 } 241 242 if (!allowNonEmergencyCalls) { 243 return Connection.createFailedConnection( 244 DisconnectCauseUtil.toTelecomDisconnectCause( 245 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY, 246 "Cannot make non-emergency call in ECM mode." 247 )); 248 } 249 } 250 251 if (isEmergencyNumber) { 252 if (!phone.isRadioOn()) { 253 useEmergencyCallHelper = true; 254 } 255 } else { 256 switch (state) { 257 case ServiceState.STATE_IN_SERVICE: 258 case ServiceState.STATE_EMERGENCY_ONLY: 259 break; 260 case ServiceState.STATE_OUT_OF_SERVICE: 261 if (phone.isUtEnabled() && number.endsWith("#")) { 262 Log.d(this, "onCreateOutgoingConnection dial for UT"); 263 break; 264 } else { 265 return Connection.createFailedConnection( 266 DisconnectCauseUtil.toTelecomDisconnectCause( 267 android.telephony.DisconnectCause.OUT_OF_SERVICE, 268 "ServiceState.STATE_OUT_OF_SERVICE")); 269 } 270 case ServiceState.STATE_POWER_OFF: 271 return Connection.createFailedConnection( 272 DisconnectCauseUtil.toTelecomDisconnectCause( 273 android.telephony.DisconnectCause.POWER_OFF, 274 "ServiceState.STATE_POWER_OFF")); 275 default: 276 Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state); 277 return Connection.createFailedConnection( 278 DisconnectCauseUtil.toTelecomDisconnectCause( 279 android.telephony.DisconnectCause.OUTGOING_FAILURE, 280 "Unknown service state " + state)); 281 } 282 } 283 284 final Context context = getApplicationContext(); 285 if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled(context) && 286 !isEmergencyNumber) { 287 return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause( 288 android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED)); 289 } 290 291 // Check for additional limits on CDMA phones. 292 final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone); 293 if (failedConnection != null) { 294 return failedConnection; 295 } 296 297 final TelephonyConnection connection = 298 createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(), 299 request.getTelecomCallId(), request.getAddress()); 300 if (connection == null) { 301 return Connection.createFailedConnection( 302 DisconnectCauseUtil.toTelecomDisconnectCause( 303 android.telephony.DisconnectCause.OUTGOING_FAILURE, 304 "Invalid phone type")); 305 } 306 connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED); 307 connection.setInitializing(); 308 connection.setVideoState(request.getVideoState()); 309 310 if (useEmergencyCallHelper) { 311 if (mEmergencyCallHelper == null) { 312 mEmergencyCallHelper = new EmergencyCallHelper(this); 313 } 314 mEmergencyCallHelper.startTurnOnRadioSequence(phone, 315 new EmergencyCallHelper.Callback() { 316 @Override 317 public void onComplete(boolean isRadioReady) { 318 if (connection.getState() == Connection.STATE_DISCONNECTED) { 319 // If the connection has already been disconnected, do nothing. 320 } else if (isRadioReady) { 321 connection.setInitialized(); 322 placeOutgoingConnection(connection, phone, request); 323 } else { 324 Log.d(this, "onCreateOutgoingConnection, failed to turn on radio"); 325 connection.setDisconnected( 326 DisconnectCauseUtil.toTelecomDisconnectCause( 327 android.telephony.DisconnectCause.POWER_OFF, 328 "Failed to turn on radio.")); 329 connection.destroy(); 330 } 331 } 332 }); 333 334 } else { 335 placeOutgoingConnection(connection, phone, request); 336 } 337 338 return connection; 339 } 340 341 @Override onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)342 public Connection onCreateIncomingConnection( 343 PhoneAccountHandle connectionManagerPhoneAccount, 344 ConnectionRequest request) { 345 Log.i(this, "onCreateIncomingConnection, request: " + request); 346 // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM), 347 // make sure the PhoneAccount lookup retrieves the default Emergency Phone. 348 PhoneAccountHandle accountHandle = request.getAccountHandle(); 349 boolean isEmergency = false; 350 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 351 accountHandle.getId())) { 352 Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " + 353 "Treat as an Emergency Call."); 354 isEmergency = true; 355 } 356 Phone phone = getPhoneForAccount(accountHandle, isEmergency); 357 if (phone == null) { 358 return Connection.createFailedConnection( 359 DisconnectCauseUtil.toTelecomDisconnectCause( 360 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 361 "Phone is null")); 362 } 363 364 Call call = phone.getRingingCall(); 365 if (!call.getState().isRinging()) { 366 Log.i(this, "onCreateIncomingConnection, no ringing call"); 367 return Connection.createFailedConnection( 368 DisconnectCauseUtil.toTelecomDisconnectCause( 369 android.telephony.DisconnectCause.INCOMING_MISSED, 370 "Found no ringing call")); 371 } 372 373 com.android.internal.telephony.Connection originalConnection = 374 call.getState() == Call.State.WAITING ? 375 call.getLatestConnection() : call.getEarliestConnection(); 376 if (isOriginalConnectionKnown(originalConnection)) { 377 Log.i(this, "onCreateIncomingConnection, original connection already registered"); 378 return Connection.createCanceledConnection(); 379 } 380 381 Connection connection = 382 createConnectionFor(phone, originalConnection, false /* isOutgoing */, 383 request.getAccountHandle(), request.getTelecomCallId(), 384 request.getAddress()); 385 if (connection == null) { 386 return Connection.createCanceledConnection(); 387 } else { 388 return connection; 389 } 390 } 391 392 @Override triggerConferenceRecalculate()393 public void triggerConferenceRecalculate() { 394 if (mTelephonyConferenceController.shouldRecalculate()) { 395 mTelephonyConferenceController.recalculate(); 396 } 397 } 398 399 @Override onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)400 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 401 ConnectionRequest request) { 402 Log.i(this, "onCreateUnknownConnection, request: " + request); 403 // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's 404 // Emergency PhoneAccount 405 PhoneAccountHandle accountHandle = request.getAccountHandle(); 406 boolean isEmergency = false; 407 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 408 accountHandle.getId())) { 409 Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " + 410 "Treat as an Emergency Call."); 411 isEmergency = true; 412 } 413 Phone phone = getPhoneForAccount(accountHandle, isEmergency); 414 if (phone == null) { 415 return Connection.createFailedConnection( 416 DisconnectCauseUtil.toTelecomDisconnectCause( 417 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 418 "Phone is null")); 419 } 420 Bundle extras = request.getExtras(); 421 422 final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>(); 423 424 // Handle the case where an unknown connection has an IMS external call ID specified; we can 425 // skip the rest of the guesswork and just grad that unknown call now. 426 if (phone.getImsPhone() != null && extras != null && 427 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) { 428 429 ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); 430 ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker(); 431 int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, 432 -1); 433 434 if (externalCallTracker != null) { 435 com.android.internal.telephony.Connection connection = 436 externalCallTracker.getConnectionById(externalCallId); 437 438 if (connection != null) { 439 allConnections.add(connection); 440 } 441 } 442 } 443 444 if (allConnections.isEmpty()) { 445 final Call ringingCall = phone.getRingingCall(); 446 if (ringingCall.hasConnections()) { 447 allConnections.addAll(ringingCall.getConnections()); 448 } 449 final Call foregroundCall = phone.getForegroundCall(); 450 if ((foregroundCall.getState() != Call.State.DISCONNECTED) 451 && (foregroundCall.hasConnections())) { 452 allConnections.addAll(foregroundCall.getConnections()); 453 } 454 if (phone.getImsPhone() != null) { 455 final Call imsFgCall = phone.getImsPhone().getForegroundCall(); 456 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall 457 .hasConnections()) { 458 allConnections.addAll(imsFgCall.getConnections()); 459 } 460 } 461 final Call backgroundCall = phone.getBackgroundCall(); 462 if (backgroundCall.hasConnections()) { 463 allConnections.addAll(phone.getBackgroundCall().getConnections()); 464 } 465 } 466 467 com.android.internal.telephony.Connection unknownConnection = null; 468 for (com.android.internal.telephony.Connection telephonyConnection : allConnections) { 469 if (!isOriginalConnectionKnown(telephonyConnection)) { 470 unknownConnection = telephonyConnection; 471 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection); 472 break; 473 } 474 } 475 476 if (unknownConnection == null) { 477 Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection."); 478 return Connection.createCanceledConnection(); 479 } 480 481 TelephonyConnection connection = 482 createConnectionFor(phone, unknownConnection, 483 !unknownConnection.isIncoming() /* isOutgoing */, 484 request.getAccountHandle(), request.getTelecomCallId(), 485 request.getAddress()); 486 487 if (connection == null) { 488 return Connection.createCanceledConnection(); 489 } else { 490 connection.updateState(); 491 return connection; 492 } 493 } 494 495 @Override onConference(Connection connection1, Connection connection2)496 public void onConference(Connection connection1, Connection connection2) { 497 if (connection1 instanceof TelephonyConnection && 498 connection2 instanceof TelephonyConnection) { 499 ((TelephonyConnection) connection1).performConference( 500 (TelephonyConnection) connection2); 501 } 502 503 } 504 placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)505 private void placeOutgoingConnection( 506 TelephonyConnection connection, Phone phone, ConnectionRequest request) { 507 String number = connection.getAddress().getSchemeSpecificPart(); 508 509 com.android.internal.telephony.Connection originalConnection; 510 try { 511 originalConnection = 512 phone.dial(number, null, request.getVideoState(), request.getExtras()); 513 } catch (CallStateException e) { 514 Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e); 515 int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 516 if (e.getError() == CallStateException.ERROR_DISCONNECTED) { 517 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE; 518 } 519 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 520 cause, e.getMessage())); 521 return; 522 } 523 524 if (originalConnection == null) { 525 int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 526 // On GSM phones, null connection means that we dialed an MMI code 527 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 528 Log.d(this, "dialed MMI code"); 529 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; 530 final Intent intent = new Intent(this, MMIDialogActivity.class); 531 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 532 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 533 startActivity(intent); 534 } 535 Log.d(this, "placeOutgoingConnection, phone.dial returned null"); 536 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 537 telephonyDisconnectCause, "Connection is null")); 538 } else { 539 connection.setOriginalConnection(originalConnection); 540 } 541 } 542 createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, Uri address)543 private TelephonyConnection createConnectionFor( 544 Phone phone, 545 com.android.internal.telephony.Connection originalConnection, 546 boolean isOutgoing, 547 PhoneAccountHandle phoneAccountHandle, 548 String telecomCallId, 549 Uri address) { 550 TelephonyConnection returnConnection = null; 551 int phoneType = phone.getPhoneType(); 552 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 553 returnConnection = new GsmConnection(originalConnection, telecomCallId); 554 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 555 boolean allowsMute = allowsMute(phone); 556 returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer, 557 allowsMute, isOutgoing, telecomCallId); 558 } 559 if (returnConnection != null) { 560 // Listen to Telephony specific callbacks from the connection 561 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener); 562 returnConnection.setVideoPauseSupported( 563 TelecomAccountRegistry.getInstance(this).isVideoPauseSupported( 564 phoneAccountHandle)); 565 boolean isEmergencyCall = (address != null && PhoneNumberUtils.isEmergencyNumber( 566 address.getSchemeSpecificPart())); 567 returnConnection.setConferenceSupported(!isEmergencyCall 568 && TelecomAccountRegistry.getInstance(this).isMergeCallSupported( 569 phoneAccountHandle)); 570 } 571 return returnConnection; 572 } 573 isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)574 private boolean isOriginalConnectionKnown( 575 com.android.internal.telephony.Connection originalConnection) { 576 for (Connection connection : getAllConnections()) { 577 if (connection instanceof TelephonyConnection) { 578 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 579 if (telephonyConnection.getOriginalConnection() == originalConnection) { 580 return true; 581 } 582 } 583 } 584 return false; 585 } 586 getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency)587 private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) { 588 Phone chosenPhone = null; 589 int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle); 590 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 591 int phoneId = SubscriptionController.getInstance().getPhoneId(subId); 592 chosenPhone = PhoneFactory.getPhone(phoneId); 593 } 594 // If this is an emergency call and the phone we originally planned to make this call 595 // with is not in service or was invalid, try to find one that is in service, using the 596 // default as a last chance backup. 597 if (isEmergency && (chosenPhone == null || ServiceState.STATE_IN_SERVICE != chosenPhone 598 .getServiceState().getState())) { 599 Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service " 600 + "or invalid for emergency call.", accountHandle); 601 chosenPhone = getFirstPhoneForEmergencyCall(); 602 Log.d(this, "getPhoneForAccount: using subId: " + 603 (chosenPhone == null ? "null" : chosenPhone.getSubId())); 604 } 605 return chosenPhone; 606 } 607 608 /** 609 * Retrieves the most sensible Phone to use for an emergency call using the following Priority 610 * list (for multi-SIM devices): 611 * 1) The User's SIM preference for Voice calling 612 * 2) The First Phone that is currently IN_SERVICE or is available for emergency calling 613 * 3) The First Phone that has a SIM card in it (Starting from Slot 0...N) 614 * 4) The Default Phone (Currently set as Slot 0) 615 */ getFirstPhoneForEmergencyCall()616 private Phone getFirstPhoneForEmergencyCall() { 617 Phone firstPhoneWithSim = null; 618 619 // 1) 620 int phoneId = SubscriptionManager.getDefaultVoicePhoneId(); 621 if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) { 622 Phone defaultPhone = PhoneFactory.getPhone(phoneId); 623 if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) { 624 return defaultPhone; 625 } 626 } 627 628 for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) { 629 Phone phone = PhoneFactory.getPhone(i); 630 if (phone == null) 631 continue; 632 // 2) 633 if (isAvailableForEmergencyCalls(phone)) { 634 // the slot has the radio on & state is in service. 635 Log.d(this, "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i); 636 return phone; 637 } 638 // 3) 639 if (firstPhoneWithSim == null && TelephonyManager.getDefault().hasIccCard(i)) { 640 // The slot has a SIM card inserted, but is not in service, so keep track of this 641 // Phone. Do not return because we want to make sure that none of the other Phones 642 // are in service (because that is always faster). 643 Log.d(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" + i); 644 firstPhoneWithSim = phone; 645 } 646 } 647 // 4) 648 if (firstPhoneWithSim == null) { 649 // No SIMs inserted, get the default. 650 Log.d(this, "getFirstPhoneForEmergencyCall, return default phone"); 651 return PhoneFactory.getDefaultPhone(); 652 } else { 653 return firstPhoneWithSim; 654 } 655 } 656 657 /** 658 * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only. 659 */ isAvailableForEmergencyCalls(Phone phone)660 private boolean isAvailableForEmergencyCalls(Phone phone) { 661 return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() || 662 phone.getServiceState().isEmergencyOnly(); 663 } 664 665 /** 666 * Determines if the connection should allow mute. 667 * 668 * @param phone The current phone. 669 * @return {@code True} if the connection should allow mute. 670 */ allowsMute(Phone phone)671 private boolean allowsMute(Phone phone) { 672 // For CDMA phones, check if we are in Emergency Callback Mode (ECM). Mute is disallowed 673 // in ECM mode. 674 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 675 if (phone.isInEcm()) { 676 return false; 677 } 678 } 679 680 return true; 681 } 682 683 @Override removeConnection(Connection connection)684 public void removeConnection(Connection connection) { 685 super.removeConnection(connection); 686 if (connection instanceof TelephonyConnection) { 687 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 688 telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener); 689 } 690 } 691 692 /** 693 * When a {@link TelephonyConnection} has its underlying original connection configured, 694 * we need to add it to the correct conference controller. 695 * 696 * @param connection The connection to be added to the controller 697 */ addConnectionToConferenceController(TelephonyConnection connection)698 public void addConnectionToConferenceController(TelephonyConnection connection) { 699 // TODO: Do we need to handle the case of the original connection changing 700 // and triggering this callback multiple times for the same connection? 701 // If that is the case, we might want to remove this connection from all 702 // conference controllers first before re-adding it. 703 if (connection.isImsConnection()) { 704 Log.d(this, "Adding IMS connection to conference controller: " + connection); 705 mImsConferenceController.add(connection); 706 } else { 707 int phoneType = connection.getCall().getPhone().getPhoneType(); 708 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 709 Log.d(this, "Adding GSM connection to conference controller: " + connection); 710 mTelephonyConferenceController.add(connection); 711 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA && 712 connection instanceof CdmaConnection) { 713 Log.d(this, "Adding CDMA connection to conference controller: " + connection); 714 mCdmaConferenceController.add((CdmaConnection)connection); 715 } 716 Log.d(this, "Removing connection from IMS conference controller: " + connection); 717 mImsConferenceController.remove(connection); 718 } 719 } 720 721 /** 722 * Create a new CDMA connection. CDMA connections have additional limitations when creating 723 * additional calls which are handled in this method. Specifically, CDMA has a "FLASH" command 724 * that can be used for three purposes: merging a call, swapping unmerged calls, and adding 725 * a new outgoing call. The function of the flash command depends on the context of the current 726 * set of calls. This method will prevent an outgoing call from being made if it is not within 727 * the right circumstances to support adding a call. 728 */ checkAdditionalOutgoingCallLimits(Phone phone)729 private Connection checkAdditionalOutgoingCallLimits(Phone phone) { 730 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 731 // Check to see if any CDMA conference calls exist, and if they do, check them for 732 // limitations. 733 for (Conference conference : getAllConferences()) { 734 if (conference instanceof CdmaConference) { 735 CdmaConference cdmaConf = (CdmaConference) conference; 736 737 // If the CDMA conference has not been merged, add-call will not work, so fail 738 // this request to add a call. 739 if (cdmaConf.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 740 return Connection.createFailedConnection(new DisconnectCause( 741 DisconnectCause.RESTRICTED, 742 null, 743 getResources().getString(R.string.callFailed_cdma_call_limit), 744 "merge-capable call exists, prevent flash command.")); 745 } 746 } 747 } 748 } 749 750 return null; // null means nothing went wrong, and call should continue. 751 } 752 isTtyModeEnabled(Context context)753 private boolean isTtyModeEnabled(Context context) { 754 return (android.provider.Settings.Secure.getInt( 755 context.getContentResolver(), 756 android.provider.Settings.Secure.PREFERRED_TTY_MODE, 757 TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF); 758 } 759 } 760