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