• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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