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