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