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