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 GsmConferenceController mGsmConferenceController = 52 new GsmConferenceController(this); 53 private final CdmaConferenceController mCdmaConferenceController = 54 new CdmaConferenceController(this); 55 private ComponentName mExpectedComponentName = null; 56 private EmergencyCallHelper mEmergencyCallHelper; 57 private EmergencyTonePlayer mEmergencyTonePlayer; 58 59 @Override 60 public void onCreate() { 61 super.onCreate(); 62 mExpectedComponentName = new ComponentName(this, this.getClass()); 63 mEmergencyTonePlayer = new EmergencyTonePlayer(this); 64 } 65 66 @Override 67 public Connection onCreateOutgoingConnection( 68 PhoneAccountHandle connectionManagerPhoneAccount, 69 final ConnectionRequest request) { 70 Log.i(this, "onCreateOutgoingConnection, request: " + request); 71 72 Uri handle = request.getAddress(); 73 if (handle == null) { 74 Log.d(this, "onCreateOutgoingConnection, handle is null"); 75 return Connection.createFailedConnection( 76 DisconnectCauseUtil.toTelecomDisconnectCause( 77 android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, 78 "No phone number supplied")); 79 } 80 81 String scheme = handle.getScheme(); 82 final String number; 83 if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) { 84 // TODO: We don't check for SecurityException here (requires 85 // CALL_PRIVILEGED permission). 86 final Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 87 if (phone == null) { 88 Log.d(this, "onCreateOutgoingConnection, phone is null"); 89 return Connection.createFailedConnection( 90 DisconnectCauseUtil.toTelecomDisconnectCause( 91 android.telephony.DisconnectCause.OUTGOING_FAILURE, 92 "Phone is null")); 93 } 94 number = phone.getVoiceMailNumber(); 95 if (TextUtils.isEmpty(number)) { 96 Log.d(this, "onCreateOutgoingConnection, no voicemail number set."); 97 return Connection.createFailedConnection( 98 DisconnectCauseUtil.toTelecomDisconnectCause( 99 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING, 100 "Voicemail scheme provided but no voicemail number set.")); 101 } 102 103 // Convert voicemail: to tel: 104 handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); 105 } else { 106 if (!PhoneAccount.SCHEME_TEL.equals(scheme)) { 107 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme); 108 return Connection.createFailedConnection( 109 DisconnectCauseUtil.toTelecomDisconnectCause( 110 android.telephony.DisconnectCause.INVALID_NUMBER, 111 "Handle scheme is not type tel")); 112 } 113 114 number = handle.getSchemeSpecificPart(); 115 if (TextUtils.isEmpty(number)) { 116 Log.d(this, "onCreateOutgoingConnection, unable to parse number"); 117 return Connection.createFailedConnection( 118 DisconnectCauseUtil.toTelecomDisconnectCause( 119 android.telephony.DisconnectCause.INVALID_NUMBER, 120 "Unable to parse number")); 121 } 122 } 123 124 boolean isEmergencyNumber = PhoneNumberUtils.isPotentialEmergencyNumber(number); 125 126 // Get the right phone object from the account data passed in. 127 final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber); 128 if (phone == null) { 129 Log.d(this, "onCreateOutgoingConnection, phone is null"); 130 return Connection.createFailedConnection( 131 DisconnectCauseUtil.toTelecomDisconnectCause( 132 android.telephony.DisconnectCause.OUTGOING_FAILURE, "Phone is null")); 133 } 134 135 int state = phone.getServiceState().getState(); 136 boolean useEmergencyCallHelper = false; 137 138 if (isEmergencyNumber) { 139 if (state == ServiceState.STATE_POWER_OFF) { 140 useEmergencyCallHelper = true; 141 } 142 } else { 143 switch (state) { 144 case ServiceState.STATE_IN_SERVICE: 145 case ServiceState.STATE_EMERGENCY_ONLY: 146 break; 147 case ServiceState.STATE_OUT_OF_SERVICE: 148 return Connection.createFailedConnection( 149 DisconnectCauseUtil.toTelecomDisconnectCause( 150 android.telephony.DisconnectCause.OUT_OF_SERVICE, 151 "ServiceState.STATE_OUT_OF_SERVICE")); 152 case ServiceState.STATE_POWER_OFF: 153 return Connection.createFailedConnection( 154 DisconnectCauseUtil.toTelecomDisconnectCause( 155 android.telephony.DisconnectCause.POWER_OFF, 156 "ServiceState.STATE_POWER_OFF")); 157 default: 158 Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state); 159 return Connection.createFailedConnection( 160 DisconnectCauseUtil.toTelecomDisconnectCause( 161 android.telephony.DisconnectCause.OUTGOING_FAILURE, 162 "Unknown service state " + state)); 163 } 164 } 165 166 final TelephonyConnection connection = 167 createConnectionFor(phone, null, true /* isOutgoing */); 168 if (connection == null) { 169 return Connection.createFailedConnection( 170 DisconnectCauseUtil.toTelecomDisconnectCause( 171 android.telephony.DisconnectCause.OUTGOING_FAILURE, 172 "Invalid phone type")); 173 } 174 connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED); 175 connection.setInitializing(); 176 connection.setVideoState(request.getVideoState()); 177 178 if (useEmergencyCallHelper) { 179 if (mEmergencyCallHelper == null) { 180 mEmergencyCallHelper = new EmergencyCallHelper(this); 181 } 182 mEmergencyCallHelper.startTurnOnRadioSequence(phone, 183 new EmergencyCallHelper.Callback() { 184 @Override 185 public void onComplete(boolean isRadioReady) { 186 if (connection.getState() == Connection.STATE_DISCONNECTED) { 187 // If the connection has already been disconnected, do nothing. 188 } else if (isRadioReady) { 189 connection.setInitialized(); 190 placeOutgoingConnection(connection, phone, request); 191 } else { 192 Log.d(this, "onCreateOutgoingConnection, failed to turn on radio"); 193 connection.setDisconnected( 194 DisconnectCauseUtil.toTelecomDisconnectCause( 195 android.telephony.DisconnectCause.POWER_OFF, 196 "Failed to turn on radio.")); 197 connection.destroy(); 198 } 199 } 200 }); 201 202 } else { 203 placeOutgoingConnection(connection, phone, request); 204 } 205 206 return connection; 207 } 208 209 @Override 210 public Connection onCreateIncomingConnection( 211 PhoneAccountHandle connectionManagerPhoneAccount, 212 ConnectionRequest request) { 213 Log.i(this, "onCreateIncomingConnection, request: " + request); 214 215 Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 216 if (phone == null) { 217 return Connection.createFailedConnection( 218 DisconnectCauseUtil.toTelecomDisconnectCause( 219 android.telephony.DisconnectCause.ERROR_UNSPECIFIED)); 220 } 221 222 Call call = phone.getRingingCall(); 223 if (!call.getState().isRinging()) { 224 Log.i(this, "onCreateIncomingConnection, no ringing call"); 225 return Connection.createFailedConnection( 226 DisconnectCauseUtil.toTelecomDisconnectCause( 227 android.telephony.DisconnectCause.INCOMING_MISSED, 228 "Found no ringing call")); 229 } 230 231 com.android.internal.telephony.Connection originalConnection = 232 call.getState() == Call.State.WAITING ? 233 call.getLatestConnection() : call.getEarliestConnection(); 234 if (isOriginalConnectionKnown(originalConnection)) { 235 Log.i(this, "onCreateIncomingConnection, original connection already registered"); 236 return Connection.createCanceledConnection(); 237 } 238 239 Connection connection = 240 createConnectionFor(phone, originalConnection, false /* isOutgoing */); 241 if (connection == null) { 242 connection = Connection.createCanceledConnection(); 243 return Connection.createCanceledConnection(); 244 } else { 245 return connection; 246 } 247 } 248 249 @Override 250 public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, 251 ConnectionRequest request) { 252 Log.i(this, "onCreateUnknownConnection, request: " + request); 253 254 Phone phone = getPhoneForAccount(request.getAccountHandle(), false); 255 if (phone == null) { 256 return Connection.createFailedConnection( 257 DisconnectCauseUtil.toTelecomDisconnectCause( 258 android.telephony.DisconnectCause.ERROR_UNSPECIFIED)); 259 } 260 261 final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>(); 262 final Call ringingCall = phone.getRingingCall(); 263 if (ringingCall.hasConnections()) { 264 allConnections.addAll(ringingCall.getConnections()); 265 } 266 final Call foregroundCall = phone.getForegroundCall(); 267 if (foregroundCall.hasConnections()) { 268 allConnections.addAll(foregroundCall.getConnections()); 269 } 270 final Call backgroundCall = phone.getBackgroundCall(); 271 if (backgroundCall.hasConnections()) { 272 allConnections.addAll(phone.getBackgroundCall().getConnections()); 273 } 274 275 com.android.internal.telephony.Connection unknownConnection = null; 276 for (com.android.internal.telephony.Connection telephonyConnection : allConnections) { 277 if (!isOriginalConnectionKnown(telephonyConnection)) { 278 unknownConnection = telephonyConnection; 279 break; 280 } 281 } 282 283 if (unknownConnection == null) { 284 Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection."); 285 return Connection.createCanceledConnection(); 286 } 287 288 TelephonyConnection connection = 289 createConnectionFor(phone, unknownConnection, 290 !unknownConnection.isIncoming() /* isOutgoing */); 291 292 if (connection == null) { 293 return Connection.createCanceledConnection(); 294 } else { 295 connection.updateState(); 296 return connection; 297 } 298 } 299 300 @Override 301 public void onConference(Connection connection1, Connection connection2) { 302 if (connection1 instanceof TelephonyConnection && 303 connection2 instanceof TelephonyConnection) { 304 ((TelephonyConnection) connection1).performConference( 305 (TelephonyConnection) connection2); 306 } 307 308 } 309 310 private void placeOutgoingConnection( 311 TelephonyConnection connection, Phone phone, ConnectionRequest request) { 312 String number = connection.getAddress().getSchemeSpecificPart(); 313 314 com.android.internal.telephony.Connection originalConnection; 315 try { 316 originalConnection = phone.dial(number, request.getVideoState()); 317 } catch (CallStateException e) { 318 Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e); 319 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 320 android.telephony.DisconnectCause.OUTGOING_FAILURE, 321 e.getMessage())); 322 return; 323 } 324 325 if (originalConnection == null) { 326 int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE; 327 // On GSM phones, null connection means that we dialed an MMI code 328 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 329 Log.d(this, "dialed MMI code"); 330 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI; 331 final Intent intent = new Intent(this, MMIDialogActivity.class); 332 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 333 Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); 334 startActivity(intent); 335 } 336 Log.d(this, "placeOutgoingConnection, phone.dial returned null"); 337 connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause( 338 telephonyDisconnectCause, "Connection is null")); 339 } else { 340 connection.setOriginalConnection(originalConnection); 341 } 342 } 343 344 private TelephonyConnection createConnectionFor( 345 Phone phone, 346 com.android.internal.telephony.Connection originalConnection, 347 boolean isOutgoing) { 348 int phoneType = phone.getPhoneType(); 349 if (phoneType == TelephonyManager.PHONE_TYPE_GSM) { 350 GsmConnection connection = new GsmConnection(originalConnection); 351 mGsmConferenceController.add(connection); 352 return connection; 353 } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) { 354 boolean allowMute = allowMute(phone); 355 CdmaConnection connection = new CdmaConnection( 356 originalConnection, mEmergencyTonePlayer, allowMute, isOutgoing); 357 mCdmaConferenceController.add(connection); 358 return connection; 359 } else { 360 return null; 361 } 362 } 363 364 private boolean isOriginalConnectionKnown( 365 com.android.internal.telephony.Connection originalConnection) { 366 for (Connection connection : getAllConnections()) { 367 TelephonyConnection telephonyConnection = (TelephonyConnection) connection; 368 if (connection instanceof TelephonyConnection) { 369 if (telephonyConnection.getOriginalConnection() == originalConnection) { 370 return true; 371 } 372 } 373 } 374 return false; 375 } 376 377 private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) { 378 if (isEmergency) { 379 return PhoneFactory.getDefaultPhone(); 380 } 381 382 if (Objects.equals(mExpectedComponentName, accountHandle.getComponentName())) { 383 if (accountHandle.getId() != null) { 384 try { 385 int phoneId = SubscriptionController.getInstance().getPhoneId( 386 Long.parseLong(accountHandle.getId())); 387 return PhoneFactory.getPhone(phoneId); 388 } catch (NumberFormatException e) { 389 Log.w(this, "Could not get subId from account: " + accountHandle.getId()); 390 } 391 } 392 } 393 return null; 394 } 395 396 /** 397 * Determines if the connection should allow mute. 398 * 399 * @param phone The current phone. 400 * @return {@code True} if the connection should allow mute. 401 */ 402 private boolean allowMute(Phone phone) { 403 // For CDMA phones, check if we are in Emergency Callback Mode (ECM). Mute is disallowed 404 // in ECM mode. 405 if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 406 PhoneProxy phoneProxy = (PhoneProxy)phone; 407 CDMAPhone cdmaPhone = (CDMAPhone)phoneProxy.getActivePhone(); 408 if (cdmaPhone != null) { 409 if (cdmaPhone.isInEcm()) { 410 return false; 411 } 412 } 413 } 414 415 return true; 416 } 417} 418