CreateConnectionProcessor.java revision 92694519535c54f542b4ef3973e9c1934f2feeff
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.DisconnectCause; 21import android.telecom.ParcelableConnection; 22import android.telecom.Phone; 23import android.telecom.PhoneAccount; 24import android.telecom.PhoneAccountHandle; 25import android.telephony.TelephonyManager; 26import android.telephony.PhoneStateListener; 27import android.telephony.ServiceState; 28 29// TODO: Needed for move to system service: import com.android.internal.R; 30 31import java.util.ArrayList; 32import java.util.Collection; 33import java.util.HashSet; 34import java.util.Iterator; 35import java.util.List; 36import java.util.Set; 37import java.util.Objects; 38 39/** 40 * This class creates connections to place new outgoing calls or to attach to an existing incoming 41 * call. In either case, this class cycles through a set of connection services until: 42 * - a connection service returns a newly created connection in which case the call is displayed 43 * to the user 44 * - a connection service cancels the process, in which case the call is aborted 45 */ 46final class CreateConnectionProcessor { 47 48 // Describes information required to attempt to make a phone call 49 private static class CallAttemptRecord { 50 // The PhoneAccount describing the target connection service which we will 51 // contact in order to process an attempt 52 public final PhoneAccountHandle connectionManagerPhoneAccount; 53 // The PhoneAccount which we will tell the target connection service to use 54 // for attempting to make the actual phone call 55 public final PhoneAccountHandle targetPhoneAccount; 56 57 public CallAttemptRecord( 58 PhoneAccountHandle connectionManagerPhoneAccount, 59 PhoneAccountHandle targetPhoneAccount) { 60 this.connectionManagerPhoneAccount = connectionManagerPhoneAccount; 61 this.targetPhoneAccount = targetPhoneAccount; 62 } 63 64 @Override 65 public String toString() { 66 return "CallAttemptRecord(" 67 + Objects.toString(connectionManagerPhoneAccount) + "," 68 + Objects.toString(targetPhoneAccount) + ")"; 69 } 70 71 /** 72 * Determines if this instance of {@code CallAttemptRecord} has the same underlying 73 * {@code PhoneAccountHandle}s as another instance. 74 * 75 * @param obj The other instance to compare against. 76 * @return {@code True} if the {@code CallAttemptRecord}s are equal. 77 */ 78 @Override 79 public boolean equals(Object obj) { 80 if (obj instanceof CallAttemptRecord) { 81 CallAttemptRecord other = (CallAttemptRecord) obj; 82 return Objects.equals(connectionManagerPhoneAccount, 83 other.connectionManagerPhoneAccount) && 84 Objects.equals(targetPhoneAccount, other.targetPhoneAccount); 85 } 86 return false; 87 } 88 } 89 90 private final Call mCall; 91 private final ConnectionServiceRepository mRepository; 92 private List<CallAttemptRecord> mAttemptRecords; 93 private Iterator<CallAttemptRecord> mAttemptRecordIterator; 94 private CreateConnectionResponse mResponse; 95 private DisconnectCause mLastErrorDisconnectCause; 96 private final PhoneAccountRegistrar mPhoneAccountRegistrar; 97 private final Context mContext; 98 private boolean mShouldUseConnectionManager = true; 99 private CreateConnectionTimeout mTimeout; 100 101 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 mResponse = response; 108 mPhoneAccountRegistrar = phoneAccountRegistrar; 109 mContext = context; 110 } 111 112 boolean isProcessingComplete() { 113 return mResponse == null; 114 } 115 116 boolean isCallTimedOut() { 117 return mTimeout != null && mTimeout.isCallTimedOut(); 118 } 119 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 134 boolean hasMorePhoneAccounts() { 135 return mAttemptRecordIterator.hasNext(); 136 } 137 138 void continueProcessingIfPossible(CreateConnectionResponse response, 139 DisconnectCause disconnectCause) { 140 Log.v(this, "continueProcessingIfPossible"); 141 mResponse = response; 142 mLastErrorDisconnectCause = disconnectCause; 143 attemptNextPhoneAccount(); 144 } 145 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 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 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 235 private void clearTimeout() { 236 if (mTimeout != null) { 237 mTimeout.unregisterTimeout(); 238 mTimeout = null; 239 } 240 } 241 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 if (targetPhoneAccount == null) { 271 Log.d(this, "shouldSetConnectionManager, phone account not found"); 272 return false; 273 } 274 boolean isSimSubscription = (targetPhoneAccount.getCapabilities() & 275 PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0; 276 if (!isSimSubscription) { 277 return false; 278 } 279 280 return true; 281 } 282 283 // If there exists a registered connection manager then use it. 284 private void adjustAttemptsForConnectionManager() { 285 if (shouldSetConnectionManager()) { 286 CallAttemptRecord record = new CallAttemptRecord( 287 mPhoneAccountRegistrar.getSimCallManager(), 288 mAttemptRecords.get(0).targetPhoneAccount); 289 Log.v(this, "setConnectionManager, changing %s -> %s", mAttemptRecords.get(0), record); 290 mAttemptRecords.set(0, record); 291 } else { 292 Log.v(this, "setConnectionManager, not changing"); 293 } 294 } 295 296 // If we are possibly attempting to call a local emergency number, ensure that the 297 // plain PSTN connection services are listed, and nothing else. 298 private void adjustAttemptsForEmergency() { 299 if (TelephonyUtil.shouldProcessAsEmergency(mContext, mCall.getHandle())) { 300 Log.i(this, "Emergency number detected"); 301 mAttemptRecords.clear(); 302 List<PhoneAccount> allAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts(); 303 304 if (allAccounts.isEmpty()) { 305 // If the list of phone accounts is empty at this point, it means Telephony hasn't 306 // registered any phone accounts yet. Add a fallback emergency phone account so 307 // that emergency calls can still go through. We create a new ArrayLists here just 308 // in case the implementation of PhoneAccountRegistrar ever returns an unmodifiable 309 // list. 310 allAccounts = new ArrayList<PhoneAccount>(); 311 allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount()); 312 } 313 314 315 // First, add SIM phone accounts which can place emergency calls. 316 for (PhoneAccount phoneAccount : allAccounts) { 317 if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) && 318 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 319 Log.i(this, "Will try PSTN account %s for emergency", 320 phoneAccount.getAccountHandle()); 321 mAttemptRecords.add( 322 new CallAttemptRecord( 323 phoneAccount.getAccountHandle(), 324 phoneAccount.getAccountHandle())); 325 } 326 } 327 328 // Next, add the connection manager account as a backup if it can place emergency calls. 329 PhoneAccountHandle callManagerHandle = mPhoneAccountRegistrar.getSimCallManager(); 330 if (mShouldUseConnectionManager && callManagerHandle != null) { 331 PhoneAccount callManager = mPhoneAccountRegistrar 332 .getPhoneAccount(callManagerHandle); 333 if (callManager != null && callManager.hasCapabilities( 334 PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) { 335 CallAttemptRecord callAttemptRecord = new CallAttemptRecord(callManagerHandle, 336 mPhoneAccountRegistrar. 337 getDefaultOutgoingPhoneAccount(mCall.getHandle().getScheme()) 338 ); 339 340 if (!mAttemptRecords.contains(callAttemptRecord)) { 341 Log.i(this, "Will try Connection Manager account %s for emergency", 342 callManager); 343 mAttemptRecords.add(callAttemptRecord); 344 } 345 } 346 } 347 } 348 } 349 350 /** Returns all connection services used by the call attempt records. */ 351 private static Collection<PhoneAccountHandle> getConnectionServices( 352 List<CallAttemptRecord> records) { 353 HashSet<PhoneAccountHandle> result = new HashSet<>(); 354 for (CallAttemptRecord record : records) { 355 result.add(record.connectionManagerPhoneAccount); 356 } 357 return result; 358 } 359 360 private class Response implements CreateConnectionResponse { 361 private final ConnectionServiceWrapper mService; 362 363 Response(ConnectionServiceWrapper service) { 364 mService = service; 365 } 366 367 @Override 368 public void handleCreateConnectionSuccess( 369 CallIdMapper idMapper, 370 ParcelableConnection connection) { 371 if (mResponse == null) { 372 // Nobody is listening for this connection attempt any longer; ask the responsible 373 // ConnectionService to tear down any resources associated with the call 374 mService.abort(mCall); 375 } else { 376 // Success -- share the good news and remember that we are no longer interested 377 // in hearing about any more attempts 378 mResponse.handleCreateConnectionSuccess(idMapper, connection); 379 mResponse = null; 380 // If there's a timeout running then don't clear it. The timeout can be triggered 381 // after the call has successfully been created but before it has become active. 382 } 383 } 384 385 private boolean shouldFallbackToNoConnectionManager(DisconnectCause cause) { 386 PhoneAccountHandle handle = mCall.getConnectionManagerPhoneAccount(); 387 if (handle == null || !handle.equals(mPhoneAccountRegistrar.getSimCallManager())) { 388 return false; 389 } 390 391 ConnectionServiceWrapper connectionManager = mCall.getConnectionService(); 392 if (connectionManager == null) { 393 return false; 394 } 395 396 if (cause.getCode() == DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED) { 397 Log.d(CreateConnectionProcessor.this, "Connection manager declined to handle the " 398 + "call, falling back to not using a connection manager"); 399 return true; 400 } 401 402 if (!connectionManager.isServiceValid("createConnection")) { 403 Log.d(CreateConnectionProcessor.this, "Connection manager unbound while trying " 404 + "create a connection, falling back to not using a connection manager"); 405 return true; 406 } 407 408 return false; 409 } 410 411 @Override 412 public void handleCreateConnectionFailure(DisconnectCause errorDisconnectCause) { 413 // Failure of some sort; record the reasons for failure and try again if possible 414 Log.d(CreateConnectionProcessor.this, "Connection failed: (%s)", errorDisconnectCause); 415 mLastErrorDisconnectCause = errorDisconnectCause; 416 if (shouldFallbackToNoConnectionManager(errorDisconnectCause)) { 417 mShouldUseConnectionManager = false; 418 // Restart from the beginning. 419 process(); 420 } else { 421 attemptNextPhoneAccount(); 422 } 423 } 424 } 425} 426