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