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.phoneAccountRequiresBindPermission( 172 attempt.connectionManagerPhoneAccount)) { 173 Log.w(this, 174 "Connection mgr does not have BIND_TELECOM_CONNECTION_SERVICE for " 175 + "attempt: %s", attempt); 176 attemptNextPhoneAccount(); 177 return; 178 } 179 180 // If the target PhoneAccount differs from the ConnectionManager phone acount, ensure it 181 // also requires the BIND_TELECOM_CONNECTION_SERVICE permission. 182 if (!attempt.connectionManagerPhoneAccount.equals(attempt.targetPhoneAccount) && 183 !mPhoneAccountRegistrar.phoneAccountRequiresBindPermission( 184 attempt.targetPhoneAccount)) { 185 Log.w(this, 186 "Target PhoneAccount does not have BIND_TELECOM_CONNECTION_SERVICE for " 187 + "attempt: %s", 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 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 // TODO: Should this really be checking the "calling user" test for phone account? 269 PhoneAccount targetPhoneAccount = mPhoneAccountRegistrar.getPhoneAccountCheckCallingUser( 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 // TODO: Should this really be checking the "calling user" test for phone account? 333 PhoneAccount callManager = mPhoneAccountRegistrar 334 .getPhoneAccountCheckCallingUser(callManagerHandle); 335 if (callManager != null && callManager.hasCapabilities( 336 PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) { 337 CallAttemptRecord callAttemptRecord = new CallAttemptRecord(callManagerHandle, 338 mPhoneAccountRegistrar. 339 getOutgoingPhoneAccountForScheme(mCall.getHandle().getScheme()) 340 ); 341 342 if (!mAttemptRecords.contains(callAttemptRecord)) { 343 Log.i(this, "Will try Connection Manager account %s for emergency", 344 callManager); 345 mAttemptRecords.add(callAttemptRecord); 346 } 347 } 348 } 349 } 350 } 351 352 /** Returns all connection services used by the call attempt records. */ 353 private static Collection<PhoneAccountHandle> getConnectionServices( 354 List<CallAttemptRecord> records) { 355 HashSet<PhoneAccountHandle> result = new HashSet<>(); 356 for (CallAttemptRecord record : records) { 357 result.add(record.connectionManagerPhoneAccount); 358 } 359 return result; 360 } 361 362 private class Response implements CreateConnectionResponse { 363 private final ConnectionServiceWrapper mService; 364 365 Response(ConnectionServiceWrapper service) { 366 mService = service; 367 } 368 369 @Override 370 public void handleCreateConnectionSuccess( 371 CallIdMapper idMapper, 372 ParcelableConnection connection) { 373 if (mResponse == null) { 374 // Nobody is listening for this connection attempt any longer; ask the responsible 375 // ConnectionService to tear down any resources associated with the call 376 mService.abort(mCall); 377 } else { 378 // Success -- share the good news and remember that we are no longer interested 379 // in hearing about any more attempts 380 mResponse.handleCreateConnectionSuccess(idMapper, connection); 381 mResponse = null; 382 // If there's a timeout running then don't clear it. The timeout can be triggered 383 // after the call has successfully been created but before it has become active. 384 } 385 } 386 387 private boolean shouldFallbackToNoConnectionManager(DisconnectCause cause) { 388 PhoneAccountHandle handle = mCall.getConnectionManagerPhoneAccount(); 389 if (handle == null || !handle.equals(mPhoneAccountRegistrar.getSimCallManager())) { 390 return false; 391 } 392 393 ConnectionServiceWrapper connectionManager = mCall.getConnectionService(); 394 if (connectionManager == null) { 395 return false; 396 } 397 398 if (cause.getCode() == DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED) { 399 Log.d(CreateConnectionProcessor.this, "Connection manager declined to handle the " 400 + "call, falling back to not using a connection manager"); 401 return true; 402 } 403 404 if (!connectionManager.isServiceValid("createConnection")) { 405 Log.d(CreateConnectionProcessor.this, "Connection manager unbound while trying " 406 + "create a connection, falling back to not using a connection manager"); 407 return true; 408 } 409 410 return false; 411 } 412 413 @Override 414 public void handleCreateConnectionFailure(DisconnectCause errorDisconnectCause) { 415 // Failure of some sort; record the reasons for failure and try again if possible 416 Log.d(CreateConnectionProcessor.this, "Connection failed: (%s)", errorDisconnectCause); 417 mLastErrorDisconnectCause = errorDisconnectCause; 418 if (shouldFallbackToNoConnectionManager(errorDisconnectCause)) { 419 mShouldUseConnectionManager = false; 420 // Restart from the beginning. 421 process(); 422 } else { 423 attemptNextPhoneAccount(); 424 } 425 } 426 } 427} 428