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.Manifest; 20 import android.content.Context; 21 import android.content.pm.PackageManager; 22 import android.os.UserHandle; 23 import android.telecom.DisconnectCause; 24 import android.telecom.Log; 25 import android.telecom.ParcelableConference; 26 import android.telecom.ParcelableConnection; 27 import android.telecom.PhoneAccount; 28 import android.telecom.PhoneAccountHandle; 29 import android.telephony.SubscriptionManager; 30 import android.telephony.TelephonyManager; 31 32 // TODO: Needed for move to system service: import com.android.internal.R; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 import com.android.server.telecom.flags.Flags; 36 import com.android.server.telecom.flags.FeatureFlags; 37 38 import java.util.ArrayList; 39 import java.util.Collection; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.Iterator; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Objects; 46 import java.util.Set; 47 import java.util.stream.Collectors; 48 49 /** 50 * This class creates connections to place new outgoing calls or to attach to an existing incoming 51 * call. In either case, this class cycles through a set of connection services until: 52 * - a connection service returns a newly created connection in which case the call is displayed 53 * to the user 54 * - a connection service cancels the process, in which case the call is aborted 55 */ 56 @VisibleForTesting 57 public class CreateConnectionProcessor implements CreateConnectionResponse { 58 59 // Describes information required to attempt to make a phone call 60 private static class CallAttemptRecord { 61 // The PhoneAccount describing the target connection service which we will 62 // contact in order to process an attempt 63 public final PhoneAccountHandle connectionManagerPhoneAccount; 64 // The PhoneAccount which we will tell the target connection service to use 65 // for attempting to make the actual phone call 66 public final PhoneAccountHandle targetPhoneAccount; 67 CallAttemptRecord( PhoneAccountHandle connectionManagerPhoneAccount, PhoneAccountHandle targetPhoneAccount)68 public CallAttemptRecord( 69 PhoneAccountHandle connectionManagerPhoneAccount, 70 PhoneAccountHandle targetPhoneAccount) { 71 this.connectionManagerPhoneAccount = connectionManagerPhoneAccount; 72 this.targetPhoneAccount = targetPhoneAccount; 73 } 74 75 @Override toString()76 public String toString() { 77 return "CallAttemptRecord(" 78 + Objects.toString(connectionManagerPhoneAccount) + "," 79 + Objects.toString(targetPhoneAccount) + ")"; 80 } 81 82 /** 83 * Determines if this instance of {@code CallAttemptRecord} has the same underlying 84 * {@code PhoneAccountHandle}s as another instance. 85 * 86 * @param obj The other instance to compare against. 87 * @return {@code True} if the {@code CallAttemptRecord}s are equal. 88 */ 89 @Override equals(Object obj)90 public boolean equals(Object obj) { 91 if (obj instanceof CallAttemptRecord) { 92 CallAttemptRecord other = (CallAttemptRecord) obj; 93 return Objects.equals(connectionManagerPhoneAccount, 94 other.connectionManagerPhoneAccount) && 95 Objects.equals(targetPhoneAccount, other.targetPhoneAccount); 96 } 97 return false; 98 } 99 } 100 101 @VisibleForTesting 102 public interface ITelephonyManagerAdapter { getSubIdForPhoneAccount(Context context, PhoneAccount account)103 int getSubIdForPhoneAccount(Context context, PhoneAccount account); getSlotIndex(int subId)104 int getSlotIndex(int subId); 105 } 106 107 public static class ITelephonyManagerAdapterImpl implements ITelephonyManagerAdapter { 108 @Override getSubIdForPhoneAccount(Context context, PhoneAccount account)109 public int getSubIdForPhoneAccount(Context context, PhoneAccount account) { 110 TelephonyManager manager = context.getSystemService(TelephonyManager.class); 111 if (manager == null) { 112 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 113 } 114 try { 115 return manager.getSubscriptionId(account.getAccountHandle()); 116 } catch (UnsupportedOperationException uoe) { 117 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 118 } 119 } 120 121 @Override getSlotIndex(int subId)122 public int getSlotIndex(int subId) { 123 try { 124 return SubscriptionManager.getSlotIndex(subId); 125 } catch (UnsupportedOperationException uoe) { 126 return SubscriptionManager.INVALID_SUBSCRIPTION_ID; 127 } 128 } 129 }; 130 131 /** 132 * Call states which should be prioritized when sorting phone accounts. The ordering is 133 * intentional and should NOT be modified. Other call states will not have any priority. 134 */ 135 private static final int[] PRIORITY_CALL_STATES = new int [] 136 {CallState.ACTIVE, CallState.ON_HOLD, CallState.DIALING, CallState.RINGING}; 137 private static final int DEFAULT_CALL_STATE_PRIORITY = PRIORITY_CALL_STATES.length; 138 private static final Map<Integer, Integer> mCallStatePriorityMap = new HashMap<>(); 139 static { 140 for (int i = 0; i < PRIORITY_CALL_STATES.length; i++) { mCallStatePriorityMap.put(PRIORITY_CALL_STATES[i], i)141 mCallStatePriorityMap.put(PRIORITY_CALL_STATES[i], i); 142 } 143 } 144 145 146 private ITelephonyManagerAdapter mTelephonyAdapter = new ITelephonyManagerAdapterImpl(); 147 148 private final Call mCall; 149 private final ConnectionServiceRepository mRepository; 150 private List<CallAttemptRecord> mAttemptRecords; 151 private Iterator<CallAttemptRecord> mAttemptRecordIterator; 152 private CreateConnectionResponse mCallResponse; 153 private DisconnectCause mLastErrorDisconnectCause; 154 private final PhoneAccountRegistrar mPhoneAccountRegistrar; 155 private final CallsManager mCallsManager; 156 private final Context mContext; 157 private final FeatureFlags mFlags; 158 private final Timeouts.Adapter mTimeoutsAdapter; 159 private CreateConnectionTimeout mTimeout; 160 private ConnectionServiceWrapper mService; 161 private int mConnectionAttempt; 162 163 @VisibleForTesting CreateConnectionProcessor(Call call, ConnectionServiceRepository repository, CreateConnectionResponse response, PhoneAccountRegistrar phoneAccountRegistrar, CallsManager callsManager, Context context, FeatureFlags featureFlags, Timeouts.Adapter timeoutsAdapter)164 public CreateConnectionProcessor(Call call, 165 ConnectionServiceRepository repository, 166 CreateConnectionResponse response, 167 PhoneAccountRegistrar phoneAccountRegistrar, 168 CallsManager callsManager, 169 Context context, 170 FeatureFlags featureFlags, 171 Timeouts.Adapter timeoutsAdapter) { 172 Log.v(this, "CreateConnectionProcessor created for Call = %s", call); 173 mCall = call; 174 mRepository = repository; 175 mCallResponse = response; 176 mPhoneAccountRegistrar = phoneAccountRegistrar; 177 mCallsManager = callsManager; 178 mContext = context; 179 mConnectionAttempt = 0; 180 mFlags = featureFlags; 181 mTimeoutsAdapter = timeoutsAdapter; 182 } 183 isProcessingComplete()184 boolean isProcessingComplete() { 185 return mCallResponse == null; 186 } 187 isCallTimedOut()188 boolean isCallTimedOut() { 189 return mTimeout != null && mTimeout.isCallTimedOut(); 190 } 191 getConnectionAttempt()192 public int getConnectionAttempt() { 193 return mConnectionAttempt; 194 } 195 196 @VisibleForTesting setTelephonyManagerAdapter(ITelephonyManagerAdapter adapter)197 public void setTelephonyManagerAdapter(ITelephonyManagerAdapter adapter) { 198 mTelephonyAdapter = adapter; 199 } 200 201 @VisibleForTesting process()202 public void process() { 203 Log.v(this, "process"); 204 clearTimeout(); 205 mAttemptRecords = new ArrayList<>(); 206 if (mCall.getTargetPhoneAccount() != null) { 207 mAttemptRecords.add(new CallAttemptRecord( 208 mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount())); 209 } 210 if (!mCall.isSelfManaged()) { 211 adjustAttemptsForConnectionManager(); 212 adjustAttemptsForEmergency(mCall.getTargetPhoneAccount()); 213 } 214 mAttemptRecordIterator = mAttemptRecords.iterator(); 215 attemptNextPhoneAccount(); 216 } 217 hasMorePhoneAccounts()218 boolean hasMorePhoneAccounts() { 219 return mAttemptRecordIterator.hasNext(); 220 } 221 continueProcessingIfPossible(CreateConnectionResponse response, DisconnectCause disconnectCause)222 void continueProcessingIfPossible(CreateConnectionResponse response, 223 DisconnectCause disconnectCause) { 224 Log.v(this, "continueProcessingIfPossible"); 225 mCallResponse = response; 226 mLastErrorDisconnectCause = disconnectCause; 227 attemptNextPhoneAccount(); 228 } 229 abort()230 void abort() { 231 Log.v(this, "abort"); 232 233 // Clear the response first to prevent attemptNextConnectionService from attempting any 234 // more services. 235 CreateConnectionResponse response = mCallResponse; 236 mCallResponse = null; 237 clearTimeout(); 238 239 ConnectionServiceWrapper service = mCall.getConnectionService(); 240 if (service != null) { 241 service.abort(mCall); 242 mCall.clearConnectionService(); 243 } 244 if (response != null) { 245 response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.LOCAL)); 246 } 247 } 248 attemptNextPhoneAccount()249 private void attemptNextPhoneAccount() { 250 Log.v(this, "attemptNextPhoneAccount"); 251 CallAttemptRecord attempt = null; 252 if (mAttemptRecordIterator.hasNext()) { 253 attempt = mAttemptRecordIterator.next(); 254 255 if (!mPhoneAccountRegistrar.phoneAccountRequiresBindPermission( 256 attempt.connectionManagerPhoneAccount)) { 257 Log.w(this, 258 "Connection mgr does not have BIND_TELECOM_CONNECTION_SERVICE for " 259 + "attempt: %s", attempt); 260 attemptNextPhoneAccount(); 261 return; 262 } 263 264 // If the target PhoneAccount differs from the ConnectionManager phone acount, ensure it 265 // also requires the BIND_TELECOM_CONNECTION_SERVICE permission. 266 if (!attempt.connectionManagerPhoneAccount.equals(attempt.targetPhoneAccount) && 267 !mPhoneAccountRegistrar.phoneAccountRequiresBindPermission( 268 attempt.targetPhoneAccount)) { 269 Log.w(this, 270 "Target PhoneAccount does not have BIND_TELECOM_CONNECTION_SERVICE for " 271 + "attempt: %s", attempt); 272 attemptNextPhoneAccount(); 273 return; 274 } 275 } 276 277 if (mCallResponse != null && attempt != null) { 278 Log.i(this, "Trying attempt %s", attempt); 279 PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount; 280 mService = mRepository.getService(phoneAccount.getComponentName(), 281 phoneAccount.getUserHandle()); 282 if (mService == null) { 283 Log.i(this, "Found no connection service for attempt %s", attempt); 284 attemptNextPhoneAccount(); 285 } else { 286 mConnectionAttempt++; 287 mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount); 288 mCall.setTargetPhoneAccount(attempt.targetPhoneAccount); 289 if (mFlags.updatedRcsCallCountTracking()) { 290 if (Objects.equals(attempt.connectionManagerPhoneAccount, 291 attempt.targetPhoneAccount)) { 292 mCall.setConnectionService(mService); 293 } else { 294 PhoneAccountHandle remotePhoneAccount = attempt.targetPhoneAccount; 295 ConnectionServiceWrapper mRemoteService = 296 mRepository.getService(remotePhoneAccount.getComponentName(), 297 remotePhoneAccount.getUserHandle()); 298 if (mRemoteService == null) { 299 mCall.setConnectionService(mService); 300 } else { 301 Log.v(this, "attemptNextPhoneAccount Setting RCS = %s", mRemoteService); 302 mCall.setConnectionService(mService, mRemoteService); 303 } 304 } 305 } else { 306 mCall.setConnectionService(mService); 307 } 308 setTimeoutIfNeeded(mService, attempt); 309 if (mCall.isIncoming()) { 310 if (mCall.isAdhocConferenceCall()) { 311 mService.createConference(mCall, CreateConnectionProcessor.this); 312 } else { 313 mService.createConnection(mCall, CreateConnectionProcessor.this); 314 } 315 } else { 316 // Start to create the connection for outgoing call after the ConnectionService 317 // of the call has gained the focus. 318 mCall.getConnectionServiceFocusManager().requestFocus( 319 mCall, 320 new CallsManager.RequestCallback(new CallsManager.PendingAction() { 321 @Override 322 public void performAction() { 323 if (mCall.isAdhocConferenceCall()) { 324 Log.d(this, "perform create conference"); 325 mService.createConference(mCall, 326 CreateConnectionProcessor.this); 327 } else { 328 Log.d(this, "perform create connection"); 329 mService.createConnection( 330 mCall, 331 CreateConnectionProcessor.this); 332 } 333 } 334 })); 335 336 } 337 } 338 } else { 339 Log.v(this, "attemptNextPhoneAccount, no more accounts, failing"); 340 DisconnectCause disconnectCause = mLastErrorDisconnectCause != null ? 341 mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR); 342 if (mCall.isAdhocConferenceCall()) { 343 notifyConferenceCallFailure(disconnectCause); 344 } else { 345 notifyCallConnectionFailure(disconnectCause); 346 } 347 } 348 } 349 setTimeoutIfNeeded(ConnectionServiceWrapper service, CallAttemptRecord attempt)350 private void setTimeoutIfNeeded(ConnectionServiceWrapper service, CallAttemptRecord attempt) { 351 clearTimeout(); 352 353 CreateConnectionTimeout timeout = new CreateConnectionTimeout( 354 mContext, mPhoneAccountRegistrar, service, mCall, mTimeoutsAdapter); 355 if (timeout.isTimeoutNeededForCall(getConnectionServices(mAttemptRecords), 356 attempt.connectionManagerPhoneAccount)) { 357 mTimeout = timeout; 358 timeout.registerTimeout(); 359 } 360 } 361 clearTimeout()362 private void clearTimeout() { 363 if (mTimeout != null) { 364 mTimeout.unregisterTimeout(); 365 mTimeout = null; 366 } 367 } 368 shouldSetConnectionManager()369 private boolean shouldSetConnectionManager() { 370 if (mAttemptRecords.size() == 0) { 371 return false; 372 } 373 374 if (mAttemptRecords.size() > 1) { 375 Log.d(this, "shouldSetConnectionManager, error, mAttemptRecords should not have more " 376 + "than 1 record"); 377 return false; 378 } 379 380 PhoneAccountHandle connectionManager = 381 mPhoneAccountRegistrar.getSimCallManagerFromCall(mCall); 382 if (connectionManager == null) { 383 return false; 384 } 385 386 PhoneAccountHandle targetPhoneAccountHandle = mAttemptRecords.get(0).targetPhoneAccount; 387 if (Objects.equals(connectionManager, targetPhoneAccountHandle)) { 388 return false; 389 } 390 391 // Connection managers are only allowed to manage SIM subscriptions. 392 // TODO: Should this really be checking the "calling user" test for phone account? 393 PhoneAccount targetPhoneAccount = mPhoneAccountRegistrar 394 .getPhoneAccountUnchecked(targetPhoneAccountHandle); 395 if (targetPhoneAccount == null) { 396 Log.d(this, "shouldSetConnectionManager, phone account not found"); 397 return false; 398 } 399 boolean isSimSubscription = (targetPhoneAccount.getCapabilities() & 400 PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION) != 0; 401 if (!isSimSubscription) { 402 return false; 403 } 404 405 return true; 406 } 407 408 // If there exists a registered connection manager then use it. adjustAttemptsForConnectionManager()409 private void adjustAttemptsForConnectionManager() { 410 if (shouldSetConnectionManager()) { 411 CallAttemptRecord record = new CallAttemptRecord( 412 mPhoneAccountRegistrar.getSimCallManagerFromCall(mCall), 413 mAttemptRecords.get(0).targetPhoneAccount); 414 Log.v(this, "setConnectionManager, changing %s -> %s", mAttemptRecords.get(0), record); 415 mAttemptRecords.add(0, record); 416 } else { 417 Log.v(this, "setConnectionManager, not changing"); 418 } 419 } 420 421 // This function is used after previous attempts to find emergency PSTN connections 422 // do not find any SIM phone accounts with emergency capability. 423 // It attempts to add any accounts with CAPABILITY_PLACE_EMERGENCY_CALLS even if 424 // accounts are not SIM accounts. adjustAttemptsForEmergencyNoSimRequired(List<PhoneAccount> allAccounts)425 private void adjustAttemptsForEmergencyNoSimRequired(List<PhoneAccount> allAccounts) { 426 // Add all phone accounts which can place emergency calls. 427 if (mAttemptRecords.isEmpty()) { 428 for (PhoneAccount phoneAccount : allAccounts) { 429 if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) { 430 PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle(); 431 Log.i(this, "Will try account %s for emergency", phoneAccountHandle); 432 mAttemptRecords.add( 433 new CallAttemptRecord(phoneAccountHandle, phoneAccountHandle)); 434 // Add only one emergency PhoneAccount to the attempt list. 435 break; 436 } 437 } 438 } 439 } 440 441 // If we are possibly attempting to call a local emergency number, ensure that the 442 // plain PSTN connection services are listed, and nothing else. adjustAttemptsForEmergency(PhoneAccountHandle preferredPAH)443 private void adjustAttemptsForEmergency(PhoneAccountHandle preferredPAH) { 444 if (mCall.isEmergencyCall()) { 445 Log.i(this, "Emergency number detected"); 446 mAttemptRecords.clear(); 447 // Phone accounts in profile do not handle emergency call, use phone accounts in 448 // current user. 449 // ONLY include phone accounts which are NOT self-managed; we will never consider a self 450 // managed phone account for placing an emergency call. 451 UserHandle userFromCall = mCall.getAssociatedUser(); 452 List<PhoneAccount> allAccounts = mPhoneAccountRegistrar 453 .getAllPhoneAccounts(userFromCall, false) 454 .stream() 455 .filter(act -> !act.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) 456 .collect(Collectors.toList()); 457 458 if (allAccounts.isEmpty()) { 459 // Try using phone accounts from other users to place the call (i.e. using an 460 // available work sim) given that the current user has the INTERACT_ACROSS_USERS 461 // permission. 462 allAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts(userFromCall, true) 463 .stream() 464 .filter(act -> !act.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) 465 .collect(Collectors.toList()); 466 } 467 468 if (allAccounts.isEmpty() && mContext.getPackageManager().hasSystemFeature( 469 PackageManager.FEATURE_TELEPHONY)) { 470 // If the list of phone accounts is empty at this point, it means Telephony hasn't 471 // registered any phone accounts yet. Add a fallback emergency phone account so 472 // that emergency calls can still go through. We create a new ArrayLists here just 473 // in case the implementation of PhoneAccountRegistrar ever returns an unmodifiable 474 // list. 475 allAccounts = new ArrayList<PhoneAccount>(); 476 allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount()); 477 } 478 479 // When testing emergency calls, we want the calls to go through to the test connection 480 // service, not the telephony ConnectionService. 481 if (mCall.isTestEmergencyCall()) { 482 Log.i(this, "Processing test emergency call -- special rules"); 483 allAccounts = mPhoneAccountRegistrar.filterRestrictedPhoneAccounts(allAccounts); 484 } 485 486 // Get user preferred PA if it exists. 487 PhoneAccount preferredPA = mPhoneAccountRegistrar.getPhoneAccountUnchecked( 488 preferredPAH); 489 if (mCall.isIncoming() && preferredPA != null) { 490 // The phone account for the incoming call should be used. 491 mAttemptRecords.add(new CallAttemptRecord(preferredPA.getAccountHandle(), 492 preferredPA.getAccountHandle())); 493 } else { 494 // Next, add all SIM phone accounts which can place emergency calls. 495 sortSimPhoneAccountsForEmergency(allAccounts, preferredPA); 496 Log.i(this, "The preferred PA is: %s", preferredPA); 497 // and pick the first one that can place emergency calls. 498 for (PhoneAccount phoneAccount : allAccounts) { 499 if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) 500 && phoneAccount.hasCapabilities( 501 PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) { 502 PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle(); 503 Log.i(this, "Will try PSTN account %s for emergency", 504 phoneAccountHandle); 505 mAttemptRecords.add(new CallAttemptRecord(phoneAccountHandle, 506 phoneAccountHandle)); 507 // Add only one emergency SIM PhoneAccount to the attempt list, telephony 508 // will perform retries if the call fails. 509 break; 510 } 511 } 512 } 513 514 // Next, add the connection manager account as a backup if it can place emergency calls. 515 PhoneAccountHandle callManagerHandle = 516 mPhoneAccountRegistrar.getSimCallManagerOfCurrentUser(); 517 if (callManagerHandle != null) { 518 // TODO: Should this really be checking the "calling user" test for phone account? 519 PhoneAccount callManager = mPhoneAccountRegistrar 520 .getPhoneAccountUnchecked(callManagerHandle); 521 if (callManager != null && callManager.hasCapabilities( 522 PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) { 523 CallAttemptRecord callAttemptRecord = new CallAttemptRecord(callManagerHandle, 524 mPhoneAccountRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser( 525 mCall.getHandle() == null 526 ? null : mCall.getHandle().getScheme())); 527 // If the target phone account is null, we'll run into a NPE during the retry 528 // process, so skip it now if it's null. 529 if (callAttemptRecord.targetPhoneAccount != null 530 && !mAttemptRecords.contains(callAttemptRecord)) { 531 Log.i(this, "Will try Connection Manager account %s for emergency", 532 callManager); 533 mAttemptRecords.add(callAttemptRecord); 534 } 535 } 536 } 537 538 if (mAttemptRecords.isEmpty()) { 539 // Last best-effort attempt: choose any account with emergency capability even 540 // without SIM capability. 541 adjustAttemptsForEmergencyNoSimRequired(allAccounts); 542 } 543 } 544 } 545 546 /** Returns all connection services used by the call attempt records. */ getConnectionServices( List<CallAttemptRecord> records)547 private static Collection<PhoneAccountHandle> getConnectionServices( 548 List<CallAttemptRecord> records) { 549 HashSet<PhoneAccountHandle> result = new HashSet<>(); 550 for (CallAttemptRecord record : records) { 551 result.add(record.connectionManagerPhoneAccount); 552 } 553 return result; 554 } 555 556 notifyCallConnectionFailure(DisconnectCause errorDisconnectCause)557 private void notifyCallConnectionFailure(DisconnectCause errorDisconnectCause) { 558 if (mCallResponse != null) { 559 clearTimeout(); 560 mCallResponse.handleCreateConnectionFailure(errorDisconnectCause); 561 mCallResponse = null; 562 mCall.clearConnectionService(); 563 } 564 } 565 notifyConferenceCallFailure(DisconnectCause errorDisconnectCause)566 private void notifyConferenceCallFailure(DisconnectCause errorDisconnectCause) { 567 if (mCallResponse != null) { 568 clearTimeout(); 569 mCallResponse.handleCreateConferenceFailure(errorDisconnectCause); 570 mCallResponse = null; 571 mCall.clearConnectionService(); 572 } 573 } 574 575 576 @Override handleCreateConnectionSuccess( CallIdMapper idMapper, ParcelableConnection connection)577 public void handleCreateConnectionSuccess( 578 CallIdMapper idMapper, 579 ParcelableConnection connection) { 580 if (mCallResponse == null) { 581 // Nobody is listening for this connection attempt any longer; ask the responsible 582 // ConnectionService to tear down any resources associated with the call 583 mService.abort(mCall); 584 } else { 585 // Success -- share the good news and remember that we are no longer interested 586 // in hearing about any more attempts 587 mCallResponse.handleCreateConnectionSuccess(idMapper, connection); 588 mCallResponse = null; 589 // If there's a timeout running then don't clear it. The timeout can be triggered 590 // after the call has successfully been created but before it has become active. 591 } 592 } 593 594 @Override handleCreateConferenceSuccess( CallIdMapper idMapper, ParcelableConference conference)595 public void handleCreateConferenceSuccess( 596 CallIdMapper idMapper, 597 ParcelableConference conference) { 598 if (mCallResponse == null) { 599 // Nobody is listening for this conference attempt any longer; ask the responsible 600 // ConnectionService to tear down any resources associated with the call 601 mService.abort(mCall); 602 } else { 603 // Success -- share the good news and remember that we are no longer interested 604 // in hearing about any more attempts 605 mCallResponse.handleCreateConferenceSuccess(idMapper, conference); 606 mCallResponse = null; 607 // If there's a timeout running then don't clear it. The timeout can be triggered 608 // after the call has successfully been created but before it has become active. 609 } 610 } 611 612 shouldFailCallIfConnectionManagerFails(DisconnectCause cause)613 private boolean shouldFailCallIfConnectionManagerFails(DisconnectCause cause) { 614 // Connection Manager does not exist or does not match registered Connection Manager 615 // Since Connection manager is a proxy for SIM, fall back to SIM 616 PhoneAccountHandle handle = mCall.getConnectionManagerPhoneAccount(); 617 if (handle == null || !handle.equals(mPhoneAccountRegistrar.getSimCallManagerFromCall( 618 mCall))) { 619 return false; 620 } 621 622 // The Call's Connection Service does not exist 623 ConnectionServiceWrapper connectionManager = mCall.getConnectionService(); 624 if (connectionManager == null) { 625 return true; 626 } 627 628 // In this case, fall back to a sim because connection manager declined 629 if (cause.getCode() == DisconnectCause.CONNECTION_MANAGER_NOT_SUPPORTED) { 630 Log.d(CreateConnectionProcessor.this, "Connection manager declined to handle the " 631 + "call, falling back to not using a connection manager"); 632 return false; 633 } 634 635 if (!connectionManager.isServiceValid("createConnection")) { 636 Log.d(CreateConnectionProcessor.this, "Connection manager unbound while trying " 637 + "create a connection, falling back to not using a connection manager"); 638 return false; 639 } 640 641 // Do not fall back from connection manager and simply fail call if the failure reason is 642 // other 643 Log.d(CreateConnectionProcessor.this, "Connection Manager denied call with the following " + 644 "error: " + cause.getReason() + ". Not falling back to SIM."); 645 return true; 646 } 647 648 @Override handleCreateConnectionFailure(DisconnectCause errorDisconnectCause)649 public void handleCreateConnectionFailure(DisconnectCause errorDisconnectCause) { 650 // Failure of some sort; record the reasons for failure and try again if possible 651 Log.d(CreateConnectionProcessor.this, "Connection failed: (%s)", errorDisconnectCause); 652 if (shouldFailCallIfConnectionManagerFails(errorDisconnectCause)) { 653 notifyCallConnectionFailure(errorDisconnectCause); 654 return; 655 } 656 mLastErrorDisconnectCause = errorDisconnectCause; 657 attemptNextPhoneAccount(); 658 } 659 660 @Override handleCreateConferenceFailure(DisconnectCause errorDisconnectCause)661 public void handleCreateConferenceFailure(DisconnectCause errorDisconnectCause) { 662 // Failure of some sort; record the reasons for failure and try again if possible 663 Log.d(CreateConnectionProcessor.this, "Conference failed: (%s)", errorDisconnectCause); 664 if (shouldFailCallIfConnectionManagerFails(errorDisconnectCause)) { 665 notifyConferenceCallFailure(errorDisconnectCause); 666 return; 667 } 668 mLastErrorDisconnectCause = errorDisconnectCause; 669 attemptNextPhoneAccount(); 670 } 671 sortSimPhoneAccountsForEmergency(List<PhoneAccount> accounts, PhoneAccount userPreferredAccount)672 public void sortSimPhoneAccountsForEmergency(List<PhoneAccount> accounts, 673 PhoneAccount userPreferredAccount) { 674 // Sort the accounts according to how we want to display them (ascending order). 675 accounts.sort((account1, account2) -> { 676 int retval = 0; 677 678 // SIM accounts go first 679 boolean isSim1 = account1.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION); 680 boolean isSim2 = account2.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION); 681 if (isSim1 ^ isSim2) { 682 return isSim1 ? -1 : 1; 683 } 684 685 // Start with the account that Telephony considers as the "emergency preferred" 686 // account, which overrides the user's choice. 687 boolean isSim1Preferred = account1.hasCapabilities( 688 PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED); 689 boolean isSim2Preferred = account2.hasCapabilities( 690 PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED); 691 // Perform XOR, we only sort if one is considered emergency preferred (should 692 // always be the case). 693 if (isSim1Preferred ^ isSim2Preferred) { 694 return isSim1Preferred ? -1 : 1; 695 } 696 697 // Return the PhoneAccount associated with a valid logical slot. 698 int subId1 = mTelephonyAdapter.getSubIdForPhoneAccount(mContext, account1); 699 int subId2 = mTelephonyAdapter.getSubIdForPhoneAccount(mContext, account2); 700 int slotId1 = (subId1 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) 701 ? mTelephonyAdapter.getSlotIndex(subId1) 702 : SubscriptionManager.INVALID_SIM_SLOT_INDEX; 703 int slotId2 = (subId2 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) 704 ? mTelephonyAdapter.getSlotIndex(subId2) 705 : SubscriptionManager.INVALID_SIM_SLOT_INDEX; 706 // Make sure both slots are valid, if one is not, prefer the one that is valid. 707 if ((slotId1 == SubscriptionManager.INVALID_SIM_SLOT_INDEX) ^ 708 (slotId2 == SubscriptionManager.INVALID_SIM_SLOT_INDEX)) { 709 retval = (slotId1 != SubscriptionManager.INVALID_SIM_SLOT_INDEX) ? -1 : 1; 710 } 711 if (retval != 0) { 712 return retval; 713 } 714 715 // Sort accounts by ongoing call states 716 Set<Integer> callStatesAccount1 = mCallsManager.getCalls().stream() 717 .filter(c -> Objects.equals(account1.getAccountHandle(), 718 c.getTargetPhoneAccount())) 719 .map(Call::getState).collect(Collectors.toSet()); 720 Set<Integer> callStatesAccount2 = mCallsManager.getCalls().stream() 721 .filter(c -> Objects.equals(account2.getAccountHandle(), 722 c.getTargetPhoneAccount())) 723 .map(Call::getState).collect(Collectors.toSet()); 724 int account1Priority = computeCallStatePriority(callStatesAccount1); 725 int account2Priority = computeCallStatePriority(callStatesAccount2); 726 Log.d(this, "account1: %s, call state priority: %s", account1, account1Priority); 727 Log.d(this, "account2: %s, call state priority: %s", account2, account2Priority); 728 if (account1Priority != account2Priority) { 729 return account1Priority < account2Priority ? -1 : 1; 730 } 731 732 // Prefer the user's choice if all PhoneAccounts are associated with valid logical 733 // slots. 734 if (userPreferredAccount != null) { 735 if (account1.equals(userPreferredAccount)) { 736 return -1; 737 } else if (account2.equals(userPreferredAccount)) { 738 return 1; 739 } 740 } 741 742 // because of the xor above, slotId1 and slotId2 are either both invalid or valid at 743 // this point. If valid, prefer the lower slot index. 744 if (slotId1 != SubscriptionManager.INVALID_SIM_SLOT_INDEX) { 745 // Assuming the slots are different, we should not have slotId1 == slotId2. 746 return (slotId1 < slotId2) ? -1 : 1; 747 } 748 749 // Then order by package 750 String pkg1 = account1.getAccountHandle().getComponentName().getPackageName(); 751 String pkg2 = account2.getAccountHandle().getComponentName().getPackageName(); 752 retval = pkg1.compareTo(pkg2); 753 if (retval != 0) { 754 return retval; 755 } 756 757 // then order by label 758 String label1 = nullToEmpty(account1.getLabel().toString()); 759 String label2 = nullToEmpty(account2.getLabel().toString()); 760 retval = label1.compareTo(label2); 761 if (retval != 0) { 762 return retval; 763 } 764 765 // then by hashcode 766 return Integer.compare(account1.hashCode(), account2.hashCode()); 767 }); 768 } 769 770 /** 771 * Computes the call state priority based on the passed in call states associated with the 772 * calls present on the phone account. The lower the value, the higher the priority (i.e. 773 * ACTIVE (0) < HOLDING (1) < DIALING (2) < RINGING (3) equates to ACTIVE holding the highest 774 * priority). 775 */ computeCallStatePriority(Set<Integer> callStates)776 private int computeCallStatePriority(Set<Integer> callStates) { 777 int priority = DEFAULT_CALL_STATE_PRIORITY; 778 for (int state: callStates) { 779 if (priority == mCallStatePriorityMap.get(CallState.ACTIVE)) { 780 return priority; 781 } else if (mCallStatePriorityMap.containsKey(state) 782 && priority > mCallStatePriorityMap.get(state)) { 783 priority = mCallStatePriorityMap.get(state); 784 } 785 } 786 return priority; 787 } 788 nullToEmpty(String str)789 private static String nullToEmpty(String str) { 790 return str == null ? "" : str; 791 } 792 } 793