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