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