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