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