TelephonyConnectionService.java revision a254d02fe68c6c896926ec890ddbefd9910384e8
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.ServiceState; 37import android.telephony.SubscriptionManager; 38import android.telephony.TelephonyManager; 39import android.text.TextUtils; 40 41import com.android.internal.telephony.Call; 42import com.android.internal.telephony.CallStateException; 43import com.android.internal.telephony.IccCard; 44import com.android.internal.telephony.IccCardConstants; 45import com.android.internal.telephony.Phone; 46import com.android.internal.telephony.PhoneConstants; 47import com.android.internal.telephony.PhoneFactory; 48import com.android.internal.telephony.SubscriptionController; 49import com.android.internal.telephony.imsphone.ImsExternalCallTracker; 50import com.android.internal.telephony.imsphone.ImsPhone; 51import com.android.phone.MMIDialogActivity; 52import com.android.phone.PhoneUtils; 53import com.android.phone.R; 54 55import java.util.ArrayList; 56import java.util.List; 57import java.util.regex.Pattern; 58 59/** 60 * Service for making GSM and CDMA connections. 61 */ 62public 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 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 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 final boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(this, number); 179 180 if (isEmergencyNumber && !isRadioOn()) { 181 final Uri emergencyHandle = handle; 182 // By default, Connection based on the default Phone, since we need to return to Telecom 183 // now. 184 final int defaultPhoneType = PhoneFactory.getDefaultPhone().getPhoneType(); 185 final Connection emergencyConnection = getTelephonyConnection(request, number, 186 isEmergencyNumber, emergencyHandle, PhoneFactory.getDefaultPhone()); 187 if (mEmergencyCallHelper == null) { 188 mEmergencyCallHelper = new EmergencyCallHelper(this); 189 } 190 mEmergencyCallHelper.enableEmergencyCalling(new EmergencyCallStateListener.Callback() { 191 @Override 192 public void onComplete(EmergencyCallStateListener listener, boolean isRadioReady) { 193 // Make sure the Call has not already been canceled by the user. 194 if (emergencyConnection.getState() == Connection.STATE_DISCONNECTED) { 195 Log.i(this, "Emergency call disconnected before the outgoing call was " + 196 "placed. Skipping emergency call placement."); 197 return; 198 } 199 if (isRadioReady) { 200 // Get the right phone object since the radio has been turned on 201 // successfully. 202 final Phone phone = getPhoneForAccount(request.getAccountHandle(), 203 isEmergencyNumber); 204 // If the PhoneType of the Phone being used is different than the Default 205 // Phone, then we need create a new Connection using that PhoneType and 206 // replace it in Telecom. 207 if (phone.getPhoneType() != defaultPhoneType) { 208 Connection repConnection = getTelephonyConnection(request, number, 209 isEmergencyNumber, emergencyHandle, phone); 210 // If there was a failure, the resulting connection will not be a 211 // TelephonyConnection, so don't place the call, just return! 212 if (repConnection instanceof TelephonyConnection) { 213 placeOutgoingConnection((TelephonyConnection) repConnection, phone, 214 request); 215 } 216 // Notify Telecom of the new Connection type. 217 // TODO: Switch out the underlying connection instead of creating a new 218 // one and causing UI Jank. 219 addExistingConnection(PhoneUtils.makePstnPhoneAccountHandle(phone), 220 repConnection); 221 // Remove the old connection from Telecom after. 222 emergencyConnection.setDisconnected( 223 DisconnectCauseUtil.toTelecomDisconnectCause( 224 android.telephony.DisconnectCause.OUTGOING_CANCELED, 225 "Reconnecting outgoing Emergency Call.")); 226 emergencyConnection.destroy(); 227 } else { 228 placeOutgoingConnection((TelephonyConnection) emergencyConnection, 229 phone, request); 230 } 231 } else { 232 Log.w(this, "onCreateOutgoingConnection, failed to turn on radio"); 233 emergencyConnection.setDisconnected( 234 DisconnectCauseUtil.toTelecomDisconnectCause( 235 android.telephony.DisconnectCause.POWER_OFF, 236 "Failed to turn on radio.")); 237 emergencyConnection.destroy(); 238 } 239 } 240 }); 241 // Return the still unconnected GsmConnection and wait for the Radios to boot before 242 // connecting it to the underlying Phone. 243 return emergencyConnection; 244 } else { 245 // Get the right phone object from the account data passed in. 246 final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber); 247 Connection resultConnection = getTelephonyConnection(request, number, isEmergencyNumber, 248 handle, phone); 249 // If there was a failure, the resulting connection will not be a TelephonyConnection, 250 // so don't place the call! 251 if(resultConnection instanceof TelephonyConnection) { 252 placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request); 253 } 254 return resultConnection; 255 } 256 } 257 258 private Connection getTelephonyConnection(final ConnectionRequest request, final String number, 259 boolean isEmergencyNumber, final Uri handle, Phone phone) { 260 261 if (phone == null) { 262 final Context context = getApplicationContext(); 263 if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) { 264 // Check SIM card state before the outgoing call. 265 // Start the SIM unlock activity if PIN_REQUIRED. 266 final Phone defaultPhone = PhoneFactory.getDefaultPhone(); 267 final IccCard icc = defaultPhone.getIccCard(); 268 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN; 269 if (icc != null) { 270 simState = icc.getState(); 271 } 272 if (simState == IccCardConstants.State.PIN_REQUIRED) { 273 final String simUnlockUiPackage = context.getResources().getString( 274 R.string.config_simUnlockUiPackage); 275 final String simUnlockUiClass = context.getResources().getString( 276 R.string.config_simUnlockUiClass); 277 if (simUnlockUiPackage != null && simUnlockUiClass != null) { 278 Intent simUnlockIntent = new Intent().setComponent(new ComponentName( 279 simUnlockUiPackage, simUnlockUiClass)); 280 simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 281 try { 282 context.startActivity(simUnlockIntent); 283 } catch (ActivityNotFoundException exception) { 284 Log.e(this, exception, "Unable to find SIM unlock UI activity."); 285 } 286 } 287 return Connection.createFailedConnection( 288 DisconnectCauseUtil.toTelecomDisconnectCause( 289 android.telephony.DisconnectCause.OUT_OF_SERVICE, 290 "SIM_STATE_PIN_REQUIRED")); 291 } 292 } 293 294 Log.d(this, "onCreateOutgoingConnection, phone is null"); 295 return Connection.createFailedConnection( 296 DisconnectCauseUtil.toTelecomDisconnectCause( 297 android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null")); 298 } 299 300 // Check both voice & data RAT to enable normal CS call, 301 // when voice RAT is OOS but Data RAT is present. 302 int state = phone.getServiceState().getState(); 303 if (state == ServiceState.STATE_OUT_OF_SERVICE) { 304 int dataNetType = phone.getServiceState().getDataNetworkType(); 305 if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE || 306 dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) { 307 state = phone.getServiceState().getDataRegState(); 308 } 309 } 310 311 // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if 312 // carrier configuration specifies that we cannot make non-emergency calls in ECM mode. 313 if (!isEmergencyNumber && phone.isInEcm()) { 314 boolean allowNonEmergencyCalls = true; 315 CarrierConfigManager cfgManager = (CarrierConfigManager) 316 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 317 if (cfgManager != null) { 318 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId()) 319 .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL); 320 } 321 322 if (!allowNonEmergencyCalls) { 323 return Connection.createFailedConnection( 324 DisconnectCauseUtil.toTelecomDisconnectCause( 325 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY, 326 "Cannot make non-emergency call in ECM mode." 327 )); 328 } 329 } 330 331 if (!isEmergencyNumber) { 332 switch (state) { 333 case ServiceState.STATE_IN_SERVICE: 334 case ServiceState.STATE_EMERGENCY_ONLY: 335 break; 336 case ServiceState.STATE_OUT_OF_SERVICE: 337 if (phone.isUtEnabled() && number.endsWith("#")) { 338 Log.d(this, "onCreateOutgoingConnection dial for UT"); 339 break; 340 } else { 341 return Connection.createFailedConnection( 342 DisconnectCauseUtil.toTelecomDisconnectCause( 343 android.telephony.DisconnectCause.OUT_OF_SERVICE, 344 "ServiceState.STATE_OUT_OF_SERVICE")); 345 } 346 case ServiceState.STATE_POWER_OFF: 347 return Connection.createFailedConnection( 348 DisconnectCauseUtil.toTelecomDisconnectCause( 349 android.telephony.DisconnectCause.POWER_OFF, 350 "ServiceState.STATE_POWER_OFF")); 351 default: 352 Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state); 353 return Connection.createFailedConnection( 354 DisconnectCauseUtil.toTelecomDisconnectCause( 355 android.telephony.DisconnectCause.OUTGOING_FAILURE, 356 "Unknown service state " + state)); 357 } 358 } 359 360 final Context context = getApplicationContext(); 361 if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled(context) && 362 !isEmergencyNumber) { 363 return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause( 364 android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED)); 365 } 366 367 // Check for additional limits on CDMA phones. 368 final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone); 369 if (failedConnection != null) { 370 return failedConnection; 371 } 372 373 final TelephonyConnection connection = 374 createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(), 375 request.getTelecomCallId(), request.getAddress(), request.getVideoState()); 376 if (connection == null) { 377 return Connection.createFailedConnection( 378 DisconnectCauseUtil.toTelecomDisconnectCause( 379 android.telephony.DisconnectCause.OUTGOING_FAILURE, 380 "Invalid phone type")); 381 } 382 connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED); 383 connection.setInitializing(); 384 connection.setVideoState(request.getVideoState()); 385 386 return connection; 387 } 388 389 @Override 390 public Connection onCreateIncomingConnection( 391 PhoneAccountHandle connectionManagerPhoneAccount, 392 ConnectionRequest request) { 393 Log.i(this, "onCreateIncomingConnection, request: " + request); 394 // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM), 395 // make sure the PhoneAccount lookup retrieves the default Emergency Phone. 396 PhoneAccountHandle accountHandle = request.getAccountHandle(); 397 boolean isEmergency = false; 398 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 399 accountHandle.getId())) { 400 Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " + 401 "Treat as an Emergency Call."); 402 isEmergency = true; 403 } 404 Phone phone = getPhoneForAccount(accountHandle, isEmergency); 405 if (phone == null) { 406 return Connection.createFailedConnection( 407 DisconnectCauseUtil.toTelecomDisconnectCause( 408 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 409 "Phone is null")); 410 } 411 412 Call call = phone.getRingingCall(); 413 if (!call.getState().isRinging()) { 414 Log.i(this, "onCreateIncomingConnection, no ringing call"); 415 return Connection.createFailedConnection( 416 DisconnectCauseUtil.toTelecomDisconnectCause( 417 android.telephony.DisconnectCause.INCOMING_MISSED, 418 "Found no ringing call")); 419 } 420 421 com.android.internal.telephony.Connection originalConnection = 422 call.getState() == Call.State.WAITING ? 423 call.getLatestConnection() : call.getEarliestConnection(); 424 if (isOriginalConnectionKnown(originalConnection)) { 425 Log.i(this, "onCreateIncomingConnection, original connection already registered"); 426 return Connection.createCanceledConnection(); 427 } 428 429 // We should rely on the originalConnection to get the video state. The request coming 430 // from Telecom does not know the video state of the incoming call. 431 int videoState = originalConnection != null ? originalConnection.getVideoState() : 432 VideoProfile.STATE_AUDIO_ONLY; 433 434 Connection connection = 435 createConnectionFor(phone, originalConnection, false /* isOutgoing */, 436 request.getAccountHandle(), request.getTelecomCallId(), 437 request.getAddress(), videoState); 438 if (connection == null) { 439 return Connection.createCanceledConnection(); 440 } else { 441 return connection; 442 } 443 } 444 445 @Override 446 public void triggerConferenceRecalculate() { 447 if (mTelephonyConferenceController.shouldRecalculate()) { 448 mTelephonyConferenceController.recalculate(); 449 } 450 } 451 452 @Override 453 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 454 ConnectionRequest request) { 455 Log.i(this, "onCreateUnknownConnection, request: " + request); 456 // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's 457 // Emergency PhoneAccount 458 PhoneAccountHandle accountHandle = request.getAccountHandle(); 459 boolean isEmergency = false; 460 if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals( 461 accountHandle.getId())) { 462 Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " + 463 "Treat as an Emergency Call."); 464 isEmergency = true; 465 } 466 Phone phone = getPhoneForAccount(accountHandle, isEmergency); 467 if (phone == null) { 468 return Connection.createFailedConnection( 469 DisconnectCauseUtil.toTelecomDisconnectCause( 470 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 471 "Phone is null")); 472 } 473 Bundle extras = request.getExtras(); 474 475 final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>(); 476 477 // Handle the case where an unknown connection has an IMS external call ID specified; we can 478 // skip the rest of the guesswork and just grad that unknown call now. 479 if (phone.getImsPhone() != null && extras != null && 480 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) { 481 482 ImsPhone imsPhone = (ImsPhone) phone.getImsPhone(); 483 ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker(); 484 int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID, 485 -1); 486 487 if (externalCallTracker != null) { 488 com.android.internal.telephony.Connection connection = 489 externalCallTracker.getConnectionById(externalCallId); 490 491 if (connection != null) { 492 allConnections.add(connection); 493 } 494 } 495 } 496 497 if (allConnections.isEmpty()) { 498 final Call ringingCall = phone.getRingingCall(); 499 if (ringingCall.hasConnections()) { 500 allConnections.addAll(ringingCall.getConnections()); 501 } 502 final Call foregroundCall = phone.getForegroundCall(); 503 if ((foregroundCall.getState() != Call.State.DISCONNECTED) 504 && (foregroundCall.hasConnections())) { 505 allConnections.addAll(foregroundCall.getConnections()); 506 } 507 if (phone.getImsPhone() != null) { 508 final Call imsFgCall = phone.getImsPhone().getForegroundCall(); 509 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall 510 .hasConnections()) { 511 allConnections.addAll(imsFgCall.getConnections()); 512 } 513 } 514 final Call backgroundCall = phone.getBackgroundCall(); 515 if (backgroundCall.hasConnections()) { 516 allConnections.addAll(phone.getBackgroundCall().getConnections()); 517 } 518 } 519 520 com.android.internal.telephony.Connection unknownConnection = null; 521 for (com.android.internal.telephony.Connection telephonyConnection : allConnections) { 522 if (!isOriginalConnectionKnown(telephonyConnection)) { 523 unknownConnection = telephonyConnection; 524 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection); 525 break; 526 } 527 } 528 529 if (unknownConnection == null) { 530 Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection."); 531 return Connection.createCanceledConnection(); 532 } 533 534 // We should rely on the originalConnection to get the video state. The request coming 535 // from Telecom does not know the video state of the unknown call. 536 int videoState = unknownConnection != null ? unknownConnection.getVideoState() : 537 VideoProfile.STATE_AUDIO_ONLY; 538 539 TelephonyConnection connection = 540 createConnectionFor(phone, unknownConnection, 541 !unknownConnection.isIncoming() /* isOutgoing */, 542 request.getAccountHandle(), request.getTelecomCallId(), 543 request.getAddress(), videoState); 544 545 if (connection == null) { 546 return Connection.createCanceledConnection(); 547 } else { 548 connection.updateState(); 549 return connection; 550 } 551 } 552 553 @Override 554 public void onConference(Connection connection1, Connection connection2) { 555 if (connection1 instanceof TelephonyConnection && 556 connection2 instanceof TelephonyConnection) { 557 ((TelephonyConnection) connection1).performConference( 558 (TelephonyConnection) connection2); 559 } 560 561 } 562 563 private boolean isRadioOn() { 564 boolean result = false; 565 for (Phone phone : PhoneFactory.getPhones()) { 566 result |= phone.isRadioOn(); 567 } 568 return result; 569 } 570 571 private void placeOutgoingConnection( 572 TelephonyConnection connection, Phone phone, ConnectionRequest request) { 573 String number = connection.getAddress().getSchemeSpecificPart(); 574 575 com.android.internal.telephony.Connection originalConnection; 576 try { 577 originalConnection = 578 phone.dial(number, null, request.getVideoState(), request.getExtras()); 579 } catch (CallStateException e) { 580 Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e); 581 int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 582 if (e.getError() == CallStateException.ERROR_DISCONNECTED) { 583 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE; 584 } 585 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 586 cause, e.getMessage())); 587 return; 588 } 589 590 if (originalConnection == null) { 591 int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 592 // On GSM phones, null connection means that we dialed an MMI code 593 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 594 Log.d(this, "dialed MMI code"); 595 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; 596 final Intent intent = new Intent(this, MMIDialogActivity.class); 597 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 598 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 599 startActivity(intent); 600 } 601 Log.d(this, "placeOutgoingConnection, phone.dial returned null"); 602 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 603 telephonyDisconnectCause, "Connection is null")); 604 } else { 605 connection.setOriginalConnection(originalConnection); 606 } 607 } 608 609 private TelephonyConnection createConnectionFor( 610 Phone phone, 611 com.android.internal.telephony.Connection originalConnection, 612 boolean isOutgoing, 613 PhoneAccountHandle phoneAccountHandle, 614 String telecomCallId, 615 Uri address, 616 int videoState) { 617 TelephonyConnection returnConnection = null; 618 int phoneType = phone.getPhoneType(); 619 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 620 returnConnection = new GsmConnection(originalConnection, telecomCallId); 621 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 622 boolean allowsMute = allowsMute(phone); 623 returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer, 624 allowsMute, isOutgoing, telecomCallId); 625 } 626 if (returnConnection != null) { 627 // Listen to Telephony specific callbacks from the connection 628 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener); 629 returnConnection.setVideoPauseSupported( 630 TelecomAccountRegistry.getInstance(this).isVideoPauseSupported( 631 phoneAccountHandle)); 632 } 633 return returnConnection; 634 } 635 636 private boolean isOriginalConnectionKnown( 637 com.android.internal.telephony.Connection originalConnection) { 638 for (Connection connection : getAllConnections()) { 639 if (connection instanceof TelephonyConnection) { 640 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 641 if (telephonyConnection.getOriginalConnection() == originalConnection) { 642 return true; 643 } 644 } 645 } 646 return false; 647 } 648 649 private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) { 650 Phone chosenPhone = null; 651 int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle); 652 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 653 int phoneId = SubscriptionController.getInstance().getPhoneId(subId); 654 chosenPhone = PhoneFactory.getPhone(phoneId); 655 } 656 // If this is an emergency call and the phone we originally planned to make this call 657 // with is not in service or was invalid, try to find one that is in service, using the 658 // default as a last chance backup. 659 if (isEmergency && (chosenPhone == null || ServiceState.STATE_IN_SERVICE != chosenPhone 660 .getServiceState().getState())) { 661 Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service " 662 + "or invalid for emergency call.", accountHandle); 663 chosenPhone = getFirstPhoneForEmergencyCall(); 664 Log.d(this, "getPhoneForAccount: using subId: " + 665 (chosenPhone == null ? "null" : chosenPhone.getSubId())); 666 } 667 return chosenPhone; 668 } 669 670 /** 671 * Retrieves the most sensible Phone to use for an emergency call using the following Priority 672 * list (for multi-SIM devices): 673 * 1) The User's SIM preference for Voice calling 674 * 2) The First Phone that is currently IN_SERVICE or is available for emergency calling 675 * 3) The First Phone that has a SIM card in it (Starting from Slot 0...N) 676 * 4) The Default Phone (Currently set as Slot 0) 677 */ 678 private Phone getFirstPhoneForEmergencyCall() { 679 Phone firstPhoneWithSim = null; 680 681 // 1) 682 int phoneId = SubscriptionManager.getDefaultVoicePhoneId(); 683 if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) { 684 Phone defaultPhone = PhoneFactory.getPhone(phoneId); 685 if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) { 686 return defaultPhone; 687 } 688 } 689 690 for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) { 691 Phone phone = PhoneFactory.getPhone(i); 692 if (phone == null) 693 continue; 694 // 2) 695 if (isAvailableForEmergencyCalls(phone)) { 696 // the slot has the radio on & state is in service. 697 Log.d(this, "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i); 698 return phone; 699 } 700 // 3) 701 if (firstPhoneWithSim == null && TelephonyManager.getDefault().hasIccCard(i)) { 702 // The slot has a SIM card inserted, but is not in service, so keep track of this 703 // Phone. Do not return because we want to make sure that none of the other Phones 704 // are in service (because that is always faster). 705 Log.d(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" + i); 706 firstPhoneWithSim = phone; 707 } 708 } 709 // 4) 710 if (firstPhoneWithSim == null) { 711 // No SIMs inserted, get the default. 712 Log.d(this, "getFirstPhoneForEmergencyCall, return default phone"); 713 return PhoneFactory.getDefaultPhone(); 714 } else { 715 return firstPhoneWithSim; 716 } 717 } 718 719 /** 720 * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only. 721 */ 722 private boolean isAvailableForEmergencyCalls(Phone phone) { 723 return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() || 724 phone.getServiceState().isEmergencyOnly(); 725 } 726 727 /** 728 * Determines if the connection should allow mute. 729 * 730 * @param phone The current phone. 731 * @return {@code True} if the connection should allow mute. 732 */ 733 private boolean allowsMute(Phone phone) { 734 // For CDMA phones, check if we are in Emergency Callback Mode (ECM). Mute is disallowed 735 // in ECM mode. 736 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 737 if (phone.isInEcm()) { 738 return false; 739 } 740 } 741 742 return true; 743 } 744 745 @Override 746 public void removeConnection(Connection connection) { 747 super.removeConnection(connection); 748 if (connection instanceof TelephonyConnection) { 749 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 750 telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener); 751 } 752 } 753 754 /** 755 * When a {@link TelephonyConnection} has its underlying original connection configured, 756 * we need to add it to the correct conference controller. 757 * 758 * @param connection The connection to be added to the controller 759 */ 760 public void addConnectionToConferenceController(TelephonyConnection connection) { 761 // TODO: Do we need to handle the case of the original connection changing 762 // and triggering this callback multiple times for the same connection? 763 // If that is the case, we might want to remove this connection from all 764 // conference controllers first before re-adding it. 765 if (connection.isImsConnection()) { 766 Log.d(this, "Adding IMS connection to conference controller: " + connection); 767 mImsConferenceController.add(connection); 768 } else { 769 int phoneType = connection.getCall().getPhone().getPhoneType(); 770 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 771 Log.d(this, "Adding GSM connection to conference controller: " + connection); 772 mTelephonyConferenceController.add(connection); 773 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA && 774 connection instanceof CdmaConnection) { 775 Log.d(this, "Adding CDMA connection to conference controller: " + connection); 776 mCdmaConferenceController.add((CdmaConnection)connection); 777 } 778 Log.d(this, "Removing connection from IMS conference controller: " + connection); 779 mImsConferenceController.remove(connection); 780 } 781 } 782 783 /** 784 * Create a new CDMA connection. CDMA connections have additional limitations when creating 785 * additional calls which are handled in this method. Specifically, CDMA has a "FLASH" command 786 * that can be used for three purposes: merging a call, swapping unmerged calls, and adding 787 * a new outgoing call. The function of the flash command depends on the context of the current 788 * set of calls. This method will prevent an outgoing call from being made if it is not within 789 * the right circumstances to support adding a call. 790 */ 791 private Connection checkAdditionalOutgoingCallLimits(Phone phone) { 792 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 793 // Check to see if any CDMA conference calls exist, and if they do, check them for 794 // limitations. 795 for (Conference conference : getAllConferences()) { 796 if (conference instanceof CdmaConference) { 797 CdmaConference cdmaConf = (CdmaConference) conference; 798 799 // If the CDMA conference has not been merged, add-call will not work, so fail 800 // this request to add a call. 801 if (cdmaConf.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 802 return Connection.createFailedConnection(new DisconnectCause( 803 DisconnectCause.RESTRICTED, 804 null, 805 getResources().getString(R.string.callFailed_cdma_call_limit), 806 "merge-capable call exists, prevent flash command.")); 807 } 808 } 809 } 810 } 811 812 return null; // null means nothing went wrong, and call should continue. 813 } 814 815 private boolean isTtyModeEnabled(Context context) { 816 return (android.provider.Settings.Secure.getInt( 817 context.getContentResolver(), 818 android.provider.Settings.Secure.PREFERRED_TTY_MODE, 819 TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF); 820 } 821} 822