• 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.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