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