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