1 /* 2 * Copyright 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 17 package com.android.server.telecom; 18 19 import android.content.Context; 20 import android.telecom.CallState; 21 import android.telecom.DisconnectCause; 22 import android.telecom.ParcelableConnection; 23 import android.telecom.Phone; 24 import android.telecom.PhoneAccount; 25 import android.telecom.PhoneAccountHandle; 26 import android.telephony.TelephonyManager; 27 import android.telephony.PhoneStateListener; 28 import android.telephony.ServiceState; 29 30 // TODO: Needed for move to system service: import com.android.internal.R; 31 32 import java.util.ArrayList; 33 import java.util.Collection; 34 import java.util.HashSet; 35 import java.util.Iterator; 36 import java.util.List; 37 import java.util.Set; 38 import java.util.Objects; 39 40 /** 41 * This class creates connections to place new outgoing calls or to attach to an existing incoming 42 * call. In either case, this class cycles through a set of connection services until: 43 * - a connection service returns a newly created connection in which case the call is displayed 44 * to the user 45 * - a connection service cancels the process, in which case the call is aborted 46 */ 47 final class CreateConnectionProcessor { 48 49 // Describes information required to attempt to make a phone call 50 private static class CallAttemptRecord { 51 // The PhoneAccount describing the target connection service which we will 52 // contact in order to process an attempt 53 public final PhoneAccountHandle connectionManagerPhoneAccount; 54 // The PhoneAccount which we will tell the target connection service to use 55 // for attempting to make the actual phone call 56 public final PhoneAccountHandle targetPhoneAccount; 57 CallAttemptRecord( PhoneAccountHandle connectionManagerPhoneAccount, PhoneAccountHandle targetPhoneAccount)58 public CallAttemptRecord( 59 PhoneAccountHandle connectionManagerPhoneAccount, 60 PhoneAccountHandle targetPhoneAccount) { 61 this.connectionManagerPhoneAccount = connectionManagerPhoneAccount; 62 this.targetPhoneAccount = targetPhoneAccount; 63 } 64 65 @Override toString()66 public String toString() { 67 return "CallAttemptRecord(" 68 + Objects.toString(connectionManagerPhoneAccount) + "," 69 + Objects.toString(targetPhoneAccount) + ")"; 70 } 71 72 /** 73 * Determines if this instance of {@code CallAttemptRecord} has the same underlying 74 * {@code PhoneAccountHandle}s as another instance. 75 * 76 * @param obj The other instance to compare against. 77 * @return {@code True} if the {@code CallAttemptRecord}s are equal. 78 */ 79 @Override equals(Object obj)80 public boolean equals(Object obj) { 81 if (obj instanceof CallAttemptRecord) { 82 CallAttemptRecord other = (CallAttemptRecord) obj; 83 return Objects.equals(connectionManagerPhoneAccount, 84 other.connectionManagerPhoneAccount) && 85 Objects.equals(targetPhoneAccount, other.targetPhoneAccount); 86 } 87 return false; 88 } 89 } 90 91 private final Call mCall; 92 private final ConnectionServiceRepository mRepository; 93 private List<CallAttemptRecord> mAttemptRecords; 94 private Iterator<CallAttemptRecord> mAttemptRecordIterator; 95 private CreateConnectionResponse mResponse; 96 private DisconnectCause mLastErrorDisconnectCause; 97 private final PhoneAccountRegistrar mPhoneAccountRegistrar; 98 private final Context mContext; 99 private boolean mShouldUseConnectionManager = true; 100 private CreateConnectionTimeout mTimeout; 101 CreateConnectionProcessor( Call call, ConnectionServiceRepository repository, CreateConnectionResponse response, PhoneAccountRegistrar phoneAccountRegistrar, Context context)102 CreateConnectionProcessor( 103 Call call, ConnectionServiceRepository repository, CreateConnectionResponse response, 104 PhoneAccountRegistrar phoneAccountRegistrar, Context context) { 105 mCall = call; 106 mRepository = repository; 107 mResponse = response; 108 mPhoneAccountRegistrar = phoneAccountRegistrar; 109 mContext = context; 110 } 111 isProcessingComplete()112 boolean isProcessingComplete() { 113 return mResponse == null; 114 } 115 isCallTimedOut()116 boolean isCallTimedOut() { 117 return mTimeout != null && mTimeout.isCallTimedOut(); 118 } 119 process()120 void process() { 121 Log.v(this, "process"); 122 clearTimeout(); 123 mAttemptRecords = new ArrayList<>(); 124 if (mCall.getTargetPhoneAccount() != null) { 125 mAttemptRecords.add(new CallAttemptRecord( 126 mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount())); 127 } 128 adjustAttemptsForConnectionManager(); 129 adjustAttemptsForEmergency(); 130 mAttemptRecordIterator = mAttemptRecords.iterator(); 131 attemptNextPhoneAccount(); 132 } 133 hasMorePhoneAccounts()134 boolean hasMorePhoneAccounts() { 135 return mAttemptRecordIterator.hasNext(); 136 } 137 continueProcessingIfPossible(CreateConnectionResponse response, DisconnectCause disconnectCause)138 void continueProcessingIfPossible(CreateConnectionResponse response, 139 DisconnectCause disconnectCause) { 140 Log.v(this, "continueProcessingIfPossible"); 141 mResponse = response; 142 mLastErrorDisconnectCause = disconnectCause; 143 attemptNextPhoneAccount(); 144 } 145 abort()146 void abort() { 147 Log.v(this, "abort"); 148 149 // Clear the response first to prevent attemptNextConnectionService from attempting any 150 // more services. 151 CreateConnectionResponse response = mResponse; 152 mResponse = null; 153 clearTimeout(); 154 155 ConnectionServiceWrapper service = mCall.getConnectionService(); 156 if (service != null) { 157 service.abort(mCall); 158 mCall.clearConnectionService(); 159 } 160 if (response != null) { 161 response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.LOCAL)); 162 } 163 } 164 attemptNextPhoneAccount()165 private void attemptNextPhoneAccount() { 166 Log.v(this, "attemptNextPhoneAccount"); 167 CallAttemptRecord attempt = null; 168 if (mAttemptRecordIterator.hasNext()) { 169 attempt = mAttemptRecordIterator.next(); 170 171 if (!mPhoneAccountRegistrar.phoneAccountHasPermission( 172 attempt.connectionManagerPhoneAccount)) { 173 Log.w(this, 174 "Connection mgr does not have BIND_CONNECTION_SERVICE for attempt: %s", 175 attempt); 176 attemptNextPhoneAccount(); 177 return; 178 } 179 180 // If the target PhoneAccount differs from the ConnectionManager phone acount, ensure it 181 // also has BIND_CONNECTION_SERVICE permission. 182 if (!attempt.connectionManagerPhoneAccount.equals(attempt.targetPhoneAccount) && 183 !mPhoneAccountRegistrar.phoneAccountHasPermission(attempt.targetPhoneAccount)) { 184 Log.w(this, 185 "Target PhoneAccount does not have BIND_CONNECTION_SERVICE for attempt: %s", 186 attempt); 187 attemptNextPhoneAccount(); 188 return; 189 } 190 } 191 192 if (mResponse != null && attempt != null) { 193 Log.i(this, "Trying attempt %s", attempt); 194 PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount; 195 ConnectionServiceWrapper service = 196 mRepository.getService( 197 phoneAccount.getComponentName(), 198 phoneAccount.getUserHandle()); 199 if (service == null) { 200 Log.i(this, "Found no connection service for attempt %s", attempt); 201 attemptNextPhoneAccount(); 202 } else { 203 mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount); 204 mCall.setTargetPhoneAccount(attempt.targetPhoneAccount); 205 mCall.setConnectionService(service); 206 setTimeoutIfNeeded(service, attempt); 207 208 Log.i(this, "Attempting to call from %s", service.getComponentName()); 209 service.createConnection(mCall, new Response(service)); 210 } 211 } else { 212 Log.v(this, "attemptNextPhoneAccount, no more accounts, failing"); 213 if (mResponse != null) { 214 clearTimeout(); 215 mResponse.handleCreateConnectionFailure(mLastErrorDisconnectCause != null ? 216 mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR)); 217 mResponse = null; 218 mCall.clearConnectionService(); 219 } 220 } 221 } 222 setTimeoutIfNeeded(ConnectionServiceWrapper service, CallAttemptRecord attempt)223 private void setTimeoutIfNeeded(ConnectionServiceWrapper service, CallAttemptRecord attempt) { 224 clearTimeout(); 225 226 CreateConnectionTimeout timeout = new CreateConnectionTimeout( 227 mContext, mPhoneAccountRegistrar, service, mCall); 228 if (timeout.isTimeoutNeededForCall(getConnectionServices(mAttemptRecords), 229 attempt.connectionManagerPhoneAccount)) { 230 mTimeout = timeout; 231 timeout.registerTimeout(); 232 } 233 } 234 clearTimeout()235 private void clearTimeout() { 236 if (mTimeout != null) { 237 mTimeout.unregisterTimeout(); 238 mTimeout = null; 239 } 240 } 241 shouldSetConnectionManager()242 private boolean shouldSetConnectionManager() { 243 if (!mShouldUseConnectionManager) { 244 return false; 245 } 246 247 if (mAttemptRecords.size() == 0) { 248 return false; 249 } 250 251 if (mAttemptRecords.size() > 1) { 252 Log.d(this, "shouldSetConnectionManager, error, mAttemptRecords should not have more " 253 + "than 1 record"); 254 return false; 255 } 256 257 PhoneAccountHandle connectionManager = mPhoneAccountRegistrar.getSimCallManager(); 258 if (connectionManager == null) { 259 return false; 260 } 261 262 PhoneAccountHandle targetPhoneAccountHandle = mAttemptRecords.get(0).targetPhoneAccount; 263 if (Objects.equals(connectionManager, targetPhoneAccountHandle)) { 264 return false; 265 } 266 267 // Connection managers are only allowed to manage SIM subscriptions. 268 PhoneAccount targetPhoneAccount = mPhoneAccountRegistrar.getPhoneAccount( 269 targetPhoneAccountHandle); 270 boolean isSimSubscription = (targetPhoneAccount.getCapabilities() & 271 PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0; 272 if (!isSimSubscription) { 273 return false; 274 } 275 276 return true; 277 } 278 279 // If there exists a registered connection manager then use it. adjustAttemptsForConnectionManager()280 private void adjustAttemptsForConnectionManager() { 281 if (shouldSetConnectionManager()) { 282 CallAttemptRecord record = new CallAttemptRecord( 283 mPhoneAccountRegistrar.getSimCallManager(), 284 mAttemptRecords.get(0).targetPhoneAccount); 285 Log.v(this, "setConnectionManager, changing %s -> %s", mAttemptRecords.get(0), record); 286 mAttemptRecords.set(0, record); 287 } else { 288 Log.v(this, "setConnectionManager, not changing"); 289 } 290 } 291 292 // If we are possibly attempting to call a local emergency number, ensure that the 293 // plain PSTN connection services are listed, and nothing else. adjustAttemptsForEmergency()294 private void adjustAttemptsForEmergency() { 295 if (TelephonyUtil.shouldProcessAsEmergency(mContext, mCall.getHandle())) { 296 Log.i(this, "Emergency number detected"); 297 mAttemptRecords.clear(); 298 List<PhoneAccount> allAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts(); 299 300 if (allAccounts.isEmpty()) { 301 // If the list of phone accounts is empty at this point, it means Telephony hasn't 302 // registered any phone accounts yet. Add a fallback emergency phone account so 303 // that emergency calls can still go through. We create a new ArrayLists here just 304 // in case the implementation of PhoneAccountRegistrar ever returns an unmodifiable 305 // list. 306 allAccounts = new ArrayList<PhoneAccount>(); 307 allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount()); 308 } 309 310 311 // First, add SIM phone accounts which can place emergency calls. 312 for (PhoneAccount phoneAccount : allAccounts) { 313 if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) && 314 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 315 Log.i(this, "Will try PSTN account %s for emergency", 316 phoneAccount.getAccountHandle()); 317 mAttemptRecords.add( 318 new CallAttemptRecord( 319 phoneAccount.getAccountHandle(), 320 phoneAccount.getAccountHandle())); 321 } 322 } 323 324 // Next, add the connection manager account as a backup if it can place emergency calls. 325 PhoneAccountHandle callManagerHandle = mPhoneAccountRegistrar.getSimCallManager(); 326 if (mShouldUseConnectionManager && callManagerHandle != null) { 327 PhoneAccount callManager = mPhoneAccountRegistrar 328 .getPhoneAccount(callManagerHandle); 329 if (callManager.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) { 330 CallAttemptRecord callAttemptRecord = new CallAttemptRecord(callManagerHandle, 331 mPhoneAccountRegistrar. 332 getDefaultOutgoingPhoneAccount(mCall.getHandle().getScheme()) 333 ); 334 335 if (!mAttemptRecords.contains(callAttemptRecord)) { 336 Log.i(this, "Will try Connection Manager account %s for emergency", 337 callManager); 338 mAttemptRecords.add(callAttemptRecord); 339 } 340 } 341 } 342 } 343 } 344 345 /** Returns all connection services used by the call attempt records. */ getConnectionServices( List<CallAttemptRecord> records)346 private static Collection<PhoneAccountHandle> getConnectionServices( 347 List<CallAttemptRecord> records) { 348 HashSet<PhoneAccountHandle> result = new HashSet<>(); 349 for (CallAttemptRecord record : records) { 350 result.add(record.connectionManagerPhoneAccount); 351 } 352 return result; 353 } 354 355 private class Response implements CreateConnectionResponse { 356 private final ConnectionServiceWrapper mService; 357 Response(ConnectionServiceWrapper service)358 Response(ConnectionServiceWrapper service) { 359 mService = service; 360 } 361 362 @Override handleCreateConnectionSuccess( CallIdMapper idMapper, ParcelableConnection connection)363 public void handleCreateConnectionSuccess( 364 CallIdMapper idMapper, 365 ParcelableConnection connection) { 366 if (mResponse == null) { 367 // Nobody is listening for this connection attempt any longer; ask the responsible 368 // ConnectionService to tear down any resources associated with the call 369 mService.abort(mCall); 370 } else { 371 // Success -- share the good news and remember that we are no longer interested 372 // in hearing about any more attempts 373 mResponse.handleCreateConnectionSuccess(idMapper, connection); 374 mResponse = null; 375 // If there's a timeout running then don't clear it. The timeout can be triggered 376 // after the call has successfully been created but before it has become active. 377 } 378 } 379 shouldFallbackToNoConnectionManager(DisconnectCause cause)380 private boolean shouldFallbackToNoConnectionManager(DisconnectCause cause) { 381 PhoneAccountHandle handle = mCall.getConnectionManagerPhoneAccount(); 382 if (handle == null || !handle.equals(mPhoneAccountRegistrar.getSimCallManager())) { 383 return false; 384 } 385 386 ConnectionServiceWrapper connectionManager = mCall.getConnectionService(); 387 if (connectionManager == null) { 388 return false; 389 } 390 391 if (cause.getCode() == DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED) { 392 Log.d(CreateConnectionProcessor.this, "Connection manager declined to handle the " 393 + "call, falling back to not using a connection manager"); 394 return true; 395 } 396 397 if (!connectionManager.isServiceValid("createConnection")) { 398 Log.d(CreateConnectionProcessor.this, "Connection manager unbound while trying " 399 + "create a connection, falling back to not using a connection manager"); 400 return true; 401 } 402 403 return false; 404 } 405 406 @Override handleCreateConnectionFailure(DisconnectCause errorDisconnectCause)407 public void handleCreateConnectionFailure(DisconnectCause errorDisconnectCause) { 408 // Failure of some sort; record the reasons for failure and try again if possible 409 Log.d(CreateConnectionProcessor.this, "Connection failed: (%s)", errorDisconnectCause); 410 mLastErrorDisconnectCause = errorDisconnectCause; 411 if (shouldFallbackToNoConnectionManager(errorDisconnectCause)) { 412 mShouldUseConnectionManager = false; 413 // Restart from the beginning. 414 process(); 415 } else { 416 attemptNextPhoneAccount(); 417 } 418 } 419 } 420 } 421