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