TelephonyConnectionService.java revision 00bce13d80bfe8f8b2fff17c75fe2b27b57568f6
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.DisconnectCause; 26import android.telecom.PhoneAccount; 27import android.telecom.PhoneAccountHandle; 28import android.telephony.PhoneNumberUtils; 29import android.telephony.ServiceState; 30import android.telephony.TelephonyManager; 31import android.text.TextUtils; 32 33import com.android.internal.telephony.Call; 34import com.android.internal.telephony.CallStateException; 35import com.android.internal.telephony.Phone; 36import com.android.internal.telephony.PhoneConstants; 37import com.android.internal.telephony.PhoneFactory; 38import com.android.internal.telephony.PhoneProxy; 39import com.android.internal.telephony.SubscriptionController; 40import com.android.internal.telephony.cdma.CDMAPhone; 41import com.android.phone.MMIDialogActivity; 42 43import java.util.ArrayList; 44import java.util.List; 45import java.util.Objects; 46 47/** 48 * Service for making GSM and CDMA connections. 49 */ 50public class TelephonyConnectionService extends ConnectionService { 51 private final TelephonyConferenceController mTelephonyConferenceController = 52 new TelephonyConferenceController(this); 53 private final CdmaConferenceController mCdmaConferenceController = 54 new CdmaConferenceController(this); 55 private final ImsConferenceController mImsConferenceController = 56 new ImsConferenceController(this); 57 private ComponentName mExpectedComponentName = null; 58 private EmergencyCallHelper mEmergencyCallHelper; 59 private EmergencyTonePlayer mEmergencyTonePlayer; 60 61 /** 62 * A listener to actionable events specific to the TelephonyConnection. 63 */ 64 private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener = 65 new TelephonyConnection.TelephonyConnectionListener() { 66 @Override 67 public void onOriginalConnectionConfigured(TelephonyConnection c) { 68 addConnectionToConferenceController(c); 69 } 70 }; 71 72 @Override 73 public void onCreate() { 74 super.onCreate(); 75 mExpectedComponentName = new ComponentName(this, this.getClass()); 76 mEmergencyTonePlayer = new EmergencyTonePlayer(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 int state = phone.getServiceState().getState(); 149 boolean useEmergencyCallHelper = false; 150 151 if (isEmergencyNumber) { 152 if (state == ServiceState.STATE_POWER_OFF) { 153 useEmergencyCallHelper = true; 154 } 155 } else { 156 switch (state) { 157 case ServiceState.STATE_IN_SERVICE: 158 case ServiceState.STATE_EMERGENCY_ONLY: 159 break; 160 case ServiceState.STATE_OUT_OF_SERVICE: 161 return Connection.createFailedConnection( 162 DisconnectCauseUtil.toTelecomDisconnectCause( 163 android.telephony.DisconnectCause.OUT_OF_SERVICE, 164 "ServiceState.STATE_OUT_OF_SERVICE")); 165 case ServiceState.STATE_POWER_OFF: 166 return Connection.createFailedConnection( 167 DisconnectCauseUtil.toTelecomDisconnectCause( 168 android.telephony.DisconnectCause.POWER_OFF, 169 "ServiceState.STATE_POWER_OFF")); 170 default: 171 Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state); 172 return Connection.createFailedConnection( 173 DisconnectCauseUtil.toTelecomDisconnectCause( 174 android.telephony.DisconnectCause.OUTGOING_FAILURE, 175 "Unknown service state " + state)); 176 } 177 } 178 179 final TelephonyConnection connection = 180 createConnectionFor(phone, null, true /* isOutgoing */); 181 if (connection == null) { 182 return Connection.createFailedConnection( 183 DisconnectCauseUtil.toTelecomDisconnectCause( 184 android.telephony.DisconnectCause.OUTGOING_FAILURE, 185 "Invalid phone type")); 186 } 187 connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED); 188 connection.setInitializing(); 189 connection.setVideoState(request.getVideoState()); 190 191 if (useEmergencyCallHelper) { 192 if (mEmergencyCallHelper == null) { 193 mEmergencyCallHelper = new EmergencyCallHelper(this); 194 } 195 mEmergencyCallHelper.startTurnOnRadioSequence(phone, 196 new EmergencyCallHelper.Callback() { 197 @Override 198 public void onComplete(boolean isRadioReady) { 199 if (connection.getState() == Connection.STATE_DISCONNECTED) { 200 // If the connection has already been disconnected, do nothing. 201 } else if (isRadioReady) { 202 connection.setInitialized(); 203 placeOutgoingConnection(connection, phone, request); 204 } else { 205 Log.d(this, "onCreateOutgoingConnection, failed to turn on radio"); 206 connection.setDisconnected( 207 DisconnectCauseUtil.toTelecomDisconnectCause( 208 android.telephony.DisconnectCause.POWER_OFF, 209 "Failed to turn on radio.")); 210 connection.destroy(); 211 } 212 } 213 }); 214 215 } else { 216 placeOutgoingConnection(connection, phone, request); 217 } 218 219 return connection; 220 } 221 222 @Override 223 public Connection onCreateIncomingConnection( 224 PhoneAccountHandle connectionManagerPhoneAccount, 225 ConnectionRequest request) { 226 Log.i(this, "onCreateIncomingConnection, request: " + request); 227 228 Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 229 if (phone == null) { 230 return Connection.createFailedConnection( 231 DisconnectCauseUtil.toTelecomDisconnectCause( 232 android.telephony.DisconnectCause.ERROR_UNSPECIFIED)); 233 } 234 235 Call call = phone.getRingingCall(); 236 if (!call.getState().isRinging()) { 237 Log.i(this, "onCreateIncomingConnection, no ringing call"); 238 return Connection.createFailedConnection( 239 DisconnectCauseUtil.toTelecomDisconnectCause( 240 android.telephony.DisconnectCause.INCOMING_MISSED, 241 "Found no ringing call")); 242 } 243 244 com.android.internal.telephony.Connection originalConnection = 245 call.getState() == Call.State.WAITING ? 246 call.getLatestConnection() : call.getEarliestConnection(); 247 if (isOriginalConnectionKnown(originalConnection)) { 248 Log.i(this, "onCreateIncomingConnection, original connection already registered"); 249 return Connection.createCanceledConnection(); 250 } 251 252 Connection connection = 253 createConnectionFor(phone, originalConnection, false /* isOutgoing */); 254 if (connection == null) { 255 connection = Connection.createCanceledConnection(); 256 return Connection.createCanceledConnection(); 257 } else { 258 return connection; 259 } 260 } 261 262 @Override 263 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 264 ConnectionRequest request) { 265 Log.i(this, "onCreateUnknownConnection, request: " + request); 266 267 Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 268 if (phone == null) { 269 return Connection.createFailedConnection( 270 DisconnectCauseUtil.toTelecomDisconnectCause( 271 android.telephony.DisconnectCause.ERROR_UNSPECIFIED)); 272 } 273 274 final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>(); 275 final Call ringingCall = phone.getRingingCall(); 276 if (ringingCall.hasConnections()) { 277 allConnections.addAll(ringingCall.getConnections()); 278 } 279 final Call foregroundCall = phone.getForegroundCall(); 280 if (foregroundCall.hasConnections()) { 281 allConnections.addAll(foregroundCall.getConnections()); 282 } 283 final Call backgroundCall = phone.getBackgroundCall(); 284 if (backgroundCall.hasConnections()) { 285 allConnections.addAll(phone.getBackgroundCall().getConnections()); 286 } 287 288 com.android.internal.telephony.Connection unknownConnection = null; 289 for (com.android.internal.telephony.Connection telephonyConnection : allConnections) { 290 if (!isOriginalConnectionKnown(telephonyConnection)) { 291 unknownConnection = telephonyConnection; 292 break; 293 } 294 } 295 296 if (unknownConnection == null) { 297 Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection."); 298 return Connection.createCanceledConnection(); 299 } 300 301 TelephonyConnection connection = 302 createConnectionFor(phone, unknownConnection, 303 !unknownConnection.isIncoming() /* isOutgoing */); 304 305 if (connection == null) { 306 return Connection.createCanceledConnection(); 307 } else { 308 connection.updateState(); 309 return connection; 310 } 311 } 312 313 @Override 314 public void onConference(Connection connection1, Connection connection2) { 315 if (connection1 instanceof TelephonyConnection && 316 connection2 instanceof TelephonyConnection) { 317 ((TelephonyConnection) connection1).performConference( 318 (TelephonyConnection) connection2); 319 } 320 321 } 322 323 private void placeOutgoingConnection( 324 TelephonyConnection connection, Phone phone, ConnectionRequest request) { 325 String number = connection.getAddress().getSchemeSpecificPart(); 326 327 com.android.internal.telephony.Connection originalConnection; 328 try { 329 originalConnection = phone.dial(number, request.getVideoState()); 330 } catch (CallStateException e) { 331 Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e); 332 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 333 android.telephony.DisconnectCause.OUTGOING_FAILURE, 334 e.getMessage())); 335 return; 336 } 337 338 if (originalConnection == null) { 339 int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 340 // On GSM phones, null connection means that we dialed an MMI code 341 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 342 Log.d(this, "dialed MMI code"); 343 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; 344 final Intent intent = new Intent(this, MMIDialogActivity.class); 345 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 346 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 347 startActivity(intent); 348 } 349 Log.d(this, "placeOutgoingConnection, phone.dial returned null"); 350 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 351 telephonyDisconnectCause, "Connection is null")); 352 } else { 353 connection.setOriginalConnection(originalConnection); 354 } 355 } 356 357 private TelephonyConnection createConnectionFor( 358 Phone phone, 359 com.android.internal.telephony.Connection originalConnection, 360 boolean isOutgoing) { 361 TelephonyConnection returnConnection = null; 362 int phoneType = phone.getPhoneType(); 363 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 364 returnConnection = new GsmConnection(originalConnection); 365 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 366 boolean allowMute = allowMute(phone); 367 returnConnection = new CdmaConnection( 368 originalConnection, mEmergencyTonePlayer, allowMute, isOutgoing); 369 } 370 if (returnConnection != null) { 371 // Listen to Telephony specific callbacks from the connection 372 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener); 373 } 374 return returnConnection; 375 } 376 377 private boolean isOriginalConnectionKnown( 378 com.android.internal.telephony.Connection originalConnection) { 379 for (Connection connection : getAllConnections()) { 380 if (connection instanceof TelephonyConnection) { 381 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 382 if (telephonyConnection.getOriginalConnection() == originalConnection) { 383 return true; 384 } 385 } 386 } 387 return false; 388 } 389 390 private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) { 391 if (isEmergency) { 392 return PhoneFactory.getDefaultPhone(); 393 } 394 395 if (Objects.equals(mExpectedComponentName, accountHandle.getComponentName())) { 396 if (accountHandle.getId() != null) { 397 try { 398 int phoneId = SubscriptionController.getInstance().getPhoneId( 399 Integer.parseInt(accountHandle.getId())); 400 return PhoneFactory.getPhone(phoneId); 401 } catch (NumberFormatException e) { 402 Log.w(this, "Could not get subId from account: " + accountHandle.getId()); 403 } 404 } 405 } 406 return null; 407 } 408 409 /** 410 * Determines if the connection should allow mute. 411 * 412 * @param phone The current phone. 413 * @return {@code True} if the connection should allow mute. 414 */ 415 private boolean allowMute(Phone phone) { 416 // For CDMA phones, check if we are in Emergency Callback Mode (ECM). Mute is disallowed 417 // in ECM mode. 418 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 419 PhoneProxy phoneProxy = (PhoneProxy)phone; 420 CDMAPhone cdmaPhone = (CDMAPhone)phoneProxy.getActivePhone(); 421 if (cdmaPhone != null) { 422 if (cdmaPhone.isInEcm()) { 423 return false; 424 } 425 } 426 } 427 428 return true; 429 } 430 431 @Override 432 public void removeConnection(Connection connection) { 433 super.removeConnection(connection); 434 if (connection instanceof TelephonyConnection) { 435 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 436 telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener); 437 } 438 } 439 440 /** 441 * When a {@link TelephonyConnection} has its underlying original connection configured, 442 * we need to add it to the correct conference controller. 443 * 444 * @param connection The connection to be added to the controller 445 */ 446 public void addConnectionToConferenceController(TelephonyConnection connection) { 447 // TODO: Do we need to handle the case of the original connection changing 448 // and triggering this callback multiple times for the same connection? 449 // If that is the case, we might want to remove this connection from all 450 // conference controllers first before re-adding it. 451 if (connection.isImsConnection()) { 452 Log.d(this, "Adding IMS connection to conference controller: " + connection); 453 mImsConferenceController.add(connection); 454 } else { 455 int phoneType = connection.getCall().getPhone().getPhoneType(); 456 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 457 Log.d(this, "Adding GSM connection to conference controller: " + connection); 458 mTelephonyConferenceController.add(connection); 459 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA && 460 connection instanceof CdmaConnection) { 461 Log.d(this, "Adding CDMA connection to conference controller: " + connection); 462 mCdmaConferenceController.add((CdmaConnection)connection); 463 } 464 } 465 } 466} 467