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.ComponentName; 20import android.content.Context; 21import android.content.Intent; 22import android.content.res.Configuration; 23import android.content.res.Resources; 24import android.net.Uri; 25import android.telecom.Connection; 26import android.telecom.ConnectionRequest; 27import android.telecom.ConnectionService; 28import android.telecom.PhoneAccount; 29import android.telecom.PhoneAccountHandle; 30import android.telephony.CarrierConfigManager; 31import android.telephony.PhoneNumberUtils; 32import android.telephony.ServiceState; 33import android.telephony.SubscriptionInfo; 34import android.telephony.SubscriptionManager; 35import android.telephony.TelephonyManager; 36import android.text.TextUtils; 37 38import com.android.internal.telephony.Call; 39import com.android.internal.telephony.CallStateException; 40import com.android.internal.telephony.Phone; 41import com.android.internal.telephony.PhoneConstants; 42import com.android.internal.telephony.PhoneFactory; 43import com.android.internal.telephony.PhoneProxy; 44import com.android.internal.telephony.SubscriptionController; 45import com.android.internal.telephony.cdma.CDMAPhone; 46import com.android.phone.MMIDialogActivity; 47import com.android.phone.PhoneUtils; 48import com.android.phone.R; 49 50import java.util.ArrayList; 51import java.util.List; 52import java.util.Objects; 53import java.util.regex.Pattern; 54 55/** 56 * Service for making GSM and CDMA connections. 57 */ 58public class TelephonyConnectionService extends ConnectionService { 59 60 // If configured, reject attempts to dial numbers matching this pattern. 61 private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN = 62 Pattern.compile("\\*228[0-9]{0,2}"); 63 64 private final TelephonyConferenceController mTelephonyConferenceController = 65 new TelephonyConferenceController(this); 66 private final CdmaConferenceController mCdmaConferenceController = 67 new CdmaConferenceController(this); 68 private final ImsConferenceController mImsConferenceController = 69 new ImsConferenceController(this); 70 71 private ComponentName mExpectedComponentName = null; 72 private EmergencyCallHelper mEmergencyCallHelper; 73 private EmergencyTonePlayer mEmergencyTonePlayer; 74 75 /** 76 * A listener to actionable events specific to the TelephonyConnection. 77 */ 78 private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener = 79 new TelephonyConnection.TelephonyConnectionListener() { 80 @Override 81 public void onOriginalConnectionConfigured(TelephonyConnection c) { 82 addConnectionToConferenceController(c); 83 } 84 }; 85 86 @Override 87 public void onCreate() { 88 super.onCreate(); 89 mExpectedComponentName = new ComponentName(this, this.getClass()); 90 mEmergencyTonePlayer = new EmergencyTonePlayer(this); 91 TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this); 92 } 93 94 @Override 95 public Connection onCreateOutgoingConnection( 96 PhoneAccountHandle connectionManagerPhoneAccount, 97 final ConnectionRequest request) { 98 Log.i(this, "onCreateOutgoingConnection, request: " + request); 99 100 Uri handle = request.getAddress(); 101 if (handle == null) { 102 Log.d(this, "onCreateOutgoingConnection, handle is null"); 103 return Connection.createFailedConnection( 104 DisconnectCauseUtil.toTelecomDisconnectCause( 105 android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, 106 "No phone number supplied")); 107 } 108 109 String scheme = handle.getScheme(); 110 final String number; 111 if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) { 112 // TODO: We don't check for SecurityException here (requires 113 // CALL_PRIVILEGED permission). 114 final Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 115 if (phone == null) { 116 Log.d(this, "onCreateOutgoingConnection, phone is null"); 117 return Connection.createFailedConnection( 118 DisconnectCauseUtil.toTelecomDisconnectCause( 119 android.telephony.DisconnectCause.OUT_OF_SERVICE, 120 "Phone is null")); 121 } 122 number = phone.getVoiceMailNumber(); 123 if (TextUtils.isEmpty(number)) { 124 Log.d(this, "onCreateOutgoingConnection, no voicemail number set."); 125 return Connection.createFailedConnection( 126 DisconnectCauseUtil.toTelecomDisconnectCause( 127 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING, 128 "Voicemail scheme provided but no voicemail number set.")); 129 } 130 131 // Convert voicemail: to tel: 132 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 133 } else { 134 if (!PhoneAccount.SCHEME_TEL.equals(scheme)) { 135 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme); 136 return Connection.createFailedConnection( 137 DisconnectCauseUtil.toTelecomDisconnectCause( 138 android.telephony.DisconnectCause.INVALID_NUMBER, 139 "Handle scheme is not type tel")); 140 } 141 142 number = handle.getSchemeSpecificPart(); 143 if (TextUtils.isEmpty(number)) { 144 Log.d(this, "onCreateOutgoingConnection, unable to parse number"); 145 return Connection.createFailedConnection( 146 DisconnectCauseUtil.toTelecomDisconnectCause( 147 android.telephony.DisconnectCause.INVALID_NUMBER, 148 "Unable to parse number")); 149 } 150 151 final Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 152 if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) { 153 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number 154 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and 155 // when dialed could lock LTE SIMs to 3G if not prohibited.. 156 boolean disableActivation = false; 157 CarrierConfigManager cfgManager = (CarrierConfigManager) 158 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 159 if (cfgManager != null) { 160 disableActivation = cfgManager.getConfigForSubId(phone.getSubId()) 161 .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL); 162 } 163 164 if (disableActivation) { 165 return Connection.createFailedConnection( 166 DisconnectCauseUtil.toTelecomDisconnectCause( 167 android.telephony.DisconnectCause 168 .CDMA_ALREADY_ACTIVATED, 169 "Tried to dial *228")); 170 } 171 } 172 } 173 174 boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(this, number); 175 176 // Get the right phone object from the account data passed in. 177 final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber); 178 if (phone == null) { 179 Log.d(this, "onCreateOutgoingConnection, phone is null"); 180 return Connection.createFailedConnection( 181 DisconnectCauseUtil.toTelecomDisconnectCause( 182 android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null")); 183 } 184 185 // Check both voice & data RAT to enable normal CS call, 186 // when voice RAT is OOS but Data RAT is present. 187 int state = phone.getServiceState().getState(); 188 if (state == ServiceState.STATE_OUT_OF_SERVICE) { 189 if (phone.getServiceState().getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE) { 190 state = phone.getServiceState().getDataRegState(); 191 } 192 } 193 boolean useEmergencyCallHelper = false; 194 195 if (isEmergencyNumber) { 196 if (!phone.isRadioOn()) { 197 useEmergencyCallHelper = true; 198 } 199 } else { 200 switch (state) { 201 case ServiceState.STATE_IN_SERVICE: 202 case ServiceState.STATE_EMERGENCY_ONLY: 203 break; 204 case ServiceState.STATE_OUT_OF_SERVICE: 205 return Connection.createFailedConnection( 206 DisconnectCauseUtil.toTelecomDisconnectCause( 207 android.telephony.DisconnectCause.OUT_OF_SERVICE, 208 "ServiceState.STATE_OUT_OF_SERVICE")); 209 case ServiceState.STATE_POWER_OFF: 210 return Connection.createFailedConnection( 211 DisconnectCauseUtil.toTelecomDisconnectCause( 212 android.telephony.DisconnectCause.POWER_OFF, 213 "ServiceState.STATE_POWER_OFF")); 214 default: 215 Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state); 216 return Connection.createFailedConnection( 217 DisconnectCauseUtil.toTelecomDisconnectCause( 218 android.telephony.DisconnectCause.OUTGOING_FAILURE, 219 "Unknown service state " + state)); 220 } 221 } 222 223 final TelephonyConnection connection = 224 createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle()); 225 if (connection == null) { 226 return Connection.createFailedConnection( 227 DisconnectCauseUtil.toTelecomDisconnectCause( 228 android.telephony.DisconnectCause.OUTGOING_FAILURE, 229 "Invalid phone type")); 230 } 231 connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED); 232 connection.setInitializing(); 233 connection.setVideoState(request.getVideoState()); 234 235 if (useEmergencyCallHelper) { 236 if (mEmergencyCallHelper == null) { 237 mEmergencyCallHelper = new EmergencyCallHelper(this); 238 } 239 mEmergencyCallHelper.startTurnOnRadioSequence(phone, 240 new EmergencyCallHelper.Callback() { 241 @Override 242 public void onComplete(boolean isRadioReady) { 243 if (connection.getState() == Connection.STATE_DISCONNECTED) { 244 // If the connection has already been disconnected, do nothing. 245 } else if (isRadioReady) { 246 connection.setInitialized(); 247 placeOutgoingConnection(connection, phone, request); 248 } else { 249 Log.d(this, "onCreateOutgoingConnection, failed to turn on radio"); 250 connection.setDisconnected( 251 DisconnectCauseUtil.toTelecomDisconnectCause( 252 android.telephony.DisconnectCause.POWER_OFF, 253 "Failed to turn on radio.")); 254 connection.destroy(); 255 } 256 } 257 }); 258 259 } else { 260 placeOutgoingConnection(connection, phone, request); 261 } 262 263 return connection; 264 } 265 266 @Override 267 public Connection onCreateIncomingConnection( 268 PhoneAccountHandle connectionManagerPhoneAccount, 269 ConnectionRequest request) { 270 Log.i(this, "onCreateIncomingConnection, request: " + request); 271 272 Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 273 if (phone == null) { 274 return Connection.createFailedConnection( 275 DisconnectCauseUtil.toTelecomDisconnectCause( 276 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 277 "Phone is null")); 278 } 279 280 Call call = phone.getRingingCall(); 281 if (!call.getState().isRinging()) { 282 Log.i(this, "onCreateIncomingConnection, no ringing call"); 283 return Connection.createFailedConnection( 284 DisconnectCauseUtil.toTelecomDisconnectCause( 285 android.telephony.DisconnectCause.INCOMING_MISSED, 286 "Found no ringing call")); 287 } 288 289 com.android.internal.telephony.Connection originalConnection = 290 call.getState() == Call.State.WAITING ? 291 call.getLatestConnection() : call.getEarliestConnection(); 292 if (isOriginalConnectionKnown(originalConnection)) { 293 Log.i(this, "onCreateIncomingConnection, original connection already registered"); 294 return Connection.createCanceledConnection(); 295 } 296 297 Connection connection = 298 createConnectionFor(phone, originalConnection, false /* isOutgoing */, 299 request.getAccountHandle()); 300 if (connection == null) { 301 return Connection.createCanceledConnection(); 302 } else { 303 return connection; 304 } 305 } 306 307 @Override 308 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 309 ConnectionRequest request) { 310 Log.i(this, "onCreateUnknownConnection, request: " + request); 311 312 Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 313 if (phone == null) { 314 return Connection.createFailedConnection( 315 DisconnectCauseUtil.toTelecomDisconnectCause( 316 android.telephony.DisconnectCause.ERROR_UNSPECIFIED, 317 "Phone is null")); 318 } 319 320 final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>(); 321 final Call ringingCall = phone.getRingingCall(); 322 if (ringingCall.hasConnections()) { 323 allConnections.addAll(ringingCall.getConnections()); 324 } 325 final Call foregroundCall = phone.getForegroundCall(); 326 if (foregroundCall.hasConnections()) { 327 allConnections.addAll(foregroundCall.getConnections()); 328 } 329 final Call backgroundCall = phone.getBackgroundCall(); 330 if (backgroundCall.hasConnections()) { 331 allConnections.addAll(phone.getBackgroundCall().getConnections()); 332 } 333 334 com.android.internal.telephony.Connection unknownConnection = null; 335 for (com.android.internal.telephony.Connection telephonyConnection : allConnections) { 336 if (!isOriginalConnectionKnown(telephonyConnection)) { 337 unknownConnection = telephonyConnection; 338 break; 339 } 340 } 341 342 if (unknownConnection == null) { 343 Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection."); 344 return Connection.createCanceledConnection(); 345 } 346 347 TelephonyConnection connection = 348 createConnectionFor(phone, unknownConnection, 349 !unknownConnection.isIncoming() /* isOutgoing */, 350 request.getAccountHandle()); 351 352 if (connection == null) { 353 return Connection.createCanceledConnection(); 354 } else { 355 connection.updateState(); 356 return connection; 357 } 358 } 359 360 @Override 361 public void onConference(Connection connection1, Connection connection2) { 362 if (connection1 instanceof TelephonyConnection && 363 connection2 instanceof TelephonyConnection) { 364 ((TelephonyConnection) connection1).performConference( 365 (TelephonyConnection) connection2); 366 } 367 368 } 369 370 private void placeOutgoingConnection( 371 TelephonyConnection connection, Phone phone, ConnectionRequest request) { 372 String number = connection.getAddress().getSchemeSpecificPart(); 373 374 com.android.internal.telephony.Connection originalConnection; 375 try { 376 originalConnection = 377 phone.dial(number, null, request.getVideoState(), request.getExtras()); 378 } catch (CallStateException e) { 379 Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e); 380 int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 381 if (e.getError() == CallStateException.ERROR_DISCONNECTED) { 382 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE; 383 } 384 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 385 cause, e.getMessage())); 386 return; 387 } 388 389 if (originalConnection == null) { 390 int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 391 // On GSM phones, null connection means that we dialed an MMI code 392 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 393 Log.d(this, "dialed MMI code"); 394 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; 395 final Intent intent = new Intent(this, MMIDialogActivity.class); 396 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 397 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 398 startActivity(intent); 399 } 400 Log.d(this, "placeOutgoingConnection, phone.dial returned null"); 401 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 402 telephonyDisconnectCause, "Connection is null")); 403 } else { 404 connection.setOriginalConnection(originalConnection); 405 } 406 } 407 408 private TelephonyConnection createConnectionFor( 409 Phone phone, 410 com.android.internal.telephony.Connection originalConnection, 411 boolean isOutgoing, 412 PhoneAccountHandle phoneAccountHandle) { 413 TelephonyConnection returnConnection = null; 414 int phoneType = phone.getPhoneType(); 415 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 416 returnConnection = new GsmConnection(originalConnection); 417 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 418 boolean allowMute = allowMute(phone); 419 returnConnection = new CdmaConnection( 420 originalConnection, mEmergencyTonePlayer, allowMute, isOutgoing); 421 } 422 if (returnConnection != null) { 423 // Listen to Telephony specific callbacks from the connection 424 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener); 425 returnConnection.setVideoPauseSupported( 426 TelecomAccountRegistry.getInstance(this).isVideoPauseSupported( 427 phoneAccountHandle)); 428 } 429 return returnConnection; 430 } 431 432 private boolean isOriginalConnectionKnown( 433 com.android.internal.telephony.Connection originalConnection) { 434 for (Connection connection : getAllConnections()) { 435 if (connection instanceof TelephonyConnection) { 436 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 437 if (telephonyConnection.getOriginalConnection() == originalConnection) { 438 return true; 439 } 440 } 441 } 442 return false; 443 } 444 445 private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) { 446 if (isEmergency) { 447 return PhoneFactory.getDefaultPhone(); 448 } 449 450 int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle); 451 if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { 452 int phoneId = SubscriptionController.getInstance().getPhoneId(subId); 453 return PhoneFactory.getPhone(phoneId); 454 } 455 456 return null; 457 } 458 459 private Phone getFirstPhoneForEmergencyCall() { 460 Phone selectPhone = null; 461 for (int i = 0; i < TelephonyManager.getDefault().getSimCount(); i++) { 462 int[] subIds = SubscriptionController.getInstance().getSubIdUsingSlotId(i); 463 if (subIds.length == 0) 464 continue; 465 466 int phoneId = SubscriptionController.getInstance().getPhoneId(subIds[0]); 467 Phone phone = PhoneFactory.getPhone(phoneId); 468 if (phone == null) 469 continue; 470 471 if (ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState()) { 472 // the slot is radio on & state is in service 473 Log.d(this, "pickBestPhoneForEmergencyCall, radio on & in service, slotId:" + i); 474 return phone; 475 } else if (ServiceState.STATE_POWER_OFF != phone.getServiceState().getState()) { 476 // the slot is radio on & with SIM card inserted. 477 if (TelephonyManager.getDefault().hasIccCard(i)) { 478 Log.d(this, "pickBestPhoneForEmergencyCall," + 479 "radio on and SIM card inserted, slotId:" + i); 480 selectPhone = phone; 481 } else if (selectPhone == null) { 482 Log.d(this, "pickBestPhoneForEmergencyCall, radio on, slotId:" + i); 483 selectPhone = phone; 484 } 485 } 486 } 487 488 if (selectPhone == null) { 489 Log.d(this, "pickBestPhoneForEmergencyCall, return default phone"); 490 selectPhone = PhoneFactory.getDefaultPhone(); 491 } 492 493 return selectPhone; 494 } 495 496 /** 497 * Determines if the connection should allow mute. 498 * 499 * @param phone The current phone. 500 * @return {@code True} if the connection should allow mute. 501 */ 502 private boolean allowMute(Phone phone) { 503 // For CDMA phones, check if we are in Emergency Callback Mode (ECM). Mute is disallowed 504 // in ECM mode. 505 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 506 PhoneProxy phoneProxy = (PhoneProxy)phone; 507 CDMAPhone cdmaPhone = (CDMAPhone)phoneProxy.getActivePhone(); 508 if (cdmaPhone != null) { 509 if (cdmaPhone.isInEcm()) { 510 return false; 511 } 512 } 513 } 514 515 return true; 516 } 517 518 @Override 519 public void removeConnection(Connection connection) { 520 super.removeConnection(connection); 521 if (connection instanceof TelephonyConnection) { 522 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 523 telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener); 524 } 525 } 526 527 /** 528 * When a {@link TelephonyConnection} has its underlying original connection configured, 529 * we need to add it to the correct conference controller. 530 * 531 * @param connection The connection to be added to the controller 532 */ 533 public void addConnectionToConferenceController(TelephonyConnection connection) { 534 // TODO: Do we need to handle the case of the original connection changing 535 // and triggering this callback multiple times for the same connection? 536 // If that is the case, we might want to remove this connection from all 537 // conference controllers first before re-adding it. 538 if (connection.isImsConnection()) { 539 Log.d(this, "Adding IMS connection to conference controller: " + connection); 540 mImsConferenceController.add(connection); 541 } else { 542 int phoneType = connection.getCall().getPhone().getPhoneType(); 543 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 544 Log.d(this, "Adding GSM connection to conference controller: " + connection); 545 mTelephonyConferenceController.add(connection); 546 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA && 547 connection instanceof CdmaConnection) { 548 Log.d(this, "Adding CDMA connection to conference controller: " + connection); 549 mCdmaConferenceController.add((CdmaConnection)connection); 550 } 551 Log.d(this, "Removing connection from IMS conference controller: " + connection); 552 mImsConferenceController.remove(connection); 553 } 554 } 555} 556