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