TelephonyConnectionService.java revision 87050c69ed4c62d5fddc494eb1a9b85a23d78214
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 (!phone.isRadioOn()) { 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 int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 338 if (e.getError() == CallStateException.ERROR_DISCONNECTED) { 339 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE; 340 } 341 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 342 cause, e.getMessage())); 343 return; 344 } 345 346 if (originalConnection == null) { 347 int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 348 // On GSM phones, null connection means that we dialed an MMI code 349 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 350 Log.d(this, "dialed MMI code"); 351 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; 352 final Intent intent = new Intent(this, MMIDialogActivity.class); 353 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 354 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 355 startActivity(intent); 356 } 357 Log.d(this, "placeOutgoingConnection, phone.dial returned null"); 358 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 359 telephonyDisconnectCause, "Connection is null")); 360 } else { 361 connection.setOriginalConnection(originalConnection); 362 } 363 } 364 365 private TelephonyConnection createConnectionFor( 366 Phone phone, 367 com.android.internal.telephony.Connection originalConnection, 368 boolean isOutgoing) { 369 TelephonyConnection returnConnection = null; 370 int phoneType = phone.getPhoneType(); 371 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 372 returnConnection = new GsmConnection(originalConnection); 373 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 374 boolean allowMute = allowMute(phone); 375 returnConnection = new CdmaConnection( 376 originalConnection, mEmergencyTonePlayer, allowMute, isOutgoing); 377 } 378 if (returnConnection != null) { 379 // Listen to Telephony specific callbacks from the connection 380 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener); 381 } 382 return returnConnection; 383 } 384 385 private boolean isOriginalConnectionKnown( 386 com.android.internal.telephony.Connection originalConnection) { 387 for (Connection connection : getAllConnections()) { 388 if (connection instanceof TelephonyConnection) { 389 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 390 if (telephonyConnection.getOriginalConnection() == originalConnection) { 391 return true; 392 } 393 } 394 } 395 return false; 396 } 397 398 private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) { 399 if (Objects.equals(mExpectedComponentName, accountHandle.getComponentName())) { 400 if (accountHandle.getId() != null) { 401 try { 402 int phoneId = SubscriptionController.getInstance().getPhoneId( 403 Integer.parseInt(accountHandle.getId())); 404 return PhoneFactory.getPhone(phoneId); 405 } catch (NumberFormatException e) { 406 Log.w(this, "Could not get subId from account: " + accountHandle.getId()); 407 } 408 } 409 } 410 411 if (isEmergency) { 412 // If this is an emergency number and we've been asked to dial it using a PhoneAccount 413 // which does not exist, then default to whatever subscription is available currently. 414 return getFirstPhoneForEmergencyCall(); 415 } 416 417 return null; 418 } 419 420 private Phone getFirstPhoneForEmergencyCall() { 421 Phone selectPhone = null; 422 for (int i = 0; i < TelephonyManager.getDefault().getSimCount(); i++) { 423 int[] subIds = SubscriptionController.getInstance().getSubIdUsingSlotId(i); 424 if (subIds.length == 0) 425 continue; 426 427 int phoneId = SubscriptionController.getInstance().getPhoneId(subIds[0]); 428 Phone phone = PhoneFactory.getPhone(phoneId); 429 if (phone == null) 430 continue; 431 432 if (ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState()) { 433 // the slot is radio on & state is in service 434 Log.d(this, "pickBestPhoneForEmergencyCall, radio on & in service, slotId:" + i); 435 return phone; 436 } else if (ServiceState.STATE_POWER_OFF != phone.getServiceState().getState()) { 437 // the slot is radio on & with SIM card inserted. 438 if (TelephonyManager.getDefault().hasIccCard(i)) { 439 Log.d(this, "pickBestPhoneForEmergencyCall," + 440 "radio on and SIM card inserted, slotId:" + i); 441 selectPhone = phone; 442 } else if (selectPhone == null) { 443 Log.d(this, "pickBestPhoneForEmergencyCall, radio on, slotId:" + i); 444 selectPhone = phone; 445 } 446 } 447 } 448 449 if (selectPhone == null) { 450 Log.d(this, "pickBestPhoneForEmergencyCall, return default phone"); 451 selectPhone = PhoneFactory.getDefaultPhone(); 452 } 453 454 return selectPhone; 455 } 456 457 /** 458 * Determines if the connection should allow mute. 459 * 460 * @param phone The current phone. 461 * @return {@code True} if the connection should allow mute. 462 */ 463 private boolean allowMute(Phone phone) { 464 // For CDMA phones, check if we are in Emergency Callback Mode (ECM). Mute is disallowed 465 // in ECM mode. 466 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 467 PhoneProxy phoneProxy = (PhoneProxy)phone; 468 CDMAPhone cdmaPhone = (CDMAPhone)phoneProxy.getActivePhone(); 469 if (cdmaPhone != null) { 470 if (cdmaPhone.isInEcm()) { 471 return false; 472 } 473 } 474 } 475 476 return true; 477 } 478 479 @Override 480 public void removeConnection(Connection connection) { 481 super.removeConnection(connection); 482 if (connection instanceof TelephonyConnection) { 483 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 484 telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener); 485 } 486 } 487 488 /** 489 * When a {@link TelephonyConnection} has its underlying original connection configured, 490 * we need to add it to the correct conference controller. 491 * 492 * @param connection The connection to be added to the controller 493 */ 494 public void addConnectionToConferenceController(TelephonyConnection connection) { 495 // TODO: Do we need to handle the case of the original connection changing 496 // and triggering this callback multiple times for the same connection? 497 // If that is the case, we might want to remove this connection from all 498 // conference controllers first before re-adding it. 499 if (connection.isImsConnection()) { 500 Log.d(this, "Adding IMS connection to conference controller: " + connection); 501 mImsConferenceController.add(connection); 502 } else { 503 int phoneType = connection.getCall().getPhone().getPhoneType(); 504 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 505 Log.d(this, "Adding GSM connection to conference controller: " + connection); 506 mTelephonyConferenceController.add(connection); 507 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA && 508 connection instanceof CdmaConnection) { 509 Log.d(this, "Adding CDMA connection to conference controller: " + connection); 510 mCdmaConferenceController.add((CdmaConnection)connection); 511 } 512 } 513 } 514} 515