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