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 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 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 */ 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 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 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 473 public void triggerConferenceRecalculate() { 474 if (mTelephonyConferenceController.shouldRecalculate()) { 475 mTelephonyConferenceController.recalculate(); 476 } 477 } 478 479 @Override 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 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 590 private boolean isRadioOn() { 591 boolean result = false; 592 for (Phone phone : PhoneFactory.getPhones()) { 593 result |= phone.isRadioOn(); 594 } 595 return result; 596 } 597 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 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 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 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 */ 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 */ 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 */ 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 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 */ 787 public void addConnectionToConferenceController(TelephonyConnection connection) { 788 // TODO: Do we need to handle the case of the original connection changing 789 // and triggering this callback multiple times for the same connection? 790 // If that is the case, we might want to remove this connection from all 791 // conference controllers first before re-adding it. 792 if (connection.isImsConnection()) { 793 Log.d(this, "Adding IMS connection to conference controller: " + connection); 794 mImsConferenceController.add(connection); 795 } else { 796 int phoneType = connection.getCall().getPhone().getPhoneType(); 797 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 798 Log.d(this, "Adding GSM connection to conference controller: " + connection); 799 mTelephonyConferenceController.add(connection); 800 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA && 801 connection instanceof CdmaConnection) { 802 Log.d(this, "Adding CDMA connection to conference controller: " + connection); 803 mCdmaConferenceController.add((CdmaConnection)connection); 804 } 805 Log.d(this, "Removing connection from IMS conference controller: " + connection); 806 mImsConferenceController.remove(connection); 807 } 808 } 809 810 /** 811 * Create a new CDMA connection. CDMA connections have additional limitations when creating 812 * additional calls which are handled in this method. Specifically, CDMA has a "FLASH" command 813 * that can be used for three purposes: merging a call, swapping unmerged calls, and adding 814 * a new outgoing call. The function of the flash command depends on the context of the current 815 * set of calls. This method will prevent an outgoing call from being made if it is not within 816 * the right circumstances to support adding a call. 817 */ 818 private Connection checkAdditionalOutgoingCallLimits(Phone phone) { 819 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 820 // Check to see if any CDMA conference calls exist, and if they do, check them for 821 // limitations. 822 for (Conference conference : getAllConferences()) { 823 if (conference instanceof CdmaConference) { 824 CdmaConference cdmaConf = (CdmaConference) conference; 825 826 // If the CDMA conference has not been merged, add-call will not work, so fail 827 // this request to add a call. 828 if (cdmaConf.can(Connection.CAPABILITY_MERGE_CONFERENCE)) { 829 return Connection.createFailedConnection(new DisconnectCause( 830 DisconnectCause.RESTRICTED, 831 null, 832 getResources().getString(R.string.callFailed_cdma_call_limit), 833 "merge-capable call exists, prevent flash command.")); 834 } 835 } 836 } 837 } 838 839 return null; // null means nothing went wrong, and call should continue. 840 } 841 842 private boolean isTtyModeEnabled(Context context) { 843 return (android.provider.Settings.Secure.getInt( 844 context.getContentResolver(), 845 android.provider.Settings.Secure.PREFERRED_TTY_MODE, 846 TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF); 847 } 848 } 849