• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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.services.telephony;
18 
19 import static android.telephony.DomainSelectionService.SELECTOR_TYPE_CALLING;
20 import static android.telephony.TelephonyManager.HAL_SERVICE_VOICE;
21 
22 import android.annotation.NonNull;
23 import android.app.AlertDialog;
24 import android.app.Dialog;
25 import android.content.ActivityNotFoundException;
26 import android.content.BroadcastReceiver;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.DialogInterface;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.os.ParcelUuid;
35 import android.provider.DeviceConfig;
36 import android.telecom.Conference;
37 import android.telecom.Conferenceable;
38 import android.telecom.Connection;
39 import android.telecom.ConnectionRequest;
40 import android.telecom.ConnectionService;
41 import android.telecom.DisconnectCause;
42 import android.telecom.PhoneAccount;
43 import android.telecom.PhoneAccountHandle;
44 import android.telecom.TelecomManager;
45 import android.telecom.VideoProfile;
46 import android.telephony.AccessNetworkConstants;
47 import android.telephony.Annotation.DisconnectCauses;
48 import android.telephony.CarrierConfigManager;
49 import android.telephony.DataSpecificRegistrationInfo;
50 import android.telephony.DomainSelectionService;
51 import android.telephony.DomainSelectionService.SelectionAttributes;
52 import android.telephony.EmergencyRegResult;
53 import android.telephony.NetworkRegistrationInfo;
54 import android.telephony.PhoneNumberUtils;
55 import android.telephony.RadioAccessFamily;
56 import android.telephony.ServiceState;
57 import android.telephony.SubscriptionManager;
58 import android.telephony.TelephonyManager;
59 import android.telephony.emergency.EmergencyNumber;
60 import android.telephony.ims.ImsReasonInfo;
61 import android.telephony.ims.stub.ImsRegistrationImplBase;
62 import android.text.TextUtils;
63 import android.util.Pair;
64 import android.view.WindowManager;
65 
66 import com.android.ims.ImsManager;
67 import com.android.internal.annotations.VisibleForTesting;
68 import com.android.internal.telephony.Call;
69 import com.android.internal.telephony.CallStateException;
70 import com.android.internal.telephony.GsmCdmaPhone;
71 import com.android.internal.telephony.IccCard;
72 import com.android.internal.telephony.IccCardConstants;
73 import com.android.internal.telephony.Phone;
74 import com.android.internal.telephony.PhoneConstants;
75 import com.android.internal.telephony.PhoneFactory;
76 import com.android.internal.telephony.RIL;
77 import com.android.internal.telephony.d2d.Communicator;
78 import com.android.internal.telephony.data.PhoneSwitcher;
79 import com.android.internal.telephony.domainselection.DomainSelectionConnection;
80 import com.android.internal.telephony.domainselection.DomainSelectionResolver;
81 import com.android.internal.telephony.domainselection.EmergencyCallDomainSelectionConnection;
82 import com.android.internal.telephony.domainselection.NormalCallDomainSelectionConnection;
83 import com.android.internal.telephony.emergency.EmergencyStateTracker;
84 import com.android.internal.telephony.emergency.RadioOnHelper;
85 import com.android.internal.telephony.emergency.RadioOnStateListener;
86 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
87 import com.android.internal.telephony.imsphone.ImsPhone;
88 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
89 import com.android.internal.telephony.imsphone.ImsPhoneMmiCode;
90 import com.android.internal.telephony.satellite.SatelliteController;
91 import com.android.internal.telephony.satellite.SatelliteSOSMessageRecommender;
92 import com.android.internal.telephony.subscription.SubscriptionInfoInternal;
93 import com.android.internal.telephony.subscription.SubscriptionManagerService;
94 import com.android.phone.FrameworksUtils;
95 import com.android.phone.MMIDialogActivity;
96 import com.android.phone.PhoneUtils;
97 import com.android.phone.R;
98 import com.android.phone.callcomposer.CallComposerPictureManager;
99 import com.android.phone.settings.SuppServicesUiUtil;
100 
101 import java.lang.ref.WeakReference;
102 import java.util.ArrayList;
103 import java.util.Arrays;
104 import java.util.Collection;
105 import java.util.Collections;
106 import java.util.HashMap;
107 import java.util.LinkedList;
108 import java.util.List;
109 import java.util.Map;
110 import java.util.Objects;
111 import java.util.Queue;
112 import java.util.Set;
113 import java.util.concurrent.CompletableFuture;
114 import java.util.concurrent.Executor;
115 import java.util.function.Consumer;
116 import java.util.regex.Pattern;
117 import java.util.stream.Stream;
118 
119 import javax.annotation.Nullable;
120 
121 /**
122  * Service for making GSM and CDMA connections.
123  */
124 public class TelephonyConnectionService extends ConnectionService {
125     private static final String LOG_TAG = TelephonyConnectionService.class.getSimpleName();
126     // Timeout before we continue with the emergency call without waiting for DDS switch response
127     // from the modem.
128     private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000;
129 
130     // Timeout to start dynamic routing of normal routing emergency numbers.
131     @VisibleForTesting
132     public static final int TIMEOUT_TO_DYNAMIC_ROUTING_MS = 10000;
133 
134     // Timeout before we terminate the outgoing DSDA call if HOLD did not complete in time on the
135     // existing call.
136     private static final int DEFAULT_DSDA_OUTGOING_CALL_HOLD_TIMEOUT_MS = 2000;
137     private static final String KEY_DOMAIN_COMPARE_FEATURE_ENABLED_FLAG =
138             "is_domain_selection_compare_feature_enabled";
139 
140     // If configured, reject attempts to dial numbers matching this pattern.
141     private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
142             Pattern.compile("\\*228[0-9]{0,2}");
143 
144     private final TelephonyConnectionServiceProxy mTelephonyConnectionServiceProxy =
145             new TelephonyConnectionServiceProxy() {
146         @Override
147         public Collection<Connection> getAllConnections() {
148             return TelephonyConnectionService.this.getAllConnections();
149         }
150         @Override
151         public void addConference(TelephonyConference mTelephonyConference) {
152             TelephonyConnectionService.this.addTelephonyConference(mTelephonyConference);
153         }
154         @Override
155         public void addConference(ImsConference mImsConference) {
156             Connection conferenceHost = mImsConference.getConferenceHost();
157             if (conferenceHost instanceof TelephonyConnection) {
158                 TelephonyConnection tcConferenceHost = (TelephonyConnection) conferenceHost;
159                 tcConferenceHost.setTelephonyConnectionService(TelephonyConnectionService.this);
160                 tcConferenceHost.setPhoneAccountHandle(mImsConference.getPhoneAccountHandle());
161             }
162             TelephonyConnectionService.this.addTelephonyConference(mImsConference);
163         }
164         @Override
165         public void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
166                                           Connection connection) {
167             TelephonyConnectionService.this
168                     .addExistingConnection(phoneAccountHandle, connection);
169         }
170         @Override
171         public void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
172                 Connection connection, Conference conference) {
173             TelephonyConnectionService.this
174                     .addExistingConnection(phoneAccountHandle, connection, conference);
175         }
176         @Override
177         public void addConnectionToConferenceController(TelephonyConnection connection) {
178             TelephonyConnectionService.this.addConnectionToConferenceController(connection);
179         }
180     };
181 
182     private final BroadcastReceiver mTtyBroadcastReceiver = new BroadcastReceiver() {
183         @Override
184         public void onReceive(Context context, Intent intent) {
185             String action = intent.getAction();
186             Log.v(this, "onReceive, action: %s", action);
187             if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) {
188                 int newPreferredTtyMode = intent.getIntExtra(
189                         TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
190 
191                 boolean isTtyNowEnabled = newPreferredTtyMode != TelecomManager.TTY_MODE_OFF;
192                 if (isTtyNowEnabled != mIsTtyEnabled) {
193                     handleTtyModeChange(isTtyNowEnabled);
194                 }
195             }
196         }
197     };
198 
199     private final TelephonyConferenceController mTelephonyConferenceController =
200             new TelephonyConferenceController(mTelephonyConnectionServiceProxy);
201     private final CdmaConferenceController mCdmaConferenceController =
202             new CdmaConferenceController(this);
203     private ImsConferenceController mImsConferenceController;
204 
205     private ComponentName mExpectedComponentName = null;
206     private RadioOnHelper mRadioOnHelper;
207     private EmergencyTonePlayer mEmergencyTonePlayer;
208     private HoldTracker mHoldTracker;
209     private boolean mIsTtyEnabled;
210     /** Set to true when there is an emergency call pending which will potential trigger a dial.
211      * This must be set to false when the call is dialed. */
212     private volatile boolean mIsEmergencyCallPending;
213 
214     // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has
215     // already tried to connect with. There should be only one TelephonyConnection trying to place a
216     // call at one time. We also only access this cache from a TelephonyConnection that wishes to
217     // redial, so we use a WeakReference that will become stale once the TelephonyConnection is
218     // destroyed.
219     @VisibleForTesting
220     public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
221     private DeviceState mDeviceState = new DeviceState();
222     private EmergencyStateTracker mEmergencyStateTracker;
223     private SatelliteSOSMessageRecommender mSatelliteSOSMessageRecommender;
224     private DomainSelectionResolver mDomainSelectionResolver;
225     private EmergencyCallDomainSelectionConnection mEmergencyCallDomainSelectionConnection;
226     private TelephonyConnection mEmergencyConnection;
227     private String mEmergencyCallId = null;
228     private Executor mDomainSelectionMainExecutor;
229     private ImsManager mImsManager = null;
230     private DomainSelectionConnection mDomainSelectionConnection;
231     private TelephonyConnection mNormalCallConnection;
232     private SatelliteController mSatelliteController;
233 
234     /**
235      * Keeps track of the status of a SIM slot.
236      */
237     private static class SlotStatus {
238         public int slotId;
239         public int activeSubId;
240         // RAT capabilities
241         public int capabilities;
242         // By default, we will assume that the slots are not locked.
243         public boolean isLocked = false;
244         // Is the emergency number associated with the slot
245         public boolean hasDialedEmergencyNumber = false;
246         //SimState.
247         public int simState;
248 
249         //helper to check if sim is really 'present' in the traditional sense.
250         // since eSIM always reports SIM_STATE_READY
isSubActiveAndSimPresent()251         public boolean isSubActiveAndSimPresent() {
252             return (simState != TelephonyManager.SIM_STATE_ABSENT
253                 && activeSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID);
254         }
255 
SlotStatus(int slotId, int capabilities, int activeSubId)256         public SlotStatus(int slotId, int capabilities, int activeSubId) {
257             this.slotId = slotId;
258             this.capabilities = capabilities;
259             this.activeSubId = activeSubId;
260         }
261     }
262 
263     /**
264      * SubscriptionManager dependencies for testing.
265      */
266     @VisibleForTesting
267     public interface SubscriptionManagerProxy {
getDefaultVoicePhoneId()268         int getDefaultVoicePhoneId();
getDefaultDataPhoneId()269         int getDefaultDataPhoneId();
getSimStateForSlotIdx(int slotId)270         int getSimStateForSlotIdx(int slotId);
getPhoneId(int subId)271         int getPhoneId(int subId);
272     }
273 
274     private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() {
275         @Override
276         public int getDefaultVoicePhoneId() {
277             return SubscriptionManager.getDefaultVoicePhoneId();
278         }
279 
280         @Override
281         public int getDefaultDataPhoneId() {
282             return getPhoneId(SubscriptionManager.getDefaultDataSubscriptionId());
283         }
284 
285         @Override
286         public int getSimStateForSlotIdx(int slotId) {
287             return TelephonyManager.getSimStateForSlotIndex(slotId);
288         }
289 
290         @Override
291         public int getPhoneId(int subId) {
292             return SubscriptionManager.getPhoneId(subId);
293         }
294 
295     };
296 
297     /**
298      * TelephonyManager dependencies for testing.
299      */
300     @VisibleForTesting
301     public interface TelephonyManagerProxy {
getPhoneCount()302         int getPhoneCount();
isCurrentEmergencyNumber(String number)303         boolean isCurrentEmergencyNumber(String number);
getCurrentEmergencyNumberList()304         Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList();
305 
306         /**
307          * Determines whether concurrent IMS calls across both SIMs are possible, based on whether
308          * the device is DSDA capable, or if the DSDS device supports virtual DSDA.
309          */
isConcurrentCallsPossible()310         boolean isConcurrentCallsPossible();
311 
312         /**
313          * Gets the maximum number of SIMs that can be active, based on the device's multisim
314          * configuration. Returns 1 for DSDS, 2 for DSDA.
315          */
getMaxNumberOfSimultaneouslyActiveSims()316         int getMaxNumberOfSimultaneouslyActiveSims();
317     }
318 
319     private TelephonyManagerProxy mTelephonyManagerProxy;
320 
321     private class TelephonyManagerProxyImpl implements TelephonyManagerProxy {
322         private final TelephonyManager mTelephonyManager;
323 
324 
TelephonyManagerProxyImpl(Context context)325         TelephonyManagerProxyImpl(Context context) {
326             mTelephonyManager = new TelephonyManager(context);
327         }
328 
329         @Override
getPhoneCount()330         public int getPhoneCount() {
331             return mTelephonyManager.getPhoneCount();
332         }
333 
334         @Override
isCurrentEmergencyNumber(String number)335         public boolean isCurrentEmergencyNumber(String number) {
336             try {
337                 return mTelephonyManager.isEmergencyNumber(number);
338             } catch (IllegalStateException ise) {
339                 return false;
340             }
341         }
342 
343         @Override
getCurrentEmergencyNumberList()344         public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList() {
345             try {
346                 return mTelephonyManager.getEmergencyNumberList();
347             } catch (IllegalStateException ise) {
348                 return new HashMap<>();
349             }
350         }
351 
352         @Override
getMaxNumberOfSimultaneouslyActiveSims()353         public int getMaxNumberOfSimultaneouslyActiveSims() {
354             try {
355                 return mTelephonyManager.getMaxNumberOfSimultaneouslyActiveSims();
356             } catch (IllegalStateException ise) {
357                 return 1;
358             }
359         }
360 
361         @Override
isConcurrentCallsPossible()362         public boolean isConcurrentCallsPossible() {
363             try {
364                 return getMaxNumberOfSimultaneouslyActiveSims() > 1
365                     || mTelephonyManager.getPhoneCapability().getMaxActiveVoiceSubscriptions() > 1;
366             } catch (IllegalStateException ise) {
367                 return false;
368             }
369         }
370     }
371 
372     /**
373      * PhoneFactory Dependencies for testing.
374      */
375     @VisibleForTesting
376     public interface PhoneFactoryProxy {
getPhone(int index)377         Phone getPhone(int index);
getDefaultPhone()378         Phone getDefaultPhone();
getPhones()379         Phone[] getPhones();
380     }
381 
382     private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
383         @Override
384         public Phone getPhone(int index) {
385             return PhoneFactory.getPhone(index);
386         }
387 
388         @Override
389         public Phone getDefaultPhone() {
390             return PhoneFactory.getDefaultPhone();
391         }
392 
393         @Override
394         public Phone[] getPhones() {
395             return PhoneFactory.getPhones();
396         }
397     };
398 
399     /**
400      * PhoneUtils dependencies for testing.
401      */
402     @VisibleForTesting
403     public interface PhoneUtilsProxy {
getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle)404         int getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle);
makePstnPhoneAccountHandle(Phone phone)405         PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone);
makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, boolean isEmergency)406         PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix,
407                 boolean isEmergency);
408     }
409 
410     private PhoneUtilsProxy mPhoneUtilsProxy = new PhoneUtilsProxy() {
411         @Override
412         public int getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle) {
413             return PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
414         }
415 
416         @Override
417         public PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
418             return PhoneUtils.makePstnPhoneAccountHandle(phone);
419         }
420 
421         @Override
422         public PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix,
423                 boolean isEmergency) {
424             return PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
425                     phone, prefix, isEmergency, phone.getUserHandle());
426         }
427     };
428 
429     /**
430      * PhoneNumberUtils dependencies for testing.
431      */
432     @VisibleForTesting
433     public interface PhoneNumberUtilsProxy {
convertToEmergencyNumber(Context context, String number)434         String convertToEmergencyNumber(Context context, String number);
435     }
436 
437     private PhoneNumberUtilsProxy mPhoneNumberUtilsProxy = new PhoneNumberUtilsProxy() {
438         @Override
439         public String convertToEmergencyNumber(Context context, String number) {
440             return PhoneNumberUtils.convertToEmergencyNumber(context, number);
441         }
442     };
443 
444     /**
445      * PhoneSwitcher dependencies for testing.
446      */
447     @VisibleForTesting
448     public interface PhoneSwitcherProxy {
getPhoneSwitcher()449         PhoneSwitcher getPhoneSwitcher();
450     }
451 
452     private PhoneSwitcherProxy mPhoneSwitcherProxy = new PhoneSwitcherProxy() {
453         @Override
454         public PhoneSwitcher getPhoneSwitcher() {
455             return PhoneSwitcher.getInstance();
456         }
457     };
458 
459     /**
460      * DisconnectCause depends on PhoneGlobals in order to get a system context. Mock out
461      * dependency for testing.
462      */
463     @VisibleForTesting
464     public interface DisconnectCauseFactory {
toTelecomDisconnectCause(int telephonyDisconnectCause, String reason)465         DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, String reason);
toTelecomDisconnectCause(int telephonyDisconnectCause, String reason, int phoneId)466         DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause,
467                 String reason, int phoneId);
468     }
469 
470     private DisconnectCauseFactory mDisconnectCauseFactory = new DisconnectCauseFactory() {
471         @Override
472         public DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause,
473                 String reason) {
474             return DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause, reason);
475         }
476 
477         @Override
478         public DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, String reason,
479                 int phoneId) {
480             return DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause, reason,
481                     phoneId);
482         }
483     };
484 
485     /**
486      * Overrides SubscriptionManager dependencies for testing.
487      */
488     @VisibleForTesting
setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)489     public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) {
490         mSubscriptionManagerProxy = proxy;
491     }
492 
493     /**
494      * Overrides TelephonyManager dependencies for testing.
495      */
496     @VisibleForTesting
setTelephonyManagerProxy(TelephonyManagerProxy proxy)497     public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) {
498         mTelephonyManagerProxy = proxy;
499     }
500 
501     /**
502      * Overrides PhoneFactory dependencies for testing.
503      */
504     @VisibleForTesting
setPhoneFactoryProxy(PhoneFactoryProxy proxy)505     public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) {
506         mPhoneFactoryProxy = proxy;
507     }
508 
509     /**
510      * Overrides configuration and settings dependencies for testing.
511      */
512     @VisibleForTesting
setDeviceState(DeviceState state)513     public void setDeviceState(DeviceState state) {
514         mDeviceState = state;
515     }
516 
517     /**
518      * Overrides radioOnHelper for testing.
519      */
520     @VisibleForTesting
setRadioOnHelper(RadioOnHelper radioOnHelper)521     public void setRadioOnHelper(RadioOnHelper radioOnHelper) {
522         mRadioOnHelper = radioOnHelper;
523     }
524 
525     /**
526      * Overrides PhoneSwitcher dependencies for testing.
527      */
528     @VisibleForTesting
setPhoneSwitcherProxy(PhoneSwitcherProxy proxy)529     public void setPhoneSwitcherProxy(PhoneSwitcherProxy proxy) {
530         mPhoneSwitcherProxy = proxy;
531     }
532 
533     /**
534      * Overrides PhoneNumberUtils dependencies for testing.
535      */
536     @VisibleForTesting
setPhoneNumberUtilsProxy(PhoneNumberUtilsProxy proxy)537     public void setPhoneNumberUtilsProxy(PhoneNumberUtilsProxy proxy) {
538         mPhoneNumberUtilsProxy = proxy;
539     }
540 
541     /**
542      * Overrides PhoneUtils dependencies for testing.
543      */
544     @VisibleForTesting
setPhoneUtilsProxy(PhoneUtilsProxy proxy)545     public void setPhoneUtilsProxy(PhoneUtilsProxy proxy) {
546         mPhoneUtilsProxy = proxy;
547     }
548 
549     /**
550      * Override DisconnectCause creation for testing.
551      */
552     @VisibleForTesting
setDisconnectCauseFactory(DisconnectCauseFactory factory)553     public void setDisconnectCauseFactory(DisconnectCauseFactory factory) {
554         mDisconnectCauseFactory = factory;
555     }
556 
557     /**
558      * A listener for emergency calls.
559      */
560     private final TelephonyConnection.TelephonyConnectionListener mEmergencyConnectionListener =
561             new TelephonyConnection.TelephonyConnectionListener() {
562                 @Override
563                 public void onOriginalConnectionConfigured(TelephonyConnection c) {
564                     com.android.internal.telephony.Connection origConn = c.getOriginalConnection();
565                     if ((origConn == null) || (mEmergencyStateTracker == null)) {
566                         // mEmergencyStateTracker is null when no emergency call has been dialed
567                         // after bootup and normal call fails with 380 response.
568                         return;
569                     }
570                     // Update the domain in the case that it changes,for example during initial
571                     // setup or when there was an srvcc or internal redial.
572                     mEmergencyStateTracker.onEmergencyCallDomainUpdated(
573                             origConn.getPhoneType(), c.getTelecomCallId());
574                 }
575 
576                 @Override
577                 public void onStateChanged(Connection connection,
578                         @Connection.ConnectionState int state) {
579                     if (mEmergencyCallDomainSelectionConnection == null) return;
580                     if (connection == null) return;
581                     TelephonyConnection c = (TelephonyConnection) connection;
582                     Log.i(this, "onStateChanged callId=" + c.getTelecomCallId()
583                             + ", state=" + state);
584                     if (c.getState() == Connection.STATE_ACTIVE) {
585                         mEmergencyStateTracker.onEmergencyCallStateChanged(
586                                 c.getOriginalConnection().getState(), c.getTelecomCallId());
587                         releaseEmergencyCallDomainSelection(false);
588                     }
589                 }
590             };
591 
592     private final TelephonyConnection.TelephonyConnectionListener
593             mEmergencyConnectionSatelliteListener =
594             new TelephonyConnection.TelephonyConnectionListener() {
595                 @Override
596                 public void onStateChanged(Connection connection,
597                         @Connection.ConnectionState int state) {
598                     if (connection == null) {
599                         Log.d(this,
600                                 "onStateChanged for satellite listener: connection is null");
601                         return;
602                     }
603                     if (mSatelliteSOSMessageRecommender == null) {
604                         Log.d(this, "onStateChanged for satellite listener: "
605                                 + "mSatelliteSOSMessageRecommender is null");
606                         return;
607                     }
608 
609                     TelephonyConnection c = (TelephonyConnection) connection;
610                     mSatelliteSOSMessageRecommender.onEmergencyCallConnectionStateChanged(
611                             c.getTelecomCallId(), state);
612                     if (state == Connection.STATE_DISCONNECTED
613                             || state == Connection.STATE_ACTIVE) {
614                         c.removeTelephonyConnectionListener(mEmergencyConnectionSatelliteListener);
615                         mSatelliteSOSMessageRecommender = null;
616                     }
617                 }
618             };
619 
620     /**
621      * A listener for calls.
622      */
623     private final TelephonyConnection.TelephonyConnectionListener mNormalCallConnectionListener =
624             new TelephonyConnection.TelephonyConnectionListener() {
625                 @Override
626                 public void onStateChanged(
627                         Connection connection, @Connection.ConnectionState int state) {
628                     TelephonyConnection c = (TelephonyConnection) connection;
629                     if (c != null) {
630                         switch(c.getState()) {
631                             case Connection.STATE_ACTIVE: {
632                                 Log.d(LOG_TAG, "Call State->ACTIVE."
633                                         + "Clearing DomainSelectionConnection");
634                                 if (mDomainSelectionConnection != null) {
635                                     mDomainSelectionConnection.finishSelection();
636                                     mDomainSelectionConnection = null;
637                                 }
638                                 mNormalCallConnection = null;
639                             }
640                             break;
641 
642                             case Connection.STATE_DISCONNECTED: {
643                                 c.removeTelephonyConnectionListener(mNormalCallConnectionListener);
644                             }
645                             break;
646                         }
647                     }
648                 }
649             };
650 
651     private static class StateHoldingListener extends
652             TelephonyConnection.TelephonyConnectionListener {
653         private final CompletableFuture<Boolean> mStateHoldingFuture;
654 
StateHoldingListener(CompletableFuture<Boolean> future)655         StateHoldingListener(CompletableFuture<Boolean> future) {
656             mStateHoldingFuture = future;
657         }
658 
659         @Override
onStateChanged( Connection connection, @Connection.ConnectionState int state)660         public void onStateChanged(
661                 Connection connection, @Connection.ConnectionState int state) {
662             TelephonyConnection c = (TelephonyConnection) connection;
663             if (c != null) {
664                 switch (c.getState()) {
665                     case Connection.STATE_HOLDING: {
666                         Log.d(LOG_TAG, "Connection " + connection.getTelecomCallId()
667                                 + " changed to STATE_HOLDING!");
668                         mStateHoldingFuture.complete(true);
669                         c.removeTelephonyConnectionListener(this);
670                     }
671                     break;
672                     case Connection.STATE_DISCONNECTED: {
673                         Log.d(LOG_TAG, "Connection " + connection.getTelecomCallId()
674                                 + " changed to STATE_DISCONNECTED!");
675                         mStateHoldingFuture.complete(false);
676                         c.removeTelephonyConnectionListener(this);
677                     }
678                     break;
679                 }
680             }
681         }
682     }
683 
684     private final DomainSelectionConnection.DomainSelectionConnectionCallback
685             mEmergencyDomainSelectionConnectionCallback =
686                     new DomainSelectionConnection.DomainSelectionConnectionCallback() {
687         @Override
688         public void onSelectionTerminated(@DisconnectCauses int cause) {
689             mDomainSelectionMainExecutor.execute(() -> {
690                 Log.i(this, "onSelectionTerminated cause=" + cause);
691                 if (mEmergencyCallDomainSelectionConnection == null) {
692                     Log.i(this, "onSelectionTerminated no DomainSelectionConnection");
693                     return;
694                 }
695 
696                 // Cross stack redial
697                 if (cause == android.telephony.DisconnectCause.EMERGENCY_TEMP_FAILURE
698                         || cause == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE) {
699                     if (mEmergencyConnection != null) {
700                         boolean isPermanentFailure =
701                                 cause == android.telephony.DisconnectCause.EMERGENCY_PERM_FAILURE;
702                         Log.i(this, "onSelectionTerminated permanent=" + isPermanentFailure);
703                         TelephonyConnection c = mEmergencyConnection;
704                         Phone phone = mEmergencyCallDomainSelectionConnection.getPhone();
705                         mEmergencyConnection.removeTelephonyConnectionListener(
706                                 mEmergencyConnectionListener);
707                         releaseEmergencyCallDomainSelection(true);
708                         mEmergencyStateTracker.endCall(mEmergencyCallId);
709                         mEmergencyCallId = null;
710                         retryOutgoingOriginalConnection(c, phone, isPermanentFailure);
711                         return;
712                     }
713                 }
714                 if (mEmergencyConnection != null) {
715                     mEmergencyConnection.hangup(android.telephony.DisconnectCause.OUT_OF_NETWORK);
716                     mEmergencyConnection = null;
717                 }
718             });
719         }
720     };
721 
722     private final DomainSelectionConnection.DomainSelectionConnectionCallback
723             mCallDomainSelectionConnectionCallback =
724             new DomainSelectionConnection.DomainSelectionConnectionCallback() {
725                 @Override
726                 public void onSelectionTerminated(@DisconnectCauses int cause) {
727                     mDomainSelectionMainExecutor.execute(new Runnable() {
728                         int mCause = cause;
729                         @Override
730                         public void run() {
731                             Log.v(this, "Call domain selection terminated.");
732                             if (mDomainSelectionConnection != null) {
733                                 mDomainSelectionConnection = null;
734                             }
735                             if (mNormalCallConnection != null) {
736                                 // TODO: To support ShowPreciseFailedCause, TelephonyConnection
737                                 //  .getShowPreciseFailedCause API should be added.
738 
739                                 // If cause is NOT_VALID then, it's a redial cancellation and
740                                 // use cause code from original connection.
741                                 com.android.internal.telephony.Connection connection =
742                                         mNormalCallConnection.getOriginalConnection();
743                                 if (connection != null) {
744                                     if (mCause == android.telephony.DisconnectCause.NOT_VALID) {
745                                         mCause = connection.getDisconnectCause();
746                                     }
747 
748                                     String reason = connection.getVendorDisconnectCause();
749                                     int phoneId = mNormalCallConnection.getPhone().getPhoneId();
750                                     mNormalCallConnection.setTelephonyConnectionDisconnected(
751                                             mDisconnectCauseFactory.toTelecomDisconnectCause(
752                                                     mCause, reason, phoneId));
753                                     Log.d(this, "Call connection closed. Cause: " + mCause
754                                             + " Reason: " + reason);
755                                 }
756                                 mNormalCallConnection.close();
757                                 mNormalCallConnection = null;
758                             }
759                         }
760                     });
761                 }
762             };
763 
764     /**
765      * A listener to actionable events specific to the TelephonyConnection.
766      */
767     private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
768             new TelephonyConnection.TelephonyConnectionListener() {
769         @Override
770         public void onOriginalConnectionConfigured(TelephonyConnection c) {
771             addConnectionToConferenceController(c);
772         }
773 
774         @Override
775         public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) {
776             retryOutgoingOriginalConnection(c, c.getPhone(), isPermanentFailure);
777         }
778     };
779 
780     private final TelephonyConferenceBase.TelephonyConferenceListener mTelephonyConferenceListener =
781             new TelephonyConferenceBase.TelephonyConferenceListener() {
782         @Override
783         public void onConferenceMembershipChanged(Connection connection) {
784             mHoldTracker.updateHoldCapability();
785         }
786     };
787 
788     @Override
onCreate()789     public void onCreate() {
790         super.onCreate();
791         mImsConferenceController = new ImsConferenceController(
792                 TelecomAccountRegistry.getInstance(this),
793                 mTelephonyConnectionServiceProxy,
794                 // FeatureFlagProxy; used to determine if standalone call emulation is enabled.
795                 // TODO: Move to carrier config
796                 () -> true);
797         setTelephonyManagerProxy(new TelephonyManagerProxyImpl(getApplicationContext()));
798         mExpectedComponentName = new ComponentName(this, this.getClass());
799         mEmergencyTonePlayer = new EmergencyTonePlayer(this);
800         TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
801         mHoldTracker = new HoldTracker();
802         mIsTtyEnabled = mDeviceState.isTtyModeEnabled(this);
803         mDomainSelectionMainExecutor = getApplicationContext().getMainExecutor();
804         mDomainSelectionResolver = DomainSelectionResolver.getInstance();
805         mSatelliteController = SatelliteController.getInstance();
806 
807         IntentFilter intentFilter = new IntentFilter(
808                 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
809         registerReceiver(mTtyBroadcastReceiver, intentFilter,
810                 android.Manifest.permission.MODIFY_PHONE_STATE, null, Context.RECEIVER_EXPORTED);
811     }
812 
813     @Override
onUnbind(Intent intent)814     public boolean onUnbind(Intent intent) {
815         unregisterReceiver(mTtyBroadcastReceiver);
816         return super.onUnbind(intent);
817     }
818 
placeOutgoingConference(ConnectionRequest request, Connection resultConnection, Phone phone)819     private Conference placeOutgoingConference(ConnectionRequest request,
820             Connection resultConnection, Phone phone) {
821         if (resultConnection instanceof TelephonyConnection) {
822             return placeOutgoingConference((TelephonyConnection) resultConnection, phone, request);
823         }
824         return null;
825     }
826 
placeOutgoingConference(TelephonyConnection conferenceHostConnection, Phone phone, ConnectionRequest request)827     private Conference placeOutgoingConference(TelephonyConnection conferenceHostConnection,
828             Phone phone, ConnectionRequest request) {
829         updatePhoneAccount(conferenceHostConnection, phone);
830         com.android.internal.telephony.Connection originalConnection = null;
831         try {
832             originalConnection = phone.startConference(
833                     getParticipantsToDial(request.getParticipants()),
834                     new ImsPhone.ImsDialArgs.Builder()
835                     .setVideoState(request.getVideoState())
836                     .setRttTextStream(conferenceHostConnection.getRttTextStream())
837                     .build());
838         } catch (CallStateException e) {
839             Log.e(this, e, "placeOutgoingConference, phone.startConference exception: " + e);
840             handleCallStateException(e, conferenceHostConnection, phone);
841             return null;
842         }
843 
844         if (originalConnection == null) {
845             Log.d(this, "placeOutgoingConference, phone.startConference returned null");
846             conferenceHostConnection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
847                     android.telephony.DisconnectCause.OUTGOING_FAILURE,
848                     "conferenceHostConnection is null",
849                     phone.getPhoneId()));
850             conferenceHostConnection.clearOriginalConnection();
851             conferenceHostConnection.destroy();
852         } else {
853             conferenceHostConnection.setOriginalConnection(originalConnection);
854         }
855 
856         return prepareConference(conferenceHostConnection, request.getAccountHandle());
857     }
858 
prepareConference(Connection conn, PhoneAccountHandle phoneAccountHandle)859     Conference prepareConference(Connection conn, PhoneAccountHandle phoneAccountHandle) {
860         if (!(conn instanceof TelephonyConnection)) {
861             Log.w(this, "prepareConference returning NULL conference");
862             return null;
863         }
864 
865         TelephonyConnection connection = (TelephonyConnection)conn;
866 
867         ImsConference conference = new ImsConference(TelecomAccountRegistry.getInstance(this),
868                 mTelephonyConnectionServiceProxy, connection,
869                 phoneAccountHandle, () -> true,
870                 ImsConferenceController.getCarrierConfig(connection.getPhone()));
871         mImsConferenceController.addConference(conference);
872         conference.setVideoState(connection,
873                 connection.getVideoState());
874         conference.setVideoProvider(connection,
875                 connection.getVideoProvider());
876         conference.setStatusHints(connection.getStatusHints());
877         conference.setAddress(connection.getAddress(),
878                 connection.getAddressPresentation());
879         conference.setCallerDisplayName(connection.getCallerDisplayName(),
880                 connection.getCallerDisplayNamePresentation());
881         conference.setParticipants(connection.getParticipants());
882         return conference;
883     }
884 
885     @Override
onCreateIncomingConference( @ullable PhoneAccountHandle connectionManagerPhoneAccount, @NonNull final ConnectionRequest request)886     public @Nullable Conference onCreateIncomingConference(
887             @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
888             @NonNull final ConnectionRequest request) {
889         Log.i(this, "onCreateIncomingConference, request: " + request);
890         Connection connection = onCreateIncomingConnection(connectionManagerPhoneAccount, request);
891         Log.d(this, "onCreateIncomingConference, connection: %s", connection);
892         if (connection == null) {
893             Log.i(this, "onCreateIncomingConference, implementation returned null connection.");
894             return Conference.createFailedConference(
895                     new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"),
896                     request.getAccountHandle());
897         }
898 
899         final Phone phone = getPhoneForAccount(request.getAccountHandle(),
900                 false /* isEmergencyCall*/, null /* not an emergency call */);
901         if (phone == null) {
902             Log.d(this, "onCreateIncomingConference, phone is null");
903             return Conference.createFailedConference(
904                     DisconnectCauseUtil.toTelecomDisconnectCause(
905                             android.telephony.DisconnectCause.OUT_OF_SERVICE,
906                             "Phone is null"),
907                     request.getAccountHandle());
908         }
909 
910         return prepareConference(connection, request.getAccountHandle());
911     }
912 
913     @Override
onCreateOutgoingConference( @ullable PhoneAccountHandle connectionManagerPhoneAccount, @NonNull final ConnectionRequest request)914     public @Nullable Conference onCreateOutgoingConference(
915             @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
916             @NonNull final ConnectionRequest request) {
917         Log.i(this, "onCreateOutgoingConference, request: " + request);
918         Connection connection = onCreateOutgoingConnection(connectionManagerPhoneAccount, request);
919         Log.d(this, "onCreateOutgoingConference, connection: %s", connection);
920         if (connection == null) {
921             Log.i(this, "onCreateOutgoingConference, implementation returned null connection.");
922             return Conference.createFailedConference(
923                     new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"),
924                     request.getAccountHandle());
925         }
926 
927         final Phone phone = getPhoneForAccount(request.getAccountHandle(),
928                 false /* isEmergencyCall*/, null /* not an emergency call */);
929         if (phone == null) {
930             Log.d(this, "onCreateOutgoingConference, phone is null");
931             return Conference.createFailedConference(
932                     DisconnectCauseUtil.toTelecomDisconnectCause(
933                             android.telephony.DisconnectCause.OUT_OF_SERVICE,
934                             "Phone is null"),
935                     request.getAccountHandle());
936         }
937 
938         return placeOutgoingConference(request, connection, phone);
939     }
940 
getParticipantsToDial(List<Uri> participants)941     private String[] getParticipantsToDial(List<Uri> participants) {
942         String[] participantsToDial = new String[participants.size()];
943         int i = 0;
944         for (Uri participant : participants) {
945            participantsToDial[i] = participant.getSchemeSpecificPart();
946            i++;
947         }
948         return participantsToDial;
949     }
950 
951     @Override
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)952     public Connection onCreateOutgoingConnection(
953             PhoneAccountHandle connectionManagerPhoneAccount,
954             final ConnectionRequest request) {
955         Log.i(this, "onCreateOutgoingConnection, request: " + request);
956 
957         Uri handle = request.getAddress();
958         boolean isAdhocConference = request.isAdhocConferenceCall();
959 
960         if (!isAdhocConference && handle == null) {
961             Log.d(this, "onCreateOutgoingConnection, handle is null");
962             return Connection.createFailedConnection(
963                     mDisconnectCauseFactory.toTelecomDisconnectCause(
964                             android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
965                             "No phone number supplied"));
966         }
967 
968         String scheme = handle.getScheme();
969         String number;
970         if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
971             // TODO: We don't check for SecurityException here (requires
972             // CALL_PRIVILEGED permission).
973             final Phone phone = getPhoneForAccount(request.getAccountHandle(),
974                     false /* isEmergencyCall */, null /* not an emergency call */);
975             if (phone == null) {
976                 Log.d(this, "onCreateOutgoingConnection, phone is null");
977                 return Connection.createFailedConnection(
978                         mDisconnectCauseFactory.toTelecomDisconnectCause(
979                                 android.telephony.DisconnectCause.OUT_OF_SERVICE,
980                                 "Phone is null"));
981             }
982             number = phone.getVoiceMailNumber();
983             if (TextUtils.isEmpty(number)) {
984                 Log.d(this, "onCreateOutgoingConnection, no voicemail number set.");
985                 return Connection.createFailedConnection(
986                         mDisconnectCauseFactory.toTelecomDisconnectCause(
987                                 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING,
988                                 "Voicemail scheme provided but no voicemail number set.",
989                                 phone.getPhoneId()));
990             }
991 
992             // Convert voicemail: to tel:
993             handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
994         } else {
995             if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
996                 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme);
997                 return Connection.createFailedConnection(
998                         mDisconnectCauseFactory.toTelecomDisconnectCause(
999                                 android.telephony.DisconnectCause.INVALID_NUMBER,
1000                                 "Handle scheme is not type tel"));
1001             }
1002 
1003             number = handle.getSchemeSpecificPart();
1004             if (TextUtils.isEmpty(number)) {
1005                 Log.d(this, "onCreateOutgoingConnection, unable to parse number");
1006                 return Connection.createFailedConnection(
1007                         mDisconnectCauseFactory.toTelecomDisconnectCause(
1008                                 android.telephony.DisconnectCause.INVALID_NUMBER,
1009                                 "Unable to parse number"));
1010             }
1011 
1012             final Phone phone = getPhoneForAccount(request.getAccountHandle(),
1013                     false /* isEmergencyCall*/, null /* not an emergency call */);
1014             if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) {
1015                 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number
1016                 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and
1017                 // when dialed could lock LTE SIMs to 3G if not prohibited..
1018                 boolean disableActivation = false;
1019                 CarrierConfigManager cfgManager = (CarrierConfigManager)
1020                         phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1021                 if (cfgManager != null) {
1022                     disableActivation = cfgManager.getConfigForSubId(phone.getSubId())
1023                             .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL);
1024                 }
1025 
1026                 if (disableActivation) {
1027                     return Connection.createFailedConnection(
1028                             mDisconnectCauseFactory.toTelecomDisconnectCause(
1029                                     android.telephony.DisconnectCause
1030                                             .CDMA_ALREADY_ACTIVATED,
1031                                     "Tried to dial *228",
1032                                     phone.getPhoneId()));
1033                 }
1034             }
1035         }
1036 
1037         final boolean isEmergencyNumber = mTelephonyManagerProxy.isCurrentEmergencyNumber(number);
1038         // Find out if this is a test emergency number
1039         final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number);
1040 
1041         // Convert into emergency number if necessary
1042         // This is required in some regions (e.g. Taiwan).
1043         if (isEmergencyNumber) {
1044             final Phone phone = getPhoneForAccount(request.getAccountHandle(), false,
1045                     handle.getSchemeSpecificPart());
1046             // We only do the conversion if the phone is not in service. The un-converted
1047             // emergency numbers will go to the correct destination when the phone is in-service,
1048             // so they will only need the special emergency call setup when the phone is out of
1049             // service.
1050             if (phone == null || phone.getServiceState().getState()
1051                     != ServiceState.STATE_IN_SERVICE) {
1052                 String convertedNumber = mPhoneNumberUtilsProxy.convertToEmergencyNumber(this,
1053                         number);
1054                 if (!TextUtils.equals(convertedNumber, number)) {
1055                     Log.i(this, "onCreateOutgoingConnection, converted to emergency number");
1056                     number = convertedNumber;
1057                     handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
1058                 }
1059             }
1060         }
1061         final String numberToDial = number;
1062 
1063         final boolean isAirplaneModeOn = mDeviceState.isAirplaneModeOn(this);
1064 
1065         boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn))
1066                 || isRadioPowerDownOnBluetooth();
1067         boolean needToTurnOffSatellite = isSatelliteBlockingCall(isEmergencyNumber);
1068 
1069         // Get the right phone object from the account data passed in.
1070         final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
1071                 /* Note: when not an emergency, handle can be null for unknown callers */
1072                 handle == null ? null : handle.getSchemeSpecificPart());
1073 
1074         if (mDomainSelectionResolver.isDomainSelectionSupported()) {
1075             // Normal routing emergency number shall be handled by normal call domain selctor.
1076             if (isEmergencyNumber && !isNormalRouting(phone, number)) {
1077                 final Connection resultConnection =
1078                         placeEmergencyConnection(phone,
1079                                 request, numberToDial, isTestEmergencyNumber,
1080                                 handle, needToTurnOnRadio);
1081                 if (resultConnection != null) return resultConnection;
1082             }
1083         }
1084 
1085         if (needToTurnOnRadio || needToTurnOffSatellite) {
1086             final Uri resultHandle = handle;
1087             final int originalPhoneType = phone.getPhoneType();
1088             final Connection resultConnection = getTelephonyConnection(request, numberToDial,
1089                     isEmergencyNumber, resultHandle, phone);
1090             if (mRadioOnHelper == null) {
1091                 mRadioOnHelper = new RadioOnHelper(this);
1092             }
1093 
1094             if (isEmergencyNumber) {
1095                 mIsEmergencyCallPending = true;
1096             }
1097             int timeoutToOnTimeoutCallback = mDomainSelectionResolver.isDomainSelectionSupported()
1098                     ? TIMEOUT_TO_DYNAMIC_ROUTING_MS : 0;
1099             mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
1100                 @Override
1101                 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
1102                     handleOnComplete(isRadioReady, isEmergencyNumber, resultConnection, request,
1103                             numberToDial, resultHandle, originalPhoneType, phone);
1104                 }
1105 
1106                 @Override
1107                 public boolean onTimeout(Phone phone, int serviceState, boolean imsVoiceCapable) {
1108                     if (mDomainSelectionResolver.isDomainSelectionSupported()) {
1109                         return isEmergencyNumber;
1110                     }
1111                     return false;
1112                 }
1113 
1114                 @Override
1115                 public boolean isOkToCall(Phone phone, int serviceState, boolean imsVoiceCapable) {
1116                     // HAL 1.4 introduced a new variant of dial for emergency calls, which includes
1117                     // an isTesting parameter. For HAL 1.4+, do not wait for IN_SERVICE, this will
1118                     // be handled at the RIL/vendor level by emergencyDial(...).
1119                     boolean waitForInServiceToDialEmergency = isTestEmergencyNumber
1120                             && phone.getHalVersion(HAL_SERVICE_VOICE)
1121                             .less(RIL.RADIO_HAL_VERSION_1_4);
1122                     if (mDomainSelectionResolver.isDomainSelectionSupported()) {
1123                         if (isEmergencyNumber) {
1124                             // Since the domain selection service is enabled,
1125                             // dilaing normal routing emergency number only reaches here.
1126                             if (!isVoiceInService(phone, imsVoiceCapable)) {
1127                                 // Wait for voice in service.
1128                                 // That is, wait for IMS registration on PS only network.
1129                                 serviceState = ServiceState.STATE_OUT_OF_SERVICE;
1130                                 waitForInServiceToDialEmergency = true;
1131                             }
1132                         }
1133                     }
1134                     if (isEmergencyNumber && !waitForInServiceToDialEmergency) {
1135                         // We currently only look to make sure that the radio is on before dialing.
1136                         // We should be able to make emergency calls at any time after the radio has
1137                         // been powered on and isn't in the UNAVAILABLE state, even if it is
1138                         // reporting the OUT_OF_SERVICE state.
1139                         return phone.getState() == PhoneConstants.State.OFFHOOK
1140                                 || (phone.getServiceStateTracker().isRadioOn()
1141                                 && !mSatelliteController.isSatelliteEnabled());
1142                     } else {
1143                         SubscriptionInfoInternal subInfo = SubscriptionManagerService
1144                                 .getInstance().getSubscriptionInfoInternal(phone.getSubId());
1145                         // Wait until we are in service and ready to make calls. This can happen
1146                         // when we power down the radio on bluetooth to save power on watches or
1147                         // if it is a test emergency number and we have to wait for the device
1148                         // to move IN_SERVICE before the call can take place over normal
1149                         // routing.
1150                         return phone.getState() == PhoneConstants.State.OFFHOOK
1151                                 // Do not wait for voice in service on opportunistic SIMs.
1152                                 || subInfo != null && subInfo.isOpportunistic()
1153                                 || (serviceState == ServiceState.STATE_IN_SERVICE
1154                                 && !isSatelliteBlockingCall(isEmergencyNumber));
1155                     }
1156                 }
1157             }, isEmergencyNumber && !isTestEmergencyNumber, phone, isTestEmergencyNumber,
1158                     timeoutToOnTimeoutCallback);
1159             // Return the still unconnected GsmConnection and wait for the Radios to boot before
1160             // connecting it to the underlying Phone.
1161             return resultConnection;
1162         } else {
1163             if (!canAddCall() && !isEmergencyNumber) {
1164                 Log.d(this, "onCreateOutgoingConnection, cannot add call .");
1165                 return Connection.createFailedConnection(
1166                         new DisconnectCause(DisconnectCause.ERROR,
1167                                 getApplicationContext().getText(
1168                                         R.string.incall_error_cannot_add_call),
1169                                 getApplicationContext().getText(
1170                                         R.string.incall_error_cannot_add_call),
1171                                 "Add call restricted due to ongoing video call"));
1172             }
1173 
1174             if (!isEmergencyNumber) {
1175                 if (mSatelliteController.isSatelliteEnabled()
1176                         || isCallDisallowedDueToSatellite(phone)) {
1177                     Log.d(this, "onCreateOutgoingConnection, cannot make call in satellite mode.");
1178                     return Connection.createFailedConnection(
1179                             mDisconnectCauseFactory.toTelecomDisconnectCause(
1180                                     android.telephony.DisconnectCause.SATELLITE_ENABLED,
1181                                     "Call failed because satellite modem is enabled."));
1182                 }
1183                 final Connection resultConnection = getTelephonyConnection(request, numberToDial,
1184                         false, handle, phone);
1185                 if (isAdhocConference) {
1186                     if (resultConnection instanceof TelephonyConnection) {
1187                         TelephonyConnection conn = (TelephonyConnection)resultConnection;
1188                         conn.setParticipants(request.getParticipants());
1189                     }
1190                     return resultConnection;
1191                 } else {
1192                     if (mTelephonyManagerProxy.isConcurrentCallsPossible()) {
1193                         Conferenceable c = maybeHoldCallsOnOtherSubs(request.getAccountHandle());
1194                         if (c != null) {
1195                             delayDialForOtherSubHold(phone, c, (success) -> {
1196                                 Log.d(this,
1197                                         "onCreateOutgoingConn - delayDialForOtherSubHold"
1198                                                 + " success = " + success);
1199                                 if (success) {
1200                                     placeOutgoingConnection(request, resultConnection,
1201                                             phone);
1202                                 } else {
1203                                     ((TelephonyConnection) resultConnection).hangup(
1204                                             android.telephony.DisconnectCause.LOCAL);
1205                                 }
1206                             });
1207                             return resultConnection;
1208                         }
1209                     }
1210                     return placeOutgoingConnection(request, resultConnection, phone);
1211                 }
1212             } else {
1213                 final Connection resultConnection = getTelephonyConnection(request, numberToDial,
1214                         true, handle, phone);
1215 
1216                 CompletableFuture<Void> maybeHoldFuture = CompletableFuture.completedFuture(null);
1217                 if (mTelephonyManagerProxy.isConcurrentCallsPossible()
1218                         && shouldHoldForEmergencyCall(phone)) {
1219                     // If the PhoneAccountHandle was adjusted on building the TelephonyConnection,
1220                     // the relevant PhoneAccountHandle will be updated in resultConnection.
1221                     PhoneAccountHandle phoneAccountHandle =
1222                             resultConnection.getPhoneAccountHandle() == null
1223                             ? request.getAccountHandle() : resultConnection.getPhoneAccountHandle();
1224                     Conferenceable c = maybeHoldCallsOnOtherSubs(phoneAccountHandle);
1225                     if (c != null) {
1226                         maybeHoldFuture = delayDialForOtherSubHold(phone, c, (success) -> {
1227                             Log.i(this, "onCreateOutgoingConn emergency-"
1228                                     + " delayDialForOtherSubHold success = " + success);
1229                             if (!success) {
1230                                 // Terminates the existing call to make way for the emergency call.
1231                                 hangup(c, android.telephony.DisconnectCause
1232                                         .OUTGOING_EMERGENCY_CALL_PLACED);
1233                             }
1234                         });
1235                     }
1236                 }
1237                 Consumer<Boolean> ddsSwitchConsumer = (result) -> {
1238                     Log.i(this, "onCreateOutgoingConn emergency-"
1239                             + " delayDialForDdsSwitch result = " + result);
1240                     placeOutgoingConnection(request, resultConnection, phone);
1241                 };
1242                 maybeHoldFuture.thenRun(() -> delayDialForDdsSwitch(phone, ddsSwitchConsumer));
1243                 return resultConnection;
1244             }
1245         }
1246     }
1247 
placeOutgoingConnection(ConnectionRequest request, Connection resultConnection, Phone phone)1248     private Connection placeOutgoingConnection(ConnectionRequest request,
1249             Connection resultConnection, Phone phone) {
1250         // If there was a failure, the resulting connection will not be a TelephonyConnection,
1251         // so don't place the call!
1252         if (resultConnection instanceof TelephonyConnection) {
1253             if (request.getExtras() != null && request.getExtras().getBoolean(
1254                     TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) {
1255                 ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true);
1256             }
1257             placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
1258         }
1259         return resultConnection;
1260     }
1261 
isEmergencyNumberTestNumber(String number)1262     private boolean isEmergencyNumberTestNumber(String number) {
1263         number = PhoneNumberUtils.stripSeparators(number);
1264         Map<Integer, List<EmergencyNumber>> list =
1265                 mTelephonyManagerProxy.getCurrentEmergencyNumberList();
1266         // Do not worry about which subscription the test emergency call is on yet, only detect that
1267         // it is an emergency.
1268         for (Integer sub : list.keySet()) {
1269             for (EmergencyNumber eNumber : list.get(sub)) {
1270                 if (number.equals(eNumber.getNumber())
1271                         && eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)) {
1272                     Log.i(this, "isEmergencyNumberTestNumber: " + number + " has been detected as "
1273                             + "a test emergency number.,");
1274                     return true;
1275                 }
1276             }
1277         }
1278         return false;
1279     }
1280 
1281     /**
1282      * @return whether radio has recently been turned on for emergency call but hasn't actually
1283      * dialed the call yet.
1284      */
isEmergencyCallPending()1285     public boolean isEmergencyCallPending() {
1286         return mIsEmergencyCallPending;
1287     }
1288 
1289     /**
1290      * Whether the cellular radio is power off because the device is on Bluetooth.
1291      */
isRadioPowerDownOnBluetooth()1292     private boolean isRadioPowerDownOnBluetooth() {
1293         final boolean allowed = mDeviceState.isRadioPowerDownAllowedOnBluetooth(this);
1294         final int cellOn = mDeviceState.getCellOnStatus(this);
1295         return (allowed && cellOn == PhoneConstants.CELL_ON_FLAG && !isRadioOn());
1296     }
1297 
1298     /**
1299      * Handle the onComplete callback of RadioOnStateListener.
1300      */
handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, Connection originalConnection, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, Phone phone)1301     private void handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber,
1302             Connection originalConnection, ConnectionRequest request, String numberToDial,
1303             Uri handle, int originalPhoneType, Phone phone) {
1304         // Make sure the Call has not already been canceled by the user.
1305         if (originalConnection.getState() == Connection.STATE_DISCONNECTED) {
1306             Log.i(this, "Call disconnected before the outgoing call was placed. Skipping call "
1307                     + "placement.");
1308             if (isEmergencyNumber) {
1309                 // If call is already canceled by the user, notify modem to exit emergency call
1310                 // mode by sending radio on with forEmergencyCall=false.
1311                 for (Phone curPhone : mPhoneFactoryProxy.getPhones()) {
1312                     curPhone.setRadioPower(true, false, false, true);
1313                 }
1314                 mIsEmergencyCallPending = false;
1315             }
1316             return;
1317         }
1318         if (isRadioReady) {
1319             if (!isEmergencyNumber) {
1320                 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial,
1321                         handle, originalPhoneType, false);
1322             } else {
1323                 delayDialForDdsSwitch(phone, result -> {
1324                     Log.i(this, "handleOnComplete - delayDialForDdsSwitch "
1325                             + "result = " + result);
1326                     adjustAndPlaceOutgoingConnection(phone, originalConnection, request,
1327                             numberToDial, handle, originalPhoneType, true);
1328                     mIsEmergencyCallPending = false;
1329                 });
1330             }
1331         } else {
1332             if (isSatelliteBlockingCall(isEmergencyNumber)) {
1333                 Log.w(LOG_TAG, "handleOnComplete, failed to turn off satellite modem");
1334                 closeOrDestroyConnection(originalConnection,
1335                         mDisconnectCauseFactory.toTelecomDisconnectCause(
1336                                 android.telephony.DisconnectCause.SATELLITE_ENABLED,
1337                                 "Failed to turn off satellite modem."));
1338             } else {
1339                 Log.w(LOG_TAG, "handleOnComplete, failed to turn on radio");
1340                 closeOrDestroyConnection(originalConnection,
1341                         mDisconnectCauseFactory.toTelecomDisconnectCause(
1342                                 android.telephony.DisconnectCause.POWER_OFF,
1343                                 "Failed to turn on radio."));
1344             }
1345             mIsEmergencyCallPending = false;
1346         }
1347     }
1348 
adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, boolean isEmergencyNumber)1349     private void adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate,
1350             ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType,
1351             boolean isEmergencyNumber) {
1352         // If the PhoneType of the Phone being used is different than the Default Phone, then we
1353         // need to create a new Connection using that PhoneType and replace it in Telecom.
1354         if (phone.getPhoneType() != originalPhoneType) {
1355             Connection repConnection = getTelephonyConnection(request, numberToDial,
1356                     isEmergencyNumber, handle, phone);
1357             // If there was a failure, the resulting connection will not be a TelephonyConnection,
1358             // so don't place the call, just return!
1359             if (repConnection instanceof TelephonyConnection) {
1360                 placeOutgoingConnection((TelephonyConnection) repConnection, phone, request);
1361             }
1362             // Notify Telecom of the new Connection type.
1363             // TODO: Switch out the underlying connection instead of creating a new
1364             // one and causing UI Jank.
1365             boolean noActiveSimCard = SubscriptionManagerService.getInstance()
1366                     .getActiveSubInfoCount(phone.getContext().getOpPackageName(),
1367                             phone.getContext().getAttributionTag()) == 0;
1368             // If there's no active sim card and the device is in emergency mode, use E account.
1369             addExistingConnection(mPhoneUtilsProxy.makePstnPhoneAccountHandleWithPrefix(
1370                     phone, "", isEmergencyNumber && noActiveSimCard), repConnection);
1371             // Remove the old connection from Telecom after.
1372             closeOrDestroyConnection(connectionToEvaluate,
1373                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1374                             android.telephony.DisconnectCause.OUTGOING_CANCELED,
1375                             "Reconnecting outgoing Emergency Call.",
1376                             phone.getPhoneId()));
1377         } else {
1378             placeOutgoingConnection((TelephonyConnection) connectionToEvaluate, phone, request);
1379         }
1380     }
1381 
1382     /**
1383      * @return {@code true} if any other call is disabling the ability to add calls, {@code false}
1384      *      otherwise.
1385      */
canAddCall()1386     private boolean canAddCall() {
1387         Collection<Connection> connections = getAllConnections();
1388         for (Connection connection : connections) {
1389             if (connection.getExtras() != null &&
1390                     connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) {
1391                 return false;
1392             }
1393         }
1394         return true;
1395     }
1396 
getTelephonyConnection(final ConnectionRequest request, final String number, boolean isEmergencyNumber, final Uri handle, Phone phone)1397     private Connection getTelephonyConnection(final ConnectionRequest request, final String number,
1398             boolean isEmergencyNumber, final Uri handle, Phone phone) {
1399 
1400         if (phone == null) {
1401             final Context context = getApplicationContext();
1402             if (mDeviceState.shouldCheckSimStateBeforeOutgoingCall(this)) {
1403                 // Check SIM card state before the outgoing call.
1404                 // Start the SIM unlock activity if PIN_REQUIRED.
1405                 final Phone defaultPhone = mPhoneFactoryProxy.getDefaultPhone();
1406                 final IccCard icc = defaultPhone.getIccCard();
1407                 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN;
1408                 if (icc != null) {
1409                     simState = icc.getState();
1410                 }
1411                 if (simState == IccCardConstants.State.PIN_REQUIRED) {
1412                     final String simUnlockUiPackage = context.getResources().getString(
1413                             R.string.config_simUnlockUiPackage);
1414                     final String simUnlockUiClass = context.getResources().getString(
1415                             R.string.config_simUnlockUiClass);
1416                     if (simUnlockUiPackage != null && simUnlockUiClass != null) {
1417                         Intent simUnlockIntent = new Intent().setComponent(new ComponentName(
1418                                 simUnlockUiPackage, simUnlockUiClass));
1419                         simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1420                         try {
1421                             context.startActivity(simUnlockIntent);
1422                         } catch (ActivityNotFoundException exception) {
1423                             Log.e(this, exception, "Unable to find SIM unlock UI activity.");
1424                         }
1425                     }
1426                     return Connection.createFailedConnection(
1427                             mDisconnectCauseFactory.toTelecomDisconnectCause(
1428                                     android.telephony.DisconnectCause.OUT_OF_SERVICE,
1429                                     "SIM_STATE_PIN_REQUIRED"));
1430                 }
1431             }
1432 
1433             Log.d(this, "onCreateOutgoingConnection, phone is null");
1434             return Connection.createFailedConnection(
1435                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1436                             android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null"));
1437         }
1438 
1439         // Check both voice & data RAT to enable normal CS call,
1440         // when voice RAT is OOS but Data RAT is present.
1441         int state = phone.getServiceState().getState();
1442         if (state == ServiceState.STATE_OUT_OF_SERVICE) {
1443             int dataNetType = phone.getServiceState().getDataNetworkType();
1444             if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
1445                     dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA ||
1446                     dataNetType == TelephonyManager.NETWORK_TYPE_NR) {
1447                 state = phone.getServiceState().getDataRegistrationState();
1448             }
1449         }
1450 
1451         // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if
1452         // carrier configuration specifies that we cannot make non-emergency calls in ECM mode.
1453         if (!isEmergencyNumber && phone.isInEcm()) {
1454             boolean allowNonEmergencyCalls = true;
1455             CarrierConfigManager cfgManager = (CarrierConfigManager)
1456                     phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1457             if (cfgManager != null) {
1458                 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId())
1459                         .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL);
1460             }
1461 
1462             if (!allowNonEmergencyCalls) {
1463                 return Connection.createFailedConnection(
1464                         mDisconnectCauseFactory.toTelecomDisconnectCause(
1465                                 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY,
1466                                 "Cannot make non-emergency call in ECM mode.",
1467                                 phone.getPhoneId()));
1468             }
1469         }
1470 
1471         if (!isEmergencyNumber) {
1472             switch (state) {
1473                 case ServiceState.STATE_IN_SERVICE:
1474                 case ServiceState.STATE_EMERGENCY_ONLY:
1475                     break;
1476                 case ServiceState.STATE_OUT_OF_SERVICE:
1477                     if (phone.isUtEnabled() && number.endsWith("#")) {
1478                         Log.d(this, "onCreateOutgoingConnection dial for UT");
1479                         break;
1480                     } else {
1481                         return Connection.createFailedConnection(
1482                                 mDisconnectCauseFactory.toTelecomDisconnectCause(
1483                                         android.telephony.DisconnectCause.OUT_OF_SERVICE,
1484                                         "ServiceState.STATE_OUT_OF_SERVICE",
1485                                         phone.getPhoneId()));
1486                     }
1487                 case ServiceState.STATE_POWER_OFF:
1488                     // Don't disconnect if radio is power off because the device is on Bluetooth.
1489                     if (isRadioPowerDownOnBluetooth()) {
1490                         break;
1491                     }
1492                     return Connection.createFailedConnection(
1493                             mDisconnectCauseFactory.toTelecomDisconnectCause(
1494                                     android.telephony.DisconnectCause.POWER_OFF,
1495                                     "ServiceState.STATE_POWER_OFF",
1496                                     phone.getPhoneId()));
1497                 default:
1498                     Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state);
1499                     return Connection.createFailedConnection(
1500                             mDisconnectCauseFactory.toTelecomDisconnectCause(
1501                                     android.telephony.DisconnectCause.OUTGOING_FAILURE,
1502                                     "Unknown service state " + state,
1503                                     phone.getPhoneId()));
1504             }
1505         }
1506 
1507         final boolean isTtyModeEnabled = mDeviceState.isTtyModeEnabled(this);
1508         if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled
1509                 && !isEmergencyNumber) {
1510             return Connection.createFailedConnection(mDisconnectCauseFactory.toTelecomDisconnectCause(
1511                     android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED,
1512                     null, phone.getPhoneId()));
1513         }
1514 
1515         // Check for additional limits on CDMA phones.
1516         final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone);
1517         if (failedConnection != null) {
1518             return failedConnection;
1519         }
1520 
1521         // Check roaming status to see if we should block custom call forwarding codes
1522         if (blockCallForwardingNumberWhileRoaming(phone, number)) {
1523             return Connection.createFailedConnection(
1524                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1525                             android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING,
1526                             "Call forwarding while roaming",
1527                             phone.getPhoneId()));
1528         }
1529 
1530         PhoneAccountHandle accountHandle = adjustAccountHandle(phone, request.getAccountHandle());
1531         final TelephonyConnection connection =
1532                 createConnectionFor(phone, null, true /* isOutgoing */, accountHandle,
1533                         request.getTelecomCallId(), request.isAdhocConferenceCall());
1534         if (connection == null) {
1535             return Connection.createFailedConnection(
1536                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1537                             android.telephony.DisconnectCause.OUTGOING_FAILURE,
1538                             "Invalid phone type",
1539                             phone.getPhoneId()));
1540         }
1541         if (!Objects.equals(request.getAccountHandle(), accountHandle)) {
1542             Log.i(this, "onCreateOutgoingConnection, update phoneAccountHandle, accountHandle = "
1543                     + accountHandle);
1544             connection.setPhoneAccountHandle(accountHandle);
1545         }
1546         connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
1547         connection.setTelephonyConnectionInitializing();
1548         connection.setTelephonyVideoState(request.getVideoState());
1549         connection.setRttTextStream(request.getRttTextStream());
1550         connection.setTtyEnabled(isTtyModeEnabled);
1551         connection.setIsAdhocConferenceCall(request.isAdhocConferenceCall());
1552         connection.setParticipants(request.getParticipants());
1553         return connection;
1554     }
1555 
1556     @Override
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1557     public Connection onCreateIncomingConnection(
1558             PhoneAccountHandle connectionManagerPhoneAccount,
1559             ConnectionRequest request) {
1560         Log.i(this, "onCreateIncomingConnection, request: " + request);
1561         // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM),
1562         // make sure the PhoneAccount lookup retrieves the default Emergency Phone.
1563         PhoneAccountHandle accountHandle = request.getAccountHandle();
1564         boolean isEmergency = false;
1565         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
1566                 accountHandle.getId())) {
1567             Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " +
1568                     "Treat as an Emergency Call.");
1569             isEmergency = true;
1570         }
1571         Phone phone = getPhoneForAccount(accountHandle, isEmergency,
1572                 /* Note: when not an emergency, handle can be null for unknown callers */
1573                 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart());
1574         if (phone == null) {
1575             return Connection.createFailedConnection(
1576                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1577                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
1578                             "Phone is null"));
1579         }
1580 
1581         Bundle extras = request.getExtras();
1582         String disconnectMessage = null;
1583         if (extras.containsKey(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE)) {
1584             disconnectMessage = extras.getString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE);
1585             Log.i(this, "onCreateIncomingConnection Disconnect message " + disconnectMessage);
1586         }
1587 
1588         Call call = phone.getRingingCall();
1589         if (!call.getState().isRinging()
1590                 || (disconnectMessage != null
1591                 && disconnectMessage.equals(TelecomManager.CALL_AUTO_DISCONNECT_MESSAGE_STRING))) {
1592             Log.i(this, "onCreateIncomingConnection, no ringing call");
1593             Connection connection = Connection.createFailedConnection(
1594                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1595                             android.telephony.DisconnectCause.INCOMING_MISSED,
1596                             "Found no ringing call",
1597                             phone.getPhoneId()));
1598 
1599             long time = extras.getLong(TelecomManager.EXTRA_CALL_CREATED_EPOCH_TIME_MILLIS);
1600             if (time != 0) {
1601                 Log.i(this, "onCreateIncomingConnection. Set connect time info.");
1602                 connection.setConnectTimeMillis(time);
1603             }
1604 
1605             Uri address = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
1606             if (address != null) {
1607                 Log.i(this, "onCreateIncomingConnection. Set caller id info.");
1608                 connection.setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
1609             }
1610 
1611             return connection;
1612         }
1613 
1614         // If there are multiple Connections tracked in a call, grab the latest, since it is most
1615         // likely to be the incoming call.
1616         com.android.internal.telephony.Connection originalConnection = call.getLatestConnection();
1617         if (isOriginalConnectionKnown(originalConnection)) {
1618             Log.i(this, "onCreateIncomingConnection, original connection already registered");
1619             return Connection.createCanceledConnection();
1620         }
1621 
1622         TelephonyConnection connection =
1623                 createConnectionFor(phone, originalConnection, false /* isOutgoing */,
1624                         request.getAccountHandle(), request.getTelecomCallId(),
1625                         request.isAdhocConferenceCall());
1626 
1627         handleIncomingRtt(request, originalConnection);
1628         if (connection == null) {
1629             return Connection.createCanceledConnection();
1630         } else {
1631             // Add extra to call if answering this incoming call would cause an in progress call on
1632             // another subscription to be disconnected.
1633             maybeIndicateAnsweringWillDisconnect(connection, request.getAccountHandle());
1634 
1635             connection.setTtyEnabled(mDeviceState.isTtyModeEnabled(getApplicationContext()));
1636             return connection;
1637         }
1638     }
1639 
handleIncomingRtt(ConnectionRequest request, com.android.internal.telephony.Connection originalConnection)1640     private void handleIncomingRtt(ConnectionRequest request,
1641             com.android.internal.telephony.Connection originalConnection) {
1642         if (originalConnection == null
1643                 || originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) {
1644             if (request.isRequestingRtt()) {
1645                 Log.w(this, "Requesting RTT on non-IMS call, ignoring");
1646             }
1647             return;
1648         }
1649 
1650         ImsPhoneConnection imsOriginalConnection = (ImsPhoneConnection) originalConnection;
1651         if (!request.isRequestingRtt()) {
1652             if (imsOriginalConnection.isRttEnabledForCall()) {
1653                 Log.w(this, "Incoming call requested RTT but we did not get a RttTextStream");
1654             }
1655             return;
1656         }
1657 
1658         Log.i(this, "Setting RTT stream on ImsPhoneConnection in case we need it later");
1659         imsOriginalConnection.setCurrentRttTextStream(request.getRttTextStream());
1660 
1661         if (!imsOriginalConnection.isRttEnabledForCall()) {
1662             if (request.isRequestingRtt()) {
1663                 Log.w(this, "Incoming call processed as RTT but did not come in as one. Ignoring");
1664             }
1665             return;
1666         }
1667 
1668         Log.i(this, "Setting the call to be answered with RTT on.");
1669         imsOriginalConnection.getImsCall().setAnswerWithRtt();
1670     }
1671 
1672     /**
1673      * Called by the {@link ConnectionService} when a newly created {@link Connection} has been
1674      * added to the {@link ConnectionService} and sent to Telecom.  Here it is safe to send
1675      * connection events.
1676      *
1677      * @param connection the {@link Connection}.
1678      */
1679     @Override
onCreateConnectionComplete(Connection connection)1680     public void onCreateConnectionComplete(Connection connection) {
1681         if (connection instanceof TelephonyConnection) {
1682             TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
1683             maybeSendInternationalCallEvent(telephonyConnection);
1684         }
1685     }
1686 
1687     @Override
onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1688     public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
1689             ConnectionRequest request) {
1690         Log.i(this, "onCreateIncomingConnectionFailed, request: " + request);
1691         // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM),
1692         // make sure the PhoneAccount lookup retrieves the default Emergency Phone.
1693         PhoneAccountHandle accountHandle = request.getAccountHandle();
1694         boolean isEmergency = false;
1695         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
1696                 accountHandle.getId())) {
1697             Log.w(this, "onCreateIncomingConnectionFailed:Emergency call failed... ");
1698             isEmergency = true;
1699         }
1700         Phone phone = getPhoneForAccount(accountHandle, isEmergency,
1701                 /* Note: when not an emergency, handle can be null for unknown callers */
1702                 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart());
1703         if (phone == null) {
1704             Log.w(this, "onCreateIncomingConnectionFailed: can not find corresponding phone.");
1705             return;
1706         }
1707 
1708         Call call = phone.getRingingCall();
1709         if (!call.getState().isRinging()) {
1710             Log.w(this, "onCreateIncomingConnectionFailed, no ringing call found for failed call");
1711             return;
1712         }
1713 
1714         com.android.internal.telephony.Connection originalConnection =
1715                 call.getState() == Call.State.WAITING
1716                         ? call.getLatestConnection() : call.getEarliestConnection();
1717         TelephonyConnection knownConnection =
1718                 getConnectionForOriginalConnection(originalConnection);
1719         if (knownConnection != null) {
1720             Log.w(this, "onCreateIncomingConnectionFailed, original connection already registered."
1721                     + " Hanging it up.");
1722             knownConnection.onAbort();
1723             return;
1724         }
1725 
1726         TelephonyConnection connection =
1727                 createConnectionFor(phone, originalConnection, false /* isOutgoing */,
1728                         request.getAccountHandle(), request.getTelecomCallId());
1729         if (connection == null) {
1730             Log.w(this, "onCreateIncomingConnectionFailed, TelephonyConnection created as null, "
1731                     + "ignoring.");
1732             return;
1733         }
1734 
1735         // We have to do all of this work because in some cases, hanging up the call maps to
1736         // different underlying signaling (CDMA), which is already encapsulated in
1737         // TelephonyConnection.
1738         connection.onReject();
1739         connection.close();
1740     }
1741 
1742     /**
1743      * Called by the {@link ConnectionService} when a newly created {@link Conference} has been
1744      * added to the {@link ConnectionService} and sent to Telecom.  Here it is safe to send
1745      * connection events.
1746      *
1747      * @param conference the {@link Conference}.
1748      */
1749     @Override
onCreateConferenceComplete(Conference conference)1750     public void onCreateConferenceComplete(Conference conference) {
1751         if (conference instanceof ImsConference) {
1752             ImsConference imsConference = (ImsConference)conference;
1753             TelephonyConnection telephonyConnection =
1754                     (TelephonyConnection)(imsConference.getConferenceHost());
1755             maybeSendInternationalCallEvent(telephonyConnection);
1756         }
1757     }
1758 
onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1759     public void onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount,
1760             ConnectionRequest request) {
1761         Log.i(this, "onCreateIncomingConferenceFailed, request: " + request);
1762         onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request);
1763     }
1764 
1765     @Override
triggerConferenceRecalculate()1766     public void triggerConferenceRecalculate() {
1767         if (mTelephonyConferenceController.shouldRecalculate()) {
1768             mTelephonyConferenceController.recalculate();
1769         }
1770     }
1771 
1772     @Override
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1773     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
1774             ConnectionRequest request) {
1775         Log.i(this, "onCreateUnknownConnection, request: " + request);
1776         // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's
1777         // Emergency PhoneAccount
1778         PhoneAccountHandle accountHandle = request.getAccountHandle();
1779         boolean isEmergency = false;
1780         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
1781                 accountHandle.getId())) {
1782             Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " +
1783                     "Treat as an Emergency Call.");
1784             isEmergency = true;
1785         }
1786         Phone phone = getPhoneForAccount(accountHandle, isEmergency,
1787                 /* Note: when not an emergency, handle can be null for unknown callers */
1788                 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart());
1789         if (phone == null) {
1790             return Connection.createFailedConnection(
1791                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1792                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
1793                             "Phone is null"));
1794         }
1795         Bundle extras = request.getExtras();
1796 
1797         final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>();
1798 
1799         // Handle the case where an unknown connection has an IMS external call ID specified; we can
1800         // skip the rest of the guesswork and just grad that unknown call now.
1801         if (phone.getImsPhone() != null && extras != null &&
1802                 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) {
1803 
1804             ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
1805             ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker();
1806             int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID,
1807                     -1);
1808 
1809             if (externalCallTracker != null) {
1810                 com.android.internal.telephony.Connection connection =
1811                         externalCallTracker.getConnectionById(externalCallId);
1812 
1813                 if (connection != null) {
1814                     allConnections.add(connection);
1815                 }
1816             }
1817         }
1818 
1819         if (allConnections.isEmpty()) {
1820             final Call ringingCall = phone.getRingingCall();
1821             if (ringingCall.hasConnections()) {
1822                 allConnections.addAll(ringingCall.getConnections());
1823             }
1824             final Call foregroundCall = phone.getForegroundCall();
1825             if ((foregroundCall.getState() != Call.State.DISCONNECTED)
1826                     && (foregroundCall.hasConnections())) {
1827                 allConnections.addAll(foregroundCall.getConnections());
1828             }
1829             if (phone.getImsPhone() != null) {
1830                 final Call imsFgCall = phone.getImsPhone().getForegroundCall();
1831                 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall
1832                         .hasConnections()) {
1833                     allConnections.addAll(imsFgCall.getConnections());
1834                 }
1835             }
1836             final Call backgroundCall = phone.getBackgroundCall();
1837             if (backgroundCall.hasConnections()) {
1838                 allConnections.addAll(phone.getBackgroundCall().getConnections());
1839             }
1840         }
1841 
1842         com.android.internal.telephony.Connection unknownConnection = null;
1843         for (com.android.internal.telephony.Connection telephonyConnection : allConnections) {
1844             if (!isOriginalConnectionKnown(telephonyConnection)) {
1845                 unknownConnection = telephonyConnection;
1846                 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection);
1847                 break;
1848             }
1849         }
1850 
1851         if (unknownConnection == null) {
1852             Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection.");
1853             return Connection.createCanceledConnection();
1854         }
1855 
1856         // We should rely on the originalConnection to get the video state.  The request coming
1857         // from Telecom does not know the video state of the unknown call.
1858         int videoState = unknownConnection != null ? unknownConnection.getVideoState() :
1859                 VideoProfile.STATE_AUDIO_ONLY;
1860 
1861         TelephonyConnection connection =
1862                 createConnectionFor(phone, unknownConnection,
1863                         !unknownConnection.isIncoming() /* isOutgoing */,
1864                         request.getAccountHandle(), request.getTelecomCallId()
1865                 );
1866 
1867         if (connection == null) {
1868             return Connection.createCanceledConnection();
1869         } else {
1870             connection.updateState();
1871             return connection;
1872         }
1873     }
1874 
1875     /**
1876      * Conferences two connections.
1877      *
1878      * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has
1879      * a limitation in that it can only specify conferenceables which are instances of
1880      * {@link android.telecom.RemoteConnection}.  In the case of an {@link ImsConference}, the
1881      * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge
1882      * a {@link Conference} and a {@link Connection}.  As a result when, merging a
1883      * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference}
1884      * require merging a {@link ConferenceParticipantConnection} which is a child of the
1885      * {@link Conference} with a {@link TelephonyConnection}.  The
1886      * {@link ConferenceParticipantConnection} class does not have the capability to initiate a
1887      * conference merge, so we need to call
1888      * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or
1889      * {@code connection2}, one of which is an instance of {@link TelephonyConnection}.
1890      *
1891      * @param connection1 A connection to merge into a conference call.
1892      * @param connection2 A connection to merge into a conference call.
1893      */
1894     @Override
onConference(Connection connection1, Connection connection2)1895     public void onConference(Connection connection1, Connection connection2) {
1896         if (connection1 instanceof TelephonyConnection) {
1897             ((TelephonyConnection) connection1).performConference(connection2);
1898         } else if (connection2 instanceof TelephonyConnection) {
1899             ((TelephonyConnection) connection2).performConference(connection1);
1900         } else {
1901             Log.w(this, "onConference - cannot merge connections " +
1902                     "Connection1: %s, Connection2: %2", connection1, connection2);
1903         }
1904     }
1905 
1906     @Override
onConnectionAdded(Connection connection)1907     public void onConnectionAdded(Connection connection) {
1908         if (connection instanceof Holdable && !isExternalConnection(connection)) {
1909             mHoldTracker.addHoldable((Holdable) connection);
1910         }
1911     }
1912 
1913     @Override
onConnectionRemoved(Connection connection)1914     public void onConnectionRemoved(Connection connection) {
1915         if (connection instanceof Holdable && !isExternalConnection(connection)) {
1916             mHoldTracker.removeHoldable((Holdable) connection);
1917         }
1918     }
1919 
1920     @Override
onConferenceAdded(Conference conference)1921     public void onConferenceAdded(Conference conference) {
1922         if (conference instanceof Holdable) {
1923             mHoldTracker.addHoldable((Holdable) conference);
1924         }
1925     }
1926 
1927     @Override
onConferenceRemoved(Conference conference)1928     public void onConferenceRemoved(Conference conference) {
1929         if (conference instanceof Holdable) {
1930             mHoldTracker.removeHoldable((Holdable) conference);
1931         }
1932     }
1933 
isExternalConnection(Connection connection)1934     private boolean isExternalConnection(Connection connection) {
1935         return (connection.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL)
1936                 == Connection.PROPERTY_IS_EXTERNAL_CALL;
1937     }
1938 
blockCallForwardingNumberWhileRoaming(Phone phone, String number)1939     private boolean blockCallForwardingNumberWhileRoaming(Phone phone, String number) {
1940         if (phone == null || TextUtils.isEmpty(number) || !phone.getServiceState().getRoaming()) {
1941             return false;
1942         }
1943         boolean allowPrefixIms = true;
1944         String[] blockPrefixes = null;
1945         CarrierConfigManager cfgManager = (CarrierConfigManager)
1946                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1947         if (cfgManager != null) {
1948             allowPrefixIms = cfgManager.getConfigForSubId(phone.getSubId()).getBoolean(
1949                     CarrierConfigManager.KEY_SUPPORT_IMS_CALL_FORWARDING_WHILE_ROAMING_BOOL,
1950                     true);
1951             if (allowPrefixIms && useImsForAudioOnlyCall(phone)) {
1952                 return false;
1953             }
1954             blockPrefixes = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray(
1955                     CarrierConfigManager.KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY);
1956         }
1957 
1958         if (blockPrefixes != null) {
1959             for (String prefix : blockPrefixes) {
1960                 if (number.startsWith(prefix)) {
1961                     return true;
1962                 }
1963             }
1964         }
1965         return false;
1966     }
1967 
useImsForAudioOnlyCall(Phone phone)1968     private boolean useImsForAudioOnlyCall(Phone phone) {
1969         Phone imsPhone = phone.getImsPhone();
1970 
1971         return imsPhone != null
1972                 && (imsPhone.isVoiceOverCellularImsEnabled() || imsPhone.isWifiCallingEnabled())
1973                 && (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE);
1974     }
1975 
isRadioOn()1976     private boolean isRadioOn() {
1977         boolean result = false;
1978         for (Phone phone : mPhoneFactoryProxy.getPhones()) {
1979             result |= phone.isRadioOn();
1980         }
1981         return result;
1982     }
1983 
isSatelliteBlockingCall(boolean isEmergencyNumber)1984     private boolean isSatelliteBlockingCall(boolean isEmergencyNumber) {
1985         if (isEmergencyNumber) {
1986             return mSatelliteController.isSatelliteEnabled();
1987         } else {
1988             return mSatelliteController.isDemoModeEnabled();
1989         }
1990     }
1991 
makeCachedConnectionPhonePair( TelephonyConnection c)1992     private Pair<WeakReference<TelephonyConnection>, Queue<Phone>> makeCachedConnectionPhonePair(
1993             TelephonyConnection c) {
1994         Queue<Phone> phones = new LinkedList<>(Arrays.asList(mPhoneFactoryProxy.getPhones()));
1995         return new Pair<>(new WeakReference<>(c), phones);
1996     }
1997 
1998     // Update the mEmergencyRetryCache by removing the Phone used to call the last failed emergency
1999     // number and then moving it to the back of the queue if it is not a permanent failure cause
2000     // from the modem.
updateCachedConnectionPhonePair(TelephonyConnection c, Phone phone, boolean isPermanentFailure)2001     private void updateCachedConnectionPhonePair(TelephonyConnection c, Phone phone,
2002             boolean isPermanentFailure) {
2003         // No cache exists, create a new one.
2004         if (mEmergencyRetryCache == null) {
2005             Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache");
2006             mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
2007         // Cache is stale, create a new one with the new TelephonyConnection.
2008         } else if (mEmergencyRetryCache.first.get() != c) {
2009             Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating.");
2010             mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
2011         }
2012 
2013         Queue<Phone> cachedPhones = mEmergencyRetryCache.second;
2014         // Need to refer default phone considering ImsPhone because
2015         // cachedPhones is a list that contains default phones.
2016         Phone phoneUsed = phone.getDefaultPhone();
2017         if (phoneUsed == null) {
2018             return;
2019         }
2020         // Remove phone used from the list, but for temporary fail cause, it will be added
2021         // back to list further in this method. However in case of permanent failure, the
2022         // phone shouldn't be reused, hence it will not be added back again.
2023         cachedPhones.remove(phoneUsed);
2024         Log.i(this, "updateCachedConnectionPhonePair, isPermanentFailure:" + isPermanentFailure);
2025         if (!isPermanentFailure) {
2026             // In case of temporary failure, add the phone back, this will result adding it
2027             // to tail of list mEmergencyRetryCache.second, giving other phone more
2028             // priority and that is what we want.
2029             cachedPhones.offer(phoneUsed);
2030         }
2031     }
2032 
2033     /**
2034      * Updates a cache containing all of the slots that are available for redial at any point.
2035      *
2036      * - If a Connection returns with the disconnect cause EMERGENCY_TEMP_FAILURE, keep that phone
2037      * in the cache, but move it to the lowest priority in the list. Then, place the emergency call
2038      * on the next phone in the list.
2039      * - If a Connection returns with the disconnect cause EMERGENCY_PERM_FAILURE, remove that phone
2040      * from the cache and pull another phone from the cache to place the emergency call.
2041      *
2042      * This will continue until there are no more slots to dial on.
2043      */
2044     @VisibleForTesting
retryOutgoingOriginalConnection(TelephonyConnection c, Phone phone, boolean isPermanentFailure)2045     public void retryOutgoingOriginalConnection(TelephonyConnection c,
2046             Phone phone, boolean isPermanentFailure) {
2047         int phoneId = (phone == null) ? -1 : phone.getPhoneId();
2048         updateCachedConnectionPhonePair(c, phone, isPermanentFailure);
2049         // Pull next phone to use from the cache or null if it is empty
2050         Phone newPhoneToUse = (mEmergencyRetryCache.second != null)
2051                 ? mEmergencyRetryCache.second.peek() : null;
2052         if (newPhoneToUse != null) {
2053             int videoState = c.getVideoState();
2054             Bundle connExtras = c.getExtras();
2055             Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse);
2056             c.clearOriginalConnection();
2057             if (phoneId != newPhoneToUse.getPhoneId()) {
2058                 if (mTelephonyManagerProxy.getMaxNumberOfSimultaneouslyActiveSims() < 2) {
2059                     disconnectAllCallsOnOtherSubs(
2060                             mPhoneUtilsProxy.makePstnPhoneAccountHandle(newPhoneToUse));
2061                 }
2062                 updatePhoneAccount(c, newPhoneToUse);
2063             }
2064             if (mDomainSelectionResolver.isDomainSelectionSupported()) {
2065                 onEmergencyRedial(c, newPhoneToUse);
2066                 return;
2067             }
2068             placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
2069         } else {
2070             // We have run out of Phones to use. Disconnect the call and destroy the connection.
2071             Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting.");
2072             closeOrDestroyConnection(c, new DisconnectCause(DisconnectCause.ERROR));
2073         }
2074     }
2075 
updatePhoneAccount(TelephonyConnection connection, Phone phone)2076     private void updatePhoneAccount(TelephonyConnection connection, Phone phone) {
2077         PhoneAccountHandle pHandle = mPhoneUtilsProxy.makePstnPhoneAccountHandle(phone);
2078         // For ECall handling on MSIM, until the request reaches here (i.e PhoneApp), we don't know
2079         // on which phone account ECall can be placed. After deciding, we should notify Telecom of
2080         // the change so that the proper PhoneAccount can be displayed.
2081         Log.i(this, "updatePhoneAccount setPhoneAccountHandle, account = " + pHandle);
2082         connection.setPhoneAccountHandle(pHandle);
2083     }
2084 
placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)2085     private void placeOutgoingConnection(
2086             TelephonyConnection connection, Phone phone, ConnectionRequest request) {
2087         placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras());
2088     }
2089 
placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras)2090     private void placeOutgoingConnection(
2091             TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
2092 
2093         String number = (connection.getAddress() != null)
2094                 ? connection.getAddress().getSchemeSpecificPart()
2095                 : "";
2096 
2097         if (showDataDialog(phone, number)) {
2098             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
2099                         android.telephony.DisconnectCause.DIALED_MMI, "UT is not available"));
2100             return;
2101         }
2102 
2103         if (extras != null && extras.containsKey(TelecomManager.EXTRA_OUTGOING_PICTURE)) {
2104             ParcelUuid uuid = extras.getParcelable(TelecomManager.EXTRA_OUTGOING_PICTURE);
2105             CallComposerPictureManager.getInstance(phone.getContext(), phone.getSubId())
2106                     .storeUploadedPictureToCallLog(uuid.getUuid(), (uri) -> {
2107                         if (uri != null) {
2108                             try {
2109                                 Bundle b = new Bundle();
2110                                 b.putParcelable(TelecomManager.EXTRA_PICTURE_URI, uri);
2111                                 connection.putTelephonyExtras(b);
2112                             } catch (Exception e) {
2113                                 Log.e(this, e, "Couldn't set picture extra on outgoing call");
2114                             }
2115                         }
2116                     });
2117         }
2118 
2119         final com.android.internal.telephony.Connection originalConnection;
2120         try {
2121             if (phone != null) {
2122                 boolean isEmergency = mTelephonyManagerProxy.isCurrentEmergencyNumber(number);
2123                 Log.i(this, "placeOutgoingConnection isEmergency=" + isEmergency);
2124                 if (isEmergency) {
2125                     handleEmergencyCallStartedForSatelliteSOSMessageRecommender(connection, phone);
2126                     if (!getAllConnections().isEmpty()) {
2127                         if (!shouldHoldForEmergencyCall(phone)) {
2128                             // If we do not support holding ongoing calls for an outgoing
2129                             // emergency call, disconnect the ongoing calls.
2130                             for (Connection c : getAllConnections()) {
2131                                 if (!c.equals(connection)
2132                                         && c.getState() != Connection.STATE_DISCONNECTED
2133                                         && c instanceof TelephonyConnection) {
2134                                     ((TelephonyConnection) c).hangup(
2135                                             android.telephony.DisconnectCause
2136                                                     .OUTGOING_EMERGENCY_CALL_PLACED);
2137                                 }
2138                             }
2139                             for (Conference c : getAllConferences()) {
2140                                 if (c.getState() != Connection.STATE_DISCONNECTED
2141                                         && c instanceof Conference) {
2142                                     ((Conference) c).onDisconnect();
2143                                 }
2144                             }
2145                         } else if (!isVideoCallHoldAllowed(phone)) {
2146                             // If we do not support holding ongoing video call for an outgoing
2147                             // emergency call, disconnect the ongoing video call.
2148                             for (Connection c : getAllConnections()) {
2149                                 if (!c.equals(connection)
2150                                         && c.getState() == Connection.STATE_ACTIVE
2151                                         && VideoProfile.isVideo(c.getVideoState())
2152                                         && c instanceof TelephonyConnection) {
2153                                     ((TelephonyConnection) c).hangup(
2154                                             android.telephony.DisconnectCause
2155                                                     .OUTGOING_EMERGENCY_CALL_PLACED);
2156                                     break;
2157                                 }
2158                             }
2159                         }
2160                     }
2161                     if (mDomainSelectionResolver.isDomainSelectionSupported()) {
2162                         if (isNormalRouting(phone, number)
2163                                     && handleOutgoingCallConnection(number, connection,
2164                                             phone, videoState)) {
2165                             /** Normal routing emergency number shall be handled
2166                              * by normal call domain selctor.*/
2167                             Log.i(this, "placeOutgoingConnection normal routing number");
2168                             return;
2169                         }
2170                     }
2171                 } else if (handleOutgoingCallConnection(number, connection,
2172                         phone, videoState)) {
2173                     return;
2174                 }
2175                 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
2176                                 .setVideoState(videoState)
2177                                 .setIntentExtras(extras)
2178                                 .setRttTextStream(connection.getRttTextStream())
2179                                 .build(),
2180                         // We need to wait until the phone has been chosen in GsmCdmaPhone to
2181                         // register for the associated TelephonyConnection call event listeners.
2182                         connection::registerForCallEvents);
2183             } else {
2184                 originalConnection = null;
2185             }
2186         } catch (CallStateException e) {
2187             Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
2188             connection.unregisterForCallEvents();
2189             handleCallStateException(e, connection, phone);
2190             return;
2191         }
2192         if (originalConnection == null) {
2193             int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
2194             // On GSM phones, null connection means that we dialed an MMI code
2195             if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
2196                     phone.isUtEnabled()) {
2197                 Log.d(this, "dialed MMI code");
2198                 int subId = phone.getSubId();
2199                 Log.d(this, "subId: "+subId);
2200                 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
2201                 final Intent intent = new Intent(this, MMIDialogActivity.class);
2202                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
2203                         Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
2204                 if (SubscriptionManager.isValidSubscriptionId(subId)) {
2205                     SubscriptionManager.putSubscriptionIdExtra(intent, subId);
2206                 }
2207                 startActivity(intent);
2208             }
2209             Log.d(this, "placeOutgoingConnection, phone.dial returned null");
2210             connection.setTelephonyConnectionDisconnected(
2211                     mDisconnectCauseFactory.toTelecomDisconnectCause(telephonyDisconnectCause,
2212                             "Connection is null", phone.getPhoneId()));
2213             connection.close();
2214         } else {
2215             if (!getMainThreadHandler().getLooper().isCurrentThread()) {
2216                 Log.w(this, "placeOriginalConnection - Unexpected, this call "
2217                         + "should always be on the main thread.");
2218                 getMainThreadHandler().post(() -> {
2219                     if (connection.getOriginalConnection() == null) {
2220                         connection.setOriginalConnection(originalConnection);
2221                     }
2222                 });
2223             } else {
2224                 connection.setOriginalConnection(originalConnection);
2225             }
2226         }
2227     }
2228 
handleOutgoingCallConnectionByCallDomainSelection( int domain, Phone phone, String number, int videoState)2229     private void handleOutgoingCallConnectionByCallDomainSelection(
2230             int domain, Phone phone, String number, int videoState) {
2231         Log.d(this, "Call Domain Selected : " + domain);
2232         try {
2233             Bundle extras = mNormalCallConnection.getExtras();
2234             if (extras == null) {
2235                 extras = new Bundle();
2236             }
2237             extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
2238             // Add flag to bundle for comparing legacy and new domain selection results. When
2239             // EXTRA_COMPARE_DOMAIN flag is true, legacy domain selection result is used for
2240             // placing the call and if both the results are not same then bug report is generated.
2241             DeviceConfig.Properties properties = //read all telephony properties
2242                     DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TELEPHONY);
2243             boolean compareDomainSelection =
2244                     properties.getBoolean(KEY_DOMAIN_COMPARE_FEATURE_ENABLED_FLAG, false);
2245             extras.putBoolean(PhoneConstants.EXTRA_COMPARE_DOMAIN, compareDomainSelection);
2246 
2247             if (phone != null) {
2248                 Log.v(LOG_TAG, "Call dialing. Domain: " + domain);
2249                 com.android.internal.telephony.Connection connection =
2250                         phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
2251                                         .setVideoState(videoState)
2252                                         .setIntentExtras(extras)
2253                                         .setRttTextStream(mNormalCallConnection.getRttTextStream())
2254                                         .setIsWpsCall(NormalCallDomainSelectionConnection
2255                                                 .isWpsCall(number))
2256                                         .build(),
2257                                 mNormalCallConnection::registerForCallEvents);
2258 
2259                 mNormalCallConnection.setOriginalConnection(connection);
2260                 mNormalCallConnection.addTelephonyConnectionListener(mNormalCallConnectionListener);
2261                 return;
2262             } else {
2263                 Log.w(this, "placeOutgoingCallConnection. Dialing failed. Phone is null");
2264                 mNormalCallConnection.setTelephonyConnectionDisconnected(
2265                         mDisconnectCauseFactory.toTelecomDisconnectCause(
2266                                 android.telephony.DisconnectCause.OUTGOING_FAILURE,
2267                                 "Phone is null", phone.getPhoneId()));
2268                 mNormalCallConnection.close();
2269             }
2270         } catch (CallStateException e) {
2271             Log.e(this, e, "Call placeOutgoingCallConnection, phone.dial exception: " + e);
2272             mNormalCallConnection.unregisterForCallEvents();
2273             handleCallStateException(e, mNormalCallConnection, phone);
2274         } catch (Exception e) {
2275             Log.e(this, e, "Call exception in placeOutgoingCallConnection:" + e);
2276             mNormalCallConnection.unregisterForCallEvents();
2277             mNormalCallConnection.setTelephonyConnectionDisconnected(DisconnectCauseUtil
2278                     .toTelecomDisconnectCause(android.telephony.DisconnectCause.OUTGOING_FAILURE,
2279                             e.getMessage(), phone.getPhoneId()));
2280             mNormalCallConnection.close();
2281         }
2282         if (mDomainSelectionConnection != null) {
2283             mDomainSelectionConnection.finishSelection();
2284             mDomainSelectionConnection = null;
2285         }
2286         mNormalCallConnection = null;
2287     }
2288 
handleOutgoingCallConnection( String number, TelephonyConnection connection, Phone phone, int videoState)2289     private boolean handleOutgoingCallConnection(
2290             String number, TelephonyConnection connection, Phone phone, int videoState) {
2291 
2292         if (!mDomainSelectionResolver.isDomainSelectionSupported()) {
2293             return false;
2294         }
2295 
2296         if (phone == null) {
2297             return false;
2298         }
2299 
2300         String dialPart = PhoneNumberUtils.extractNetworkPortionAlt(
2301                 PhoneNumberUtils.stripSeparators(number));
2302         boolean isMmiCode = (dialPart.startsWith("*") || dialPart.startsWith("#"))
2303                 && dialPart.endsWith("#");
2304         boolean isSuppServiceCode = ImsPhoneMmiCode.isSuppServiceCodes(dialPart, phone);
2305 
2306         // If the number is both an MMI code and a supplementary service code,
2307         // it shall be treated as UT. In this case, domain selection is not performed.
2308         if (isMmiCode && isSuppServiceCode) {
2309             Log.v(LOG_TAG, "UT code not handled by call domain selection.");
2310             return false;
2311         }
2312 
2313         // Check and select same domain as ongoing call on the same subscription (if exists)
2314         int activeCallDomain = getActiveCallDomain(phone.getSubId());
2315         if (activeCallDomain != NetworkRegistrationInfo.DOMAIN_UNKNOWN
2316                 && !NormalCallDomainSelectionConnection.isWpsCall(number)) {
2317             Log.d(LOG_TAG, "Selecting same domain as ongoing call on same subId");
2318             mNormalCallConnection = connection;
2319             handleOutgoingCallConnectionByCallDomainSelection(
2320                     activeCallDomain, phone, number, videoState);
2321             return true;
2322         }
2323 
2324         mDomainSelectionConnection = mDomainSelectionResolver
2325                 .getDomainSelectionConnection(phone, SELECTOR_TYPE_CALLING, false);
2326         if (mDomainSelectionConnection == null) {
2327             return false;
2328         }
2329         Log.d(LOG_TAG, "Call Connection created");
2330         SelectionAttributes selectionAttributes =
2331                 new SelectionAttributes.Builder(phone.getPhoneId(), phone.getSubId(),
2332                         SELECTOR_TYPE_CALLING)
2333                         .setNumber(number)
2334                         .setEmergency(false)
2335                         .setVideoCall(VideoProfile.isVideo(videoState))
2336                         .build();
2337 
2338         NormalCallDomainSelectionConnection normalCallDomainSelectionConnection =
2339                 (NormalCallDomainSelectionConnection) mDomainSelectionConnection;
2340         CompletableFuture<Integer> future = normalCallDomainSelectionConnection
2341                 .createNormalConnection(selectionAttributes,
2342                         mCallDomainSelectionConnectionCallback);
2343         Log.d(LOG_TAG, "Call Domain selection triggered.");
2344 
2345         mNormalCallConnection = connection;
2346         future.thenAcceptAsync((domain) -> handleOutgoingCallConnectionByCallDomainSelection(
2347                 domain, phone, number, videoState), mDomainSelectionMainExecutor);
2348         return true;
2349     }
2350 
2351     @SuppressWarnings("FutureReturnValueIgnored")
placeEmergencyConnection( final Phone phone, final ConnectionRequest request, final String numberToDial, final boolean isTestEmergencyNumber, final Uri handle, final boolean needToTurnOnRadio)2352     private Connection placeEmergencyConnection(
2353             final Phone phone, final ConnectionRequest request,
2354             final String numberToDial, final boolean isTestEmergencyNumber,
2355             final Uri handle, final boolean needToTurnOnRadio) {
2356 
2357         final Connection resultConnection =
2358                 getTelephonyConnection(request, numberToDial, true, handle, phone);
2359 
2360         if (resultConnection instanceof TelephonyConnection) {
2361             Log.i(this, "placeEmergencyConnection");
2362 
2363             mIsEmergencyCallPending = true;
2364             ((TelephonyConnection) resultConnection).addTelephonyConnectionListener(
2365                     mEmergencyConnectionListener);
2366 
2367             if (mEmergencyStateTracker == null) {
2368                 mEmergencyStateTracker = EmergencyStateTracker.getInstance();
2369             }
2370 
2371             mEmergencyCallId = resultConnection.getTelecomCallId();
2372             CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencyCall(
2373                     phone, mEmergencyCallId, isTestEmergencyNumber);
2374             future.thenAccept((result) -> {
2375                 Log.d(this, "startEmergencyCall-complete result=" + result);
2376                 if (mEmergencyCallId == null) {
2377                     Log.i(this, "startEmergencyCall-complete dialing canceled");
2378                     return;
2379                 }
2380                 if (result == android.telephony.DisconnectCause.NOT_DISCONNECTED) {
2381                     createEmergencyConnection(phone, (TelephonyConnection) resultConnection,
2382                             numberToDial, request, needToTurnOnRadio,
2383                             mEmergencyStateTracker.getEmergencyRegResult());
2384                 } else {
2385                     mEmergencyConnection = null;
2386                     String reason = "Couldn't setup emergency call";
2387                     if (result == android.telephony.DisconnectCause.POWER_OFF) {
2388                         reason = "Failed to turn on radio.";
2389                     }
2390                     ((TelephonyConnection) resultConnection).setTelephonyConnectionDisconnected(
2391                             mDisconnectCauseFactory.toTelecomDisconnectCause(result, reason));
2392                     ((TelephonyConnection) resultConnection).close();
2393                     mIsEmergencyCallPending = false;
2394                 }
2395             });
2396             mEmergencyConnection = (TelephonyConnection) resultConnection;
2397             return resultConnection;
2398         }
2399         Log.i(this, "placeEmergencyConnection returns null");
2400         return null;
2401     }
2402 
2403     @SuppressWarnings("FutureReturnValueIgnored")
createEmergencyConnection(final Phone phone, final TelephonyConnection resultConnection, final String number, final ConnectionRequest request, boolean needToTurnOnRadio, final EmergencyRegResult regResult)2404     private void createEmergencyConnection(final Phone phone,
2405             final TelephonyConnection resultConnection, final String number,
2406             final ConnectionRequest request, boolean needToTurnOnRadio,
2407             final EmergencyRegResult regResult) {
2408         Log.i(this, "createEmergencyConnection");
2409 
2410         if (phone.getImsPhone() == null) {
2411             // Dialing emergency calls over IMS is not available without ImsPhone instance.
2412             Log.w(this, "createEmergencyConnection no ImsPhone");
2413             dialCsEmergencyCall(phone, resultConnection, request);
2414             return;
2415         }
2416 
2417         ImsManager imsManager = mImsManager;
2418         if (imsManager == null) {
2419             // mImsManager is not null only while unit test.
2420             imsManager = ImsManager.getInstance(phone.getContext(), phone.getPhoneId());
2421         }
2422         if (!imsManager.isNonTtyOrTtyOnVolteEnabled()) {
2423             Log.w(this, "createEmergencyConnection - TTY on VoLTE is not supported.");
2424             dialCsEmergencyCall(phone, resultConnection, request);
2425             return;
2426         }
2427 
2428         DomainSelectionConnection selectConnection =
2429                 mDomainSelectionResolver.getDomainSelectionConnection(
2430                         phone, SELECTOR_TYPE_CALLING, true);
2431 
2432         if (selectConnection == null) {
2433             // While the domain selection service is enabled, the valid
2434             // {@link DomainSelectionConnection} is not available.
2435             // This can happen when the domain selection service is not available.
2436             Log.w(this, "createEmergencyConnection - no selectionConnection");
2437             dialCsEmergencyCall(phone, resultConnection, request);
2438             return;
2439         }
2440 
2441         mEmergencyCallDomainSelectionConnection =
2442                 (EmergencyCallDomainSelectionConnection) selectConnection;
2443 
2444         DomainSelectionService.SelectionAttributes attr =
2445                 EmergencyCallDomainSelectionConnection.getSelectionAttributes(
2446                         phone.getPhoneId(), phone.getSubId(), needToTurnOnRadio,
2447                         request.getTelecomCallId(), number, 0, null, regResult);
2448 
2449         CompletableFuture<Integer> future =
2450                 mEmergencyCallDomainSelectionConnection.createEmergencyConnection(
2451                         attr, mEmergencyDomainSelectionConnectionCallback);
2452         future.thenAcceptAsync((result) -> {
2453             Log.d(this, "createEmergencyConnection-complete result=" + result);
2454             if (mEmergencyCallId == null) {
2455                 Log.i(this, "createEmergencyConnection-complete dialing canceled");
2456                 return;
2457             }
2458             Bundle extras = request.getExtras();
2459             extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, result);
2460             placeOutgoingConnection(request, resultConnection, phone);
2461             mIsEmergencyCallPending = false;
2462         }, mDomainSelectionMainExecutor);
2463     }
2464 
dialCsEmergencyCall(final Phone phone, final TelephonyConnection resultConnection, final ConnectionRequest request)2465     private void dialCsEmergencyCall(final Phone phone,
2466             final TelephonyConnection resultConnection, final ConnectionRequest request) {
2467         Log.d(this, "dialCsEmergencyCall");
2468         Bundle extras = request.getExtras();
2469         extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, NetworkRegistrationInfo.DOMAIN_CS);
2470         mDomainSelectionMainExecutor.execute(
2471                 () -> {
2472                     if (mEmergencyCallId == null) {
2473                         Log.i(this, "dialCsEmergencyCall dialing canceled");
2474                         return;
2475                     }
2476                     placeOutgoingConnection(request, resultConnection, phone);
2477                 });
2478     }
2479 
releaseEmergencyCallDomainSelection(boolean cancel)2480     private void releaseEmergencyCallDomainSelection(boolean cancel) {
2481         if (mEmergencyCallDomainSelectionConnection != null) {
2482             if (cancel) mEmergencyCallDomainSelectionConnection.cancelSelection();
2483             else mEmergencyCallDomainSelectionConnection.finishSelection();
2484             mEmergencyCallDomainSelectionConnection = null;
2485         }
2486         mIsEmergencyCallPending = false;
2487         mEmergencyConnection = null;
2488     }
2489 
2490     /**
2491      * Determine whether reselection of domain is required or not.
2492      * @param c the {@link Connection} instance.
2493      * @param callFailCause the reason why CS call is disconnected. Allowed values are defined in
2494      * {@link com.android.internal.telephony.CallFailCause}.
2495      * @param reasonInfo the reason why PS call is disconnected.
2496      * @return {@code true} if reselection of domain is required.
2497      */
maybeReselectDomain(final TelephonyConnection c, int callFailCause, ImsReasonInfo reasonInfo)2498     public boolean maybeReselectDomain(final TelephonyConnection c,
2499             int callFailCause, ImsReasonInfo reasonInfo) {
2500         if (!mDomainSelectionResolver.isDomainSelectionSupported()) return false;
2501 
2502         Log.i(this, "maybeReselectDomain csCause=" +  callFailCause + ", psCause=" + reasonInfo);
2503         if (TextUtils.equals(mEmergencyCallId, c.getTelecomCallId())) {
2504             if (mEmergencyCallDomainSelectionConnection != null) {
2505                 return maybeReselectDomainForEmergencyCall(c, callFailCause, reasonInfo);
2506             }
2507             Log.i(this, "maybeReselectDomain endCall()");
2508             c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
2509             mEmergencyStateTracker.endCall(c.getTelecomCallId());
2510             mEmergencyCallId = null;
2511             return false;
2512         }
2513 
2514         if (reasonInfo != null) {
2515             int reasonCode = reasonInfo.getCode();
2516             int extraCode = reasonInfo.getExtraCode();
2517             if ((reasonCode == ImsReasonInfo.CODE_SIP_ALTERNATE_EMERGENCY_CALL)
2518                     || (reasonCode == ImsReasonInfo.CODE_LOCAL_CALL_CS_RETRY_REQUIRED
2519                             && extraCode == ImsReasonInfo.EXTRA_CODE_CALL_RETRY_EMERGENCY)) {
2520                 // clear normal call domain selector
2521                 c.removeTelephonyConnectionListener(mNormalCallConnectionListener);
2522                 if (mDomainSelectionConnection != null) {
2523                     mDomainSelectionConnection.finishSelection();
2524                     mDomainSelectionConnection = null;
2525                 }
2526                 mNormalCallConnection = null;
2527 
2528                 onEmergencyRedial(c, c.getPhone().getDefaultPhone());
2529                 return true;
2530             }
2531         }
2532 
2533         return maybeReselectDomainForNormalCall(c, callFailCause, reasonInfo);
2534     }
2535 
maybeReselectDomainForEmergencyCall(final TelephonyConnection c, int callFailCause, ImsReasonInfo reasonInfo)2536     private boolean maybeReselectDomainForEmergencyCall(final TelephonyConnection c,
2537             int callFailCause, ImsReasonInfo reasonInfo) {
2538         Log.i(this, "maybeReselectDomainForEmergencyCall "
2539                 + "csCause=" +  callFailCause + ", psCause=" + reasonInfo);
2540 
2541         if (c.getOriginalConnection() != null
2542                 && c.getOriginalConnection().getDisconnectCause()
2543                         != android.telephony.DisconnectCause.LOCAL
2544                 && c.getOriginalConnection().getDisconnectCause()
2545                         != android.telephony.DisconnectCause.POWER_OFF) {
2546 
2547             DomainSelectionService.SelectionAttributes attr =
2548                     EmergencyCallDomainSelectionConnection.getSelectionAttributes(
2549                             c.getPhone().getPhoneId(), c.getPhone().getSubId(), false,
2550                             c.getTelecomCallId(), c.getAddress().getSchemeSpecificPart(),
2551                             callFailCause, reasonInfo, null);
2552 
2553             CompletableFuture<Integer> future =
2554                     mEmergencyCallDomainSelectionConnection.reselectDomain(attr);
2555             // TeleponyConnection will clear original connection. Keep the reference to Phone.
2556             final Phone phone = c.getPhone().getDefaultPhone();
2557             if (future != null) {
2558                 future.thenAcceptAsync((result) -> {
2559                     Log.d(this, "reselectDomain-complete");
2560                     if (mEmergencyCallId == null) {
2561                         Log.i(this, "reselectDomain-complete dialing canceled");
2562                         return;
2563                     }
2564                     onEmergencyRedialOnDomain(c, phone, result);
2565                 }, mDomainSelectionMainExecutor);
2566                 return true;
2567             }
2568         }
2569 
2570         Log.i(this, "maybeReselectDomainForEmergencyCall endCall()");
2571         c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
2572         releaseEmergencyCallDomainSelection(true);
2573         mEmergencyStateTracker.endCall(c.getTelecomCallId());
2574         mEmergencyCallId = null;
2575         return false;
2576     }
2577 
isNormalRouting(Phone phone, String number)2578     private boolean isNormalRouting(Phone phone, String number) {
2579         if (phone.getEmergencyNumberTracker() != null) {
2580             // Note: There can potentially be multiple instances of EmergencyNumber found; if any of
2581             // them have normal routing, then use normal routing.
2582             List<EmergencyNumber> nums = phone.getEmergencyNumberTracker().getEmergencyNumbers(
2583                     number);
2584             return nums.stream().anyMatch(n ->
2585                     n.getEmergencyCallRouting() == EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL);
2586         }
2587         return false;
2588     }
2589 
2590     /**
2591      * Determines the phone to use for a normal routed emergency call.
2592      * @param number The emergency number.
2593      * @return The {@link Phone} to place the normal routed emergency call on, or {@code null} if
2594      * none was found.
2595      */
2596     @VisibleForTesting
getPhoneForNormalRoutedEmergencyCall(String number)2597     public Phone getPhoneForNormalRoutedEmergencyCall(String number) {
2598         return Stream.of(mPhoneFactoryProxy.getPhones())
2599                 .filter(p -> p.shouldPreferInServiceSimForNormalRoutedEmergencyCall()
2600                         && isNormalRouting(p, number)
2601                         && isAvailableForEmergencyCalls(p,
2602                                 EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL))
2603                 .findFirst().orElse(null);
2604     }
2605 
isVoiceInService(Phone phone, boolean imsVoiceCapable)2606     private boolean isVoiceInService(Phone phone, boolean imsVoiceCapable) {
2607         // Dialing normal call is available.
2608         if (phone.isWifiCallingEnabled()) {
2609             Log.i(this, "isVoiceInService VoWi-Fi available");
2610             return true;
2611         }
2612 
2613         ServiceState ss = phone.getServiceStateTracker().getServiceState();
2614         if (ss.getState() != ServiceState.STATE_IN_SERVICE) return false;
2615 
2616         NetworkRegistrationInfo regState = ss.getNetworkRegistrationInfo(
2617                 NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
2618         if (regState != null) {
2619             int registrationState = regState.getRegistrationState();
2620             if (registrationState != NetworkRegistrationInfo.REGISTRATION_STATE_HOME
2621                     && registrationState != NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING) {
2622                 return true;
2623             }
2624 
2625             int networkType = regState.getAccessNetworkTechnology();
2626             if (networkType == TelephonyManager.NETWORK_TYPE_LTE) {
2627                 DataSpecificRegistrationInfo regInfo = regState.getDataSpecificInfo();
2628                 if (regInfo.getLteAttachResultType()
2629                         == DataSpecificRegistrationInfo.LTE_ATTACH_TYPE_COMBINED) {
2630                     Log.i(this, "isVoiceInService combined attach");
2631                     return true;
2632                 }
2633             }
2634 
2635             if (networkType == TelephonyManager.NETWORK_TYPE_NR
2636                     || networkType == TelephonyManager.NETWORK_TYPE_LTE) {
2637                 Log.i(this, "isVoiceInService PS only network, IMS available " + imsVoiceCapable);
2638                 return imsVoiceCapable;
2639             }
2640         }
2641         return true;
2642     }
2643 
maybeReselectDomainForNormalCall( final TelephonyConnection c, int callFailCause, ImsReasonInfo reasonInfo)2644     private boolean maybeReselectDomainForNormalCall(
2645             final TelephonyConnection c, int callFailCause, ImsReasonInfo reasonInfo) {
2646 
2647         Log.i(LOG_TAG, "maybeReselectDomainForNormalCall " + "csCause:" +  callFailCause
2648                 + ", psCause:" + reasonInfo);
2649 
2650         if (mDomainSelectionConnection != null && c.getOriginalConnection() != null) {
2651             Phone phone = c.getPhone().getDefaultPhone();
2652             final String number = c.getAddress().getSchemeSpecificPart();
2653             int videoState = c.getOriginalConnection().getVideoState();
2654             SelectionAttributes selectionAttributes = NormalCallDomainSelectionConnection
2655                     .getSelectionAttributes(phone.getPhoneId(), phone.getSubId(),
2656                             c.getTelecomCallId(), number, VideoProfile.isVideo(videoState),
2657                             callFailCause, reasonInfo);
2658 
2659             Log.d(LOG_TAG, "Reselecting the domain for call");
2660             mNormalCallConnection = c;
2661             CompletableFuture<Integer> future = mDomainSelectionConnection
2662                     .reselectDomain(selectionAttributes);
2663             if (future != null) {
2664                 future.thenAcceptAsync((result) -> {
2665                     onNormalCallRedial(c, phone, result, videoState);
2666                 }, mDomainSelectionMainExecutor);
2667                 return true;
2668             }
2669         }
2670 
2671         c.removeTelephonyConnectionListener(mTelephonyConnectionListener);
2672         if (mDomainSelectionConnection != null) {
2673             mDomainSelectionConnection.finishSelection();
2674             mDomainSelectionConnection = null;
2675         }
2676         mNormalCallConnection = null;
2677         Log.d(LOG_TAG, "Reselect call domain not triggered.");
2678         return false;
2679     }
2680 
onEmergencyRedialOnDomain(TelephonyConnection connection, final Phone phone, @NetworkRegistrationInfo.Domain int domain)2681     private void onEmergencyRedialOnDomain(TelephonyConnection connection,
2682             final Phone phone, @NetworkRegistrationInfo.Domain int domain) {
2683         Log.i(this, "onEmergencyRedialOnDomain phoneId=" + phone.getPhoneId()
2684                 + ", domain=" + DomainSelectionService.getDomainName(domain));
2685 
2686         String number = connection.getAddress().getSchemeSpecificPart();
2687 
2688         // Indicates undetectable emergency number with DialArgs
2689         boolean isEmergency = false;
2690         int eccCategory = EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_UNSPECIFIED;
2691         if (connection.getEmergencyServiceCategory() != null) {
2692             isEmergency = true;
2693             eccCategory = connection.getEmergencyServiceCategory();
2694             Log.i(this, "onEmergencyRedialOnDomain eccCategory=" + eccCategory);
2695         }
2696 
2697         Bundle extras = new Bundle();
2698         extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
2699 
2700         com.android.internal.telephony.Connection originalConnection =
2701                 connection.getOriginalConnection();
2702         try {
2703             if (phone != null) {
2704                 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
2705                         .setVideoState(VideoProfile.STATE_AUDIO_ONLY)
2706                         .setIntentExtras(extras)
2707                         .setRttTextStream(connection.getRttTextStream())
2708                         .setIsEmergency(isEmergency)
2709                         .setEccCategory(eccCategory)
2710                         .build(),
2711                         connection::registerForCallEvents);
2712             }
2713         } catch (CallStateException e) {
2714             Log.e(this, e, "onEmergencyRedialOnDomain, exception: " + e);
2715         }
2716         if (originalConnection == null) {
2717             Log.d(this, "onEmergencyRedialOnDomain, phone.dial returned null");
2718             connection.setDisconnected(
2719                     mDisconnectCauseFactory.toTelecomDisconnectCause(
2720                                 android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
2721                                 "unknown error"));
2722         } else {
2723             connection.setOriginalConnection(originalConnection);
2724         }
2725     }
2726 
2727     @SuppressWarnings("FutureReturnValueIgnored")
onEmergencyRedial(final TelephonyConnection c, final Phone phone)2728     private void onEmergencyRedial(final TelephonyConnection c, final Phone phone) {
2729         Log.i(this, "onEmergencyRedial phoneId=" + phone.getPhoneId());
2730 
2731         final String number = c.getAddress().getSchemeSpecificPart();
2732         final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number);
2733 
2734         mIsEmergencyCallPending = true;
2735         c.addTelephonyConnectionListener(mEmergencyConnectionListener);
2736         handleEmergencyCallStartedForSatelliteSOSMessageRecommender(c, phone);
2737 
2738         if (mEmergencyStateTracker == null) {
2739             mEmergencyStateTracker = EmergencyStateTracker.getInstance();
2740         }
2741 
2742         mEmergencyCallId = c.getTelecomCallId();
2743         CompletableFuture<Integer> future = mEmergencyStateTracker.startEmergencyCall(
2744                 phone, mEmergencyCallId, isTestEmergencyNumber);
2745         future.thenAccept((result) -> {
2746             Log.d(this, "onEmergencyRedial-complete result=" + result);
2747             if (mEmergencyCallId == null) {
2748                 Log.i(this, "onEmergencyRedial-complete dialing canceled");
2749                 return;
2750             }
2751             if (result == android.telephony.DisconnectCause.NOT_DISCONNECTED) {
2752                 DomainSelectionConnection selectConnection =
2753                         mDomainSelectionResolver.getDomainSelectionConnection(
2754                                 phone, SELECTOR_TYPE_CALLING, true);
2755 
2756                 if (selectConnection == null) {
2757                     Log.w(this, "onEmergencyRedial no selectionConnection, dial CS emergency call");
2758                     mIsEmergencyCallPending = false;
2759                     mDomainSelectionMainExecutor.execute(
2760                             () -> recreateEmergencyConnection(c, phone,
2761                                     NetworkRegistrationInfo.DOMAIN_CS));
2762                     return;
2763                 }
2764 
2765                 mEmergencyCallDomainSelectionConnection =
2766                         (EmergencyCallDomainSelectionConnection) selectConnection;
2767 
2768                 mEmergencyConnection = c;
2769 
2770                 DomainSelectionService.SelectionAttributes attr =
2771                         EmergencyCallDomainSelectionConnection.getSelectionAttributes(
2772                                 phone.getPhoneId(),
2773                                 phone.getSubId(), false,
2774                                 c.getTelecomCallId(),
2775                                 c.getAddress().getSchemeSpecificPart(),
2776                                 0, null, mEmergencyStateTracker.getEmergencyRegResult());
2777 
2778                 CompletableFuture<Integer> domainFuture =
2779                         mEmergencyCallDomainSelectionConnection.createEmergencyConnection(
2780                                 attr, mEmergencyDomainSelectionConnectionCallback);
2781                 domainFuture.thenAcceptAsync((domain) -> {
2782                     Log.d(this, "onEmergencyRedial-createEmergencyConnection-complete domain="
2783                             + domain);
2784                     recreateEmergencyConnection(c, phone, domain);
2785                     mIsEmergencyCallPending = false;
2786                 }, mDomainSelectionMainExecutor);
2787             } else {
2788                 c.setTelephonyConnectionDisconnected(
2789                         mDisconnectCauseFactory.toTelecomDisconnectCause(result, "unknown error"));
2790                 c.close();
2791                 mIsEmergencyCallPending = false;
2792             }
2793         });
2794     }
2795 
recreateEmergencyConnection(final TelephonyConnection connection, final Phone phone, final @NetworkRegistrationInfo.Domain int result)2796     private void recreateEmergencyConnection(final TelephonyConnection connection,
2797             final Phone phone, final @NetworkRegistrationInfo.Domain int result) {
2798         Log.d(this, "recreateEmergencyConnection result=" + result);
2799         if (mEmergencyCallId == null) {
2800             Log.i(this, "recreateEmergencyConnection dialing canceled");
2801             return;
2802         }
2803         if (!getAllConnections().isEmpty()) {
2804             if (!shouldHoldForEmergencyCall(phone)) {
2805                 // If we do not support holding ongoing calls for an outgoing
2806                 // emergency call, disconnect the ongoing calls.
2807                 for (Connection c : getAllConnections()) {
2808                     if (!c.equals(connection)
2809                             && c.getState() != Connection.STATE_DISCONNECTED
2810                             && c instanceof TelephonyConnection) {
2811                         ((TelephonyConnection) c).hangup(
2812                                 android.telephony.DisconnectCause
2813                                         .OUTGOING_EMERGENCY_CALL_PLACED);
2814                     }
2815                 }
2816                 for (Conference c : getAllConferences()) {
2817                     if (c.getState() != Connection.STATE_DISCONNECTED) {
2818                         c.onDisconnect();
2819                     }
2820                 }
2821             } else if (!isVideoCallHoldAllowed(phone)) {
2822                 // If we do not support holding ongoing video call for an outgoing
2823                 // emergency call, disconnect the ongoing video call.
2824                 for (Connection c : getAllConnections()) {
2825                     if (!c.equals(connection)
2826                             && c.getState() == Connection.STATE_ACTIVE
2827                             && VideoProfile.isVideo(c.getVideoState())
2828                             && c instanceof TelephonyConnection) {
2829                         ((TelephonyConnection) c).hangup(
2830                                 android.telephony.DisconnectCause
2831                                         .OUTGOING_EMERGENCY_CALL_PLACED);
2832                         break;
2833                     }
2834                 }
2835             }
2836         }
2837         onEmergencyRedialOnDomain(connection, phone, result);
2838     }
2839 
onNormalCallRedial(TelephonyConnection connection, Phone phone, @NetworkRegistrationInfo.Domain int domain, int videocallState)2840     private void onNormalCallRedial(TelephonyConnection connection, Phone phone,
2841             @NetworkRegistrationInfo.Domain int domain, int videocallState) {
2842 
2843         Log.v(LOG_TAG, "Redialing the call in domain:"
2844                 + DomainSelectionService.getDomainName(domain));
2845 
2846         String number = connection.getAddress().getSchemeSpecificPart();
2847 
2848         Bundle extras = new Bundle();
2849         extras.putInt(PhoneConstants.EXTRA_DIAL_DOMAIN, domain);
2850         // Add flag to bundle for comparing legacy and new domain selection results. When
2851         // EXTRA_COMPARE_DOMAIN flag is true, legacy domain selection result is used for
2852         // placing the call and if both the results are not same then bug report is generated.
2853         DeviceConfig.Properties properties = //read all telephony properties
2854                 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_TELEPHONY);
2855         boolean compareDomainSelection =
2856                 properties.getBoolean(KEY_DOMAIN_COMPARE_FEATURE_ENABLED_FLAG, false);
2857         extras.putBoolean(PhoneConstants.EXTRA_COMPARE_DOMAIN, compareDomainSelection);
2858 
2859         com.android.internal.telephony.Connection originalConnection =
2860                 connection.getOriginalConnection();
2861         if (originalConnection instanceof ImsPhoneConnection) {
2862             if (((ImsPhoneConnection) originalConnection).isRttEnabledForCall()) {
2863                 extras.putBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT, true);
2864             }
2865         }
2866 
2867         try {
2868             if (phone != null) {
2869                 Log.d(LOG_TAG, "Redialing Call.");
2870                 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
2871                                 .setVideoState(videocallState)
2872                                 .setIntentExtras(extras)
2873                                 .setRttTextStream(connection.getRttTextStream())
2874                                 .setIsEmergency(false)
2875                                 .build(),
2876                         connection::registerForCallEvents);
2877             }
2878         } catch (Exception e) {
2879             Log.e(LOG_TAG, e, "Call redial exception: " + e);
2880         }
2881         if (originalConnection == null) {
2882             Log.e(LOG_TAG, new Exception("Phone is null"),
2883                     "Call redial failure due to phone.dial returned null");
2884             connection.setDisconnected(mDisconnectCauseFactory.toTelecomDisconnectCause(
2885                     android.telephony.DisconnectCause.OUTGOING_FAILURE, "connection is null"));
2886             connection.close();
2887         } else {
2888             connection.setOriginalConnection(originalConnection);
2889         }
2890     }
2891 
onLocalHangup(TelephonyConnection c)2892     protected void onLocalHangup(TelephonyConnection c) {
2893         if (TextUtils.equals(mEmergencyCallId, c.getTelecomCallId())) {
2894             Log.i(this, "onLocalHangup " + mEmergencyCallId);
2895             c.removeTelephonyConnectionListener(mEmergencyConnectionListener);
2896             releaseEmergencyCallDomainSelection(true);
2897             mEmergencyStateTracker.endCall(c.getTelecomCallId());
2898             mEmergencyCallId = null;
2899         }
2900     }
2901 
2902     @VisibleForTesting
getEmergencyConnectionListener()2903     public TelephonyConnection.TelephonyConnectionListener getEmergencyConnectionListener() {
2904         return mEmergencyConnectionListener;
2905     }
2906 
2907     @VisibleForTesting
2908     public TelephonyConnection.TelephonyConnectionListener
getEmergencyConnectionSatelliteListener()2909             getEmergencyConnectionSatelliteListener() {
2910         return mEmergencyConnectionSatelliteListener;
2911     }
2912 
isVideoCallHoldAllowed(Phone phone)2913     private boolean isVideoCallHoldAllowed(Phone phone) {
2914          CarrierConfigManager cfgManager = (CarrierConfigManager)
2915                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
2916         if (cfgManager == null) {
2917             // For some reason CarrierConfigManager is unavailable, return default
2918             Log.w(this, "isVideoCallHoldAllowed: couldn't get CarrierConfigManager");
2919             return true;
2920         }
2921         return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean(
2922                 CarrierConfigManager.KEY_ALLOW_HOLD_VIDEO_CALL_BOOL, true);
2923     }
2924 
shouldHoldForEmergencyCall(Phone phone)2925     private boolean shouldHoldForEmergencyCall(Phone phone) {
2926         CarrierConfigManager cfgManager = (CarrierConfigManager)
2927                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
2928         if (cfgManager == null) {
2929             // For some reason CarrierConfigManager is unavailable, return default
2930             Log.w(this, "shouldHoldForEmergencyCall: couldn't get CarrierConfigManager");
2931             return true;
2932         }
2933         return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean(
2934                 CarrierConfigManager.KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL, true);
2935     }
2936 
handleCallStateException(CallStateException e, TelephonyConnection connection, Phone phone)2937     private void handleCallStateException(CallStateException e, TelephonyConnection connection,
2938             Phone phone) {
2939         int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
2940         switch (e.getError()) {
2941             case CallStateException.ERROR_OUT_OF_SERVICE:
2942                 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE;
2943                 break;
2944             case CallStateException.ERROR_POWER_OFF:
2945                  cause = android.telephony.DisconnectCause.POWER_OFF;
2946                  break;
2947             case CallStateException.ERROR_ALREADY_DIALING:
2948                  cause = android.telephony.DisconnectCause.ALREADY_DIALING;
2949                  break;
2950             case CallStateException.ERROR_CALL_RINGING:
2951                  cause = android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING;
2952                  break;
2953             case CallStateException.ERROR_CALLING_DISABLED:
2954                  cause = android.telephony.DisconnectCause.CALLING_DISABLED;
2955                  break;
2956             case CallStateException.ERROR_TOO_MANY_CALLS:
2957                  cause = android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS;
2958                  break;
2959             case CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS:
2960                  cause = android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS;
2961                  break;
2962             case CallStateException.ERROR_FDN_BLOCKED:
2963                  cause = android.telephony.DisconnectCause.FDN_BLOCKED;
2964                  break;
2965         }
2966         connection.setTelephonyConnectionDisconnected(
2967                 DisconnectCauseUtil.toTelecomDisconnectCause(cause, e.getMessage(),
2968                         phone.getPhoneId()));
2969         connection.close();
2970     }
2971 
createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId)2972     private TelephonyConnection createConnectionFor(
2973             Phone phone,
2974             com.android.internal.telephony.Connection originalConnection,
2975             boolean isOutgoing,
2976             PhoneAccountHandle phoneAccountHandle,
2977             String telecomCallId) {
2978             return createConnectionFor(phone, originalConnection, isOutgoing, phoneAccountHandle,
2979                     telecomCallId, false);
2980     }
2981 
createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, boolean isAdhocConference)2982     private TelephonyConnection createConnectionFor(
2983             Phone phone,
2984             com.android.internal.telephony.Connection originalConnection,
2985             boolean isOutgoing,
2986             PhoneAccountHandle phoneAccountHandle,
2987             String telecomCallId,
2988             boolean isAdhocConference) {
2989         TelephonyConnection returnConnection = null;
2990         int phoneType = phone.getPhoneType();
2991         int callDirection = isOutgoing ? android.telecom.Call.Details.DIRECTION_OUTGOING
2992                 : android.telecom.Call.Details.DIRECTION_INCOMING;
2993         if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
2994             returnConnection = new GsmConnection(originalConnection, telecomCallId, callDirection);
2995         } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
2996             boolean allowsMute = allowsMute(phone);
2997             returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer,
2998                     allowsMute, callDirection, telecomCallId);
2999         }
3000         if (returnConnection != null) {
3001             if (!isAdhocConference) {
3002                 // Listen to Telephony specific callbacks from the connection
3003                 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener);
3004             }
3005             returnConnection.setVideoPauseSupported(
3006                     TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
3007                             phoneAccountHandle));
3008             returnConnection.setManageImsConferenceCallSupported(
3009                     TelecomAccountRegistry.getInstance(this).isManageImsConferenceCallSupported(
3010                             phoneAccountHandle));
3011             returnConnection.setShowPreciseFailedCause(
3012                     TelecomAccountRegistry.getInstance(this).isShowPreciseFailedCause(
3013                             phoneAccountHandle));
3014             returnConnection.setTelephonyConnectionService(this);
3015         }
3016         return returnConnection;
3017     }
3018 
isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)3019     private boolean isOriginalConnectionKnown(
3020             com.android.internal.telephony.Connection originalConnection) {
3021         return (getConnectionForOriginalConnection(originalConnection) != null);
3022     }
3023 
getConnectionForOriginalConnection( com.android.internal.telephony.Connection originalConnection)3024     private TelephonyConnection getConnectionForOriginalConnection(
3025             com.android.internal.telephony.Connection originalConnection) {
3026         for (Connection connection : getAllConnections()) {
3027             if (connection instanceof TelephonyConnection) {
3028                 TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
3029                 if (telephonyConnection.getOriginalConnection() == originalConnection) {
3030                     return telephonyConnection;
3031                 }
3032             }
3033         }
3034         return null;
3035     }
3036 
3037     /**
3038      * Determines which {@link Phone} will be used to place the call.
3039      * @param accountHandle The {@link PhoneAccountHandle} which was sent from Telecom to place the
3040      *      call on.
3041      * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise.
3042      * @param emergencyNumberAddress When {@code isEmergency} is {@code true}, will be the phone
3043      *      of the emergency call.  Otherwise, this can be {@code null}  .
3044      * @return
3045      */
getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, @Nullable String emergencyNumberAddress)3046     private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency,
3047                                      @Nullable String emergencyNumberAddress) {
3048         Phone chosenPhone = null;
3049         int subId = mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(accountHandle);
3050         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
3051             int phoneId = mSubscriptionManagerProxy.getPhoneId(subId);
3052             chosenPhone = mPhoneFactoryProxy.getPhone(phoneId);
3053             Log.i(this, "getPhoneForAccount: handle=%s, subId=%s", accountHandle,
3054                     (chosenPhone == null ? "null" : chosenPhone.getSubId()));
3055         }
3056 
3057         // If this isn't an emergency call, just use the chosen phone (or null if none was found).
3058         if (!isEmergency) {
3059             return chosenPhone;
3060         }
3061 
3062         // Check if this call should be treated as a normal routed emergency call; we'll return null
3063         // if this is not a normal routed emergency call.
3064         Phone normalRoutingPhone = getPhoneForNormalRoutedEmergencyCall(emergencyNumberAddress);
3065         if (normalRoutingPhone != null) {
3066             Log.i(this, "getPhoneForAccount: normal routed emergency number,"
3067                             + "using phoneId=%d/subId=%d", normalRoutingPhone.getPhoneId(),
3068                     normalRoutingPhone.getSubId());
3069             return normalRoutingPhone;
3070         }
3071 
3072         // Default emergency call phone selection logic:
3073         // This is an emergency call and the phone we originally planned to make this call
3074         // with is not in service or was invalid, try to find one that is in service, using the
3075         // default as a last chance backup.
3076         if (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone)) {
3077             Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
3078                     + "or invalid for emergency call.", accountHandle);
3079             chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress);
3080             Log.i(this, "getPhoneForAccount: emergency call - using subId: %s",
3081                     (chosenPhone == null ? "null" : chosenPhone.getSubId()));
3082         }
3083         return chosenPhone;
3084     }
3085 
3086     /**
3087      * If needed, block until the default data is switched for outgoing emergency call, or
3088      * timeout expires.
3089      * @param phone The Phone to switch the DDS on.
3090      * @param completeConsumer The consumer to call once the default data subscription has been
3091      *                         switched, provides {@code true} result if the switch happened
3092      *                         successfully or {@code false} if the operation timed out/failed.
3093      */
delayDialForDdsSwitch(Phone phone, Consumer<Boolean> completeConsumer)3094     private void delayDialForDdsSwitch(Phone phone, Consumer<Boolean> completeConsumer) {
3095         if (phone == null) {
3096             // Do not block indefinitely.
3097             completeConsumer.accept(false);
3098             return;
3099         }
3100         try {
3101             // Waiting for PhoneSwitcher to complete the operation.
3102             CompletableFuture<Boolean> future = possiblyOverrideDefaultDataForEmergencyCall(phone);
3103             // In the case that there is an issue or bug in PhoneSwitcher logic, do not wait
3104             // indefinitely for the future to complete. Instead, set a timeout that will complete
3105             // the future as to not block the outgoing call indefinitely.
3106             CompletableFuture<Boolean> timeout = new CompletableFuture<>();
3107             phone.getContext().getMainThreadHandler().postDelayed(
3108                     () -> timeout.complete(false), DEFAULT_DATA_SWITCH_TIMEOUT_MS);
3109             // Also ensure that the Consumer is completed on the main thread.
3110             future.acceptEitherAsync(timeout, completeConsumer,
3111                     phone.getContext().getMainExecutor());
3112         } catch (Exception e) {
3113             Log.w(this, "delayDialForDdsSwitch - exception= "
3114                     + e.getMessage());
3115 
3116         }
3117     }
3118 
3119     /**
3120      * If needed, block until Default Data subscription is switched for outgoing emergency call.
3121      *
3122      * In some cases, we need to try to switch the Default Data subscription before placing the
3123      * emergency call on DSDS devices. This includes the following situation:
3124      * - The modem does not support processing GNSS SUPL requests on the non-default data
3125      * subscription. For some carriers that do not provide a control plane fallback mechanism, the
3126      * SUPL request will be dropped and we will not be able to get the user's location for the
3127      * emergency call. In this case, we need to swap default data temporarily.
3128      * @param phone Evaluates whether or not the default data should be moved to the phone
3129      *              specified. Should not be null.
3130      */
possiblyOverrideDefaultDataForEmergencyCall( @onNull Phone phone)3131     private CompletableFuture<Boolean> possiblyOverrideDefaultDataForEmergencyCall(
3132             @NonNull Phone phone) {
3133         int phoneCount = mTelephonyManagerProxy.getPhoneCount();
3134         // Do not override DDS if this is a single SIM device.
3135         if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) {
3136             return CompletableFuture.completedFuture(Boolean.TRUE);
3137         }
3138 
3139         // Do not switch Default data if this device supports emergency SUPL on non-DDS.
3140         final boolean gnssSuplRequiresDefaultData =
3141                 mDeviceState.isSuplDdsSwitchRequiredForEmergencyCall(this);
3142         if (!gnssSuplRequiresDefaultData) {
3143             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not "
3144                     + "require DDS switch.");
3145             return CompletableFuture.completedFuture(Boolean.TRUE);
3146         }
3147 
3148         CarrierConfigManager cfgManager = (CarrierConfigManager)
3149                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
3150         if (cfgManager == null) {
3151             // For some reason CarrierConfigManager is unavailable. Do not block emergency call.
3152             Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: couldn't get"
3153                     + "CarrierConfigManager");
3154             return CompletableFuture.completedFuture(Boolean.TRUE);
3155         }
3156 
3157         // Only override default data if we are IN_SERVICE already.
3158         if (!isAvailableForEmergencyCalls(phone)) {
3159             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS");
3160             return CompletableFuture.completedFuture(Boolean.TRUE);
3161         }
3162 
3163         // Only override default data if we are not roaming, we do not want to switch onto a network
3164         // that only supports data plane only (if we do not know).
3165         boolean isRoaming = phone.getServiceState().getVoiceRoaming();
3166         // In some roaming conditions, we know the roaming network doesn't support control plane
3167         // fallback even though the home operator does. For these operators we will need to do a DDS
3168         // switch anyway to make sure the SUPL request doesn't fail.
3169         boolean roamingNetworkSupportsControlPlaneFallback = true;
3170         String[] dataPlaneRoamPlmns = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray(
3171                 CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY);
3172         if (dataPlaneRoamPlmns != null && Arrays.asList(dataPlaneRoamPlmns).contains(
3173                 phone.getServiceState().getOperatorNumeric())) {
3174             roamingNetworkSupportsControlPlaneFallback = false;
3175         }
3176         if (isRoaming && roamingNetworkSupportsControlPlaneFallback) {
3177             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: roaming network is assumed "
3178                     + "to support CP fallback, not switching DDS.");
3179             return CompletableFuture.completedFuture(Boolean.TRUE);
3180         }
3181         // Do not try to swap default data if we support CS fallback or it is assumed that the
3182         // roaming network supports control plane fallback, we do not want to introduce
3183         // a lag in emergency call setup time if possible.
3184         final boolean supportsCpFallback = cfgManager.getConfigForSubId(phone.getSubId())
3185                 .getInt(CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
3186                         CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY)
3187                 != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY;
3188         if (supportsCpFallback && roamingNetworkSupportsControlPlaneFallback) {
3189             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier "
3190                     + "supports CP fallback.");
3191             return CompletableFuture.completedFuture(Boolean.TRUE);
3192         }
3193 
3194         // Get extension time, may be 0 for some carriers that support ECBM as well. Use
3195         // CarrierConfig default if format fails.
3196         int extensionTime = 0;
3197         try {
3198             extensionTime = Integer.parseInt(cfgManager.getConfigForSubId(phone.getSubId())
3199                     .getString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0"));
3200         } catch (NumberFormatException e) {
3201             // Just use default.
3202         }
3203         CompletableFuture<Boolean> modemResultFuture = new CompletableFuture<>();
3204         try {
3205             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for "
3206                     + extensionTime + "seconds");
3207             mPhoneSwitcherProxy.getPhoneSwitcher().overrideDefaultDataForEmergency(
3208                     phone.getPhoneId(), extensionTime, modemResultFuture);
3209             // Catch all exceptions, we want to continue with emergency call if possible.
3210         } catch (Exception e) {
3211             Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: exception = "
3212                     + e.getMessage());
3213             modemResultFuture = CompletableFuture.completedFuture(Boolean.FALSE);
3214         }
3215         return modemResultFuture;
3216     }
3217 
addTelephonyConnectionListener(Conferenceable c, TelephonyConnection.TelephonyConnectionListener listener)3218     private void addTelephonyConnectionListener(Conferenceable c,
3219             TelephonyConnection.TelephonyConnectionListener listener) {
3220         if (c instanceof TelephonyConnection) {
3221             TelephonyConnection telephonyConnection = (TelephonyConnection) c;
3222             telephonyConnection.addTelephonyConnectionListener(listener);
3223         } else if (c instanceof ImsConference) {
3224             ImsConference imsConference = (ImsConference) c;
3225             TelephonyConnection conferenceHost =
3226                     (TelephonyConnection) imsConference.getConferenceHost();
3227             conferenceHost.addTelephonyConnectionListener(listener);
3228         } else {
3229             throw new IllegalArgumentException(
3230                     "addTelephonyConnectionListener(): Unexpected conferenceable! " + c);
3231         }
3232     }
3233 
listenForHoldStateChanged( @onNull Conferenceable conferenceable)3234     private CompletableFuture<Boolean> listenForHoldStateChanged(
3235             @NonNull Conferenceable conferenceable) {
3236         CompletableFuture<Boolean> future = new CompletableFuture<>();
3237         final StateHoldingListener stateHoldingListener = new StateHoldingListener(future);
3238         addTelephonyConnectionListener(conferenceable, stateHoldingListener);
3239         return future;
3240     }
3241 
3242     // Returns a future that waits for the STATE_HOLDING confirmation on the input
3243     // {@link Conferenceable}, or times out.
delayDialForOtherSubHold(Phone phone, Conferenceable c, Consumer<Boolean> completeConsumer)3244     private CompletableFuture<Void> delayDialForOtherSubHold(Phone phone, Conferenceable c,
3245             Consumer<Boolean> completeConsumer) {
3246         if (c == null || phone == null) {
3247             // Unexpected inputs
3248             completeConsumer.accept(false);
3249             return CompletableFuture.completedFuture(null);
3250         }
3251 
3252         try {
3253             CompletableFuture<Boolean> stateHoldingFuture = listenForHoldStateChanged(c);
3254             // a timeout that will complete the future to not block the outgoing call indefinitely.
3255             CompletableFuture<Boolean> timeout = new CompletableFuture<>();
3256             phone.getContext().getMainThreadHandler().postDelayed(
3257                     () -> timeout.complete(false), DEFAULT_DSDA_OUTGOING_CALL_HOLD_TIMEOUT_MS);
3258             // Ensure that the Consumer is completed on the main thread.
3259             return stateHoldingFuture.acceptEitherAsync(timeout, completeConsumer,
3260                     phone.getContext().getMainExecutor());
3261         } catch (Exception e) {
3262             Log.w(this, "delayDialForOtherSubHold - exception= "
3263                     + e.getMessage());
3264             completeConsumer.accept(false);
3265             return CompletableFuture.completedFuture(null);
3266         }
3267     }
3268 
3269     /**
3270      * Get the Phone to use for an emergency call of the given emergency number address:
3271      *  a) If there are multiple Phones with the Subscriptions that support the emergency number
3272      *     address, and one of them is the default voice Phone, consider the default voice phone
3273      *     if 1.4 HAL is supported, or if it is available for emergency call.
3274      *  b) If there are multiple Phones with the Subscriptions that support the emergency number
3275      *     address, and none of them is the default voice Phone, use one of these Phones if 1.4 HAL
3276      *     is supported, or if it is available for emergency call.
3277      *  c) If there is no Phone that supports the emergency call for the address, use the defined
3278      *     Priority list to select the Phone via {@link #getFirstPhoneForEmergencyCall}.
3279      */
getPhoneForEmergencyCall(String emergencyNumberAddress)3280     public Phone getPhoneForEmergencyCall(String emergencyNumberAddress) {
3281         // Find the list of available Phones for the given emergency number address
3282         List<Phone> potentialEmergencyPhones = new ArrayList<>();
3283         int defaultVoicePhoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId();
3284         for (Phone phone : mPhoneFactoryProxy.getPhones()) {
3285             if (phone.getEmergencyNumberTracker() != null) {
3286                 if (phone.getEmergencyNumberTracker().isEmergencyNumber(
3287                         emergencyNumberAddress)) {
3288                     if (isAvailableForEmergencyCalls(phone)) {
3289                         // a)
3290                         if (phone.getPhoneId() == defaultVoicePhoneId) {
3291                             Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports"
3292                                     + " emergency number: " + phone.getPhoneId());
3293                             return phone;
3294                         }
3295                         potentialEmergencyPhones.add(phone);
3296                     }
3297                 }
3298             }
3299         }
3300         // b)
3301         if (potentialEmergencyPhones.size() > 0) {
3302             Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports emergency number:"
3303                     + potentialEmergencyPhones.get(0).getPhoneId());
3304             return getFirstPhoneForEmergencyCall(potentialEmergencyPhones);
3305         }
3306         // c)
3307         return getFirstPhoneForEmergencyCall();
3308     }
3309 
3310     @VisibleForTesting
getFirstPhoneForEmergencyCall()3311     public Phone getFirstPhoneForEmergencyCall() {
3312         return getFirstPhoneForEmergencyCall(null);
3313     }
3314 
3315     /**
3316      * Retrieves the most sensible Phone to use for an emergency call using the following Priority
3317      *  list (for multi-SIM devices):
3318      *  1) The Phone that is in emergency SMS mode
3319      *  2) The phone based on User's SIM preference of Voice calling or Data in order
3320      *  3) The First Phone that is currently IN_SERVICE or is available for emergency calling
3321      *  4) Prioritize phones that have the dialed emergency number as part of their emergency
3322      *     number list
3323      *  5) If there is a PUK locked SIM, compare the SIMs that are not PUK locked. If all the SIMs
3324      *     are locked, skip to condition 6).
3325      *  6) The Phone with more Capabilities.
3326      *  7) The First Phone that has a SIM card in it (Starting from Slot 0...N)
3327      *  8) The Default Phone (Currently set as Slot 0)
3328      */
3329     @VisibleForTesting
3330     @NonNull
getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber)3331     public Phone getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber) {
3332         int phoneCount = mTelephonyManagerProxy.getPhoneCount();
3333         for (int i = 0; i < phoneCount; i++) {
3334             Phone phone = mPhoneFactoryProxy.getPhone(i);
3335             // 1)
3336             if (phone != null && phone.isInEmergencySmsMode()) {
3337                 if (isAvailableForEmergencyCalls(phone)) {
3338                     if (phonesWithEmergencyNumber == null
3339                             || phonesWithEmergencyNumber.contains(phone)) {
3340                         return phone;
3341                     }
3342                 }
3343             }
3344         }
3345 
3346         // 2)
3347         int phoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId();
3348         if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
3349             phoneId = mSubscriptionManagerProxy.getDefaultDataPhoneId();
3350         }
3351         if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
3352             Phone selectedPhone = mPhoneFactoryProxy.getPhone(phoneId);
3353             if (selectedPhone != null && isAvailableForEmergencyCalls(selectedPhone)) {
3354                 if (phonesWithEmergencyNumber == null
3355                         || phonesWithEmergencyNumber.contains(selectedPhone)) {
3356                     return selectedPhone;
3357                 }
3358             }
3359         }
3360 
3361         Phone firstPhoneWithSim = null;
3362         List<SlotStatus> phoneSlotStatus = new ArrayList<>(phoneCount);
3363         for (int i = 0; i < phoneCount; i++) {
3364             Phone phone = mPhoneFactoryProxy.getPhone(i);
3365             if (phone == null) {
3366                 continue;
3367             }
3368             // 3)
3369             if (isAvailableForEmergencyCalls(phone)) {
3370                 if (phonesWithEmergencyNumber == null
3371                         || phonesWithEmergencyNumber.contains(phone)) {
3372                     // the slot has the radio on & state is in service.
3373                     Log.i(this,
3374                             "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i);
3375                     return phone;
3376                 }
3377             }
3378             // 6)
3379             // Store the RAF Capabilities for sorting later.
3380             int radioAccessFamily = phone.getRadioAccessFamily();
3381             SlotStatus status = new SlotStatus(i, radioAccessFamily, phone.getSubId());
3382             phoneSlotStatus.add(status);
3383             Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" +
3384                 Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i + " subId:"
3385                 + phone.getSubId());
3386             // 5)
3387             // Report Slot's PIN/PUK lock status for sorting later.
3388             int simState = mSubscriptionManagerProxy.getSimStateForSlotIdx(i);
3389             // Record SimState.
3390             status.simState = simState;
3391             if (simState == TelephonyManager.SIM_STATE_PIN_REQUIRED ||
3392                     simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) {
3393                 status.isLocked = true;
3394             }
3395 
3396             // 4) Store if the Phone has the corresponding emergency number
3397             if (phonesWithEmergencyNumber != null) {
3398                 for (Phone phoneWithEmergencyNumber : phonesWithEmergencyNumber) {
3399                     if (phoneWithEmergencyNumber != null
3400                             && phoneWithEmergencyNumber.getPhoneId() == i) {
3401                         status.hasDialedEmergencyNumber = true;
3402                     }
3403                 }
3404             }
3405             // 7)
3406             if (firstPhoneWithSim == null &&
3407                 (phone.getSubId() != SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
3408                 // The slot has a SIM card inserted (and an active subscription), but is not in
3409                 // service, so keep track of this Phone.
3410                 // Do not return because we want to make sure that none of the other Phones
3411                 // are in service (because that is always faster).
3412                 firstPhoneWithSim = phone;
3413                 Log.i(this, "getFirstPhoneForEmergencyCall, SIM with active sub, Phone Id:" +
3414                     firstPhoneWithSim.getPhoneId());
3415             }
3416         }
3417         // 8)
3418         if (firstPhoneWithSim == null && phoneSlotStatus.isEmpty()) {
3419             if (phonesWithEmergencyNumber != null) {
3420                 for (Phone phoneWithEmergencyNumber : phonesWithEmergencyNumber) {
3421                     if (phoneWithEmergencyNumber != null) {
3422                         return phoneWithEmergencyNumber;
3423                     }
3424                 }
3425             }
3426 
3427             // No Phones available, get the default
3428             Log.i(this, "getFirstPhoneForEmergencyCall, return default phone");
3429             return  mPhoneFactoryProxy.getDefaultPhone();
3430         } else {
3431             // 6)
3432             final int defaultPhoneId = mPhoneFactoryProxy.getDefaultPhone().getPhoneId();
3433             final Phone firstOccupiedSlot = firstPhoneWithSim;
3434             if (!phoneSlotStatus.isEmpty()) {
3435                 Log.i(this, "getFirstPhoneForEmergencyCall, list size: " + phoneSlotStatus.size()
3436                     + " defaultPhoneId: " + defaultPhoneId + " firstOccupiedSlot: "
3437                     + firstOccupiedSlot);
3438                 // Only sort if there are enough elements to do so.
3439                 if (phoneSlotStatus.size() > 1) {
3440                     Collections.sort(phoneSlotStatus, (o1, o2) -> {
3441                         // Sort by non-absent SIM (SIM without active sub is considered absent).
3442                         if (o1.isSubActiveAndSimPresent() && !o2.isSubActiveAndSimPresent()) {
3443                             return 1;
3444                         }
3445                         if (o2.isSubActiveAndSimPresent() && !o1.isSubActiveAndSimPresent()) {
3446                             return -1;
3447                         }
3448                         // First start by seeing if either of the phone slots are locked. If they
3449                         // are, then sort by non-locked SIM first. If they are both locked, sort
3450                         // by capability instead.
3451                         if (o1.isLocked && !o2.isLocked) {
3452                             return -1;
3453                         }
3454                         if (o2.isLocked && !o1.isLocked) {
3455                             return 1;
3456                         }
3457                         // Prefer slots where the number is considered emergency.
3458                         if (!o1.hasDialedEmergencyNumber && o2.hasDialedEmergencyNumber) {
3459                             return -1;
3460                         }
3461                         if (o1.hasDialedEmergencyNumber && !o2.hasDialedEmergencyNumber) {
3462                             return 1;
3463                         }
3464                         // sort by number of RadioAccessFamily Capabilities.
3465                         int compare = RadioAccessFamily.compare(o1.capabilities, o2.capabilities);
3466                         if (compare == 0) {
3467                             if (firstOccupiedSlot != null) {
3468                                 // If the RAF capability is the same, choose based on whether or
3469                                 // not any of the slots are occupied with a SIM card (if both
3470                                 // are, always choose the first).
3471                                 if (o1.slotId == firstOccupiedSlot.getPhoneId()) {
3472                                     return 1;
3473                                 } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) {
3474                                     return -1;
3475                                 }
3476                             } else {
3477                                 // No slots have SIMs detected in them, so weight the default
3478                                 // Phone Id greater than the others.
3479                                 if (o1.slotId == defaultPhoneId) {
3480                                     return 1;
3481                                 } else if (o2.slotId == defaultPhoneId) {
3482                                     return -1;
3483                                 }
3484                             }
3485                         }
3486                         return compare;
3487                     });
3488                 }
3489                 int mostCapablePhoneId = phoneSlotStatus.get(phoneSlotStatus.size() - 1).slotId;
3490                 Log.i(this, "getFirstPhoneForEmergencyCall, Using Phone Id: " + mostCapablePhoneId +
3491                         "with highest capability");
3492                 return mPhoneFactoryProxy.getPhone(mostCapablePhoneId);
3493             } else {
3494                 // 7)
3495                 return firstPhoneWithSim;
3496             }
3497         }
3498     }
3499 
isAvailableForEmergencyCalls(Phone phone)3500     private boolean isAvailableForEmergencyCalls(Phone phone) {
3501         return isAvailableForEmergencyCalls(phone,
3502                 EmergencyNumber.EMERGENCY_CALL_ROUTING_EMERGENCY);
3503     }
3504 
3505     /**
3506      * Determines if the phone is available for an emergency call given the specified routing.
3507      *
3508      * @param phone the phone to check the service availability for
3509      * @param routing the emergency call routing for this call
3510      */
3511     @VisibleForTesting
isAvailableForEmergencyCalls(Phone phone, @EmergencyNumber.EmergencyCallRouting int routing)3512     public boolean isAvailableForEmergencyCalls(Phone phone,
3513             @EmergencyNumber.EmergencyCallRouting int routing) {
3514         if (phone.getImsRegistrationTech() == ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM) {
3515             // When a Phone is registered to Cross-SIM calling, there must always be a Phone on the
3516             // other sub which is registered to cellular, so that must be selected.
3517             Log.d(this, "isAvailableForEmergencyCalls: skipping over phone "
3518                     + phone + " as it is registered to CROSS_SIM");
3519             return false;
3520         }
3521 
3522         // In service phones are always appropriate for emergency calls.
3523         if (ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState()) {
3524             return true;
3525         }
3526 
3527         // If the call routing is unknown or is using emergency routing, an emergency only attach is
3528         // sufficient for placing the emergency call.  Normal routed emergency calls cannot be
3529         // placed on an emergency-only phone.
3530         return (routing != EmergencyNumber.EMERGENCY_CALL_ROUTING_NORMAL
3531                 && phone.getServiceState().isEmergencyOnly());
3532     }
3533 
3534     /**
3535      * Determines if the connection should allow mute.
3536      *
3537      * @param phone The current phone.
3538      * @return {@code True} if the connection should allow mute.
3539      */
allowsMute(Phone phone)3540     private boolean allowsMute(Phone phone) {
3541         // For CDMA phones, check if we are in Emergency Callback Mode (ECM).  Mute is disallowed
3542         // in ECM mode.
3543         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
3544             if (phone.isInEcm()) {
3545                 return false;
3546             }
3547         }
3548 
3549         return true;
3550     }
3551 
getTelephonyConnectionListener()3552     TelephonyConnection.TelephonyConnectionListener getTelephonyConnectionListener() {
3553         return mTelephonyConnectionListener;
3554     }
3555 
3556     /**
3557      * When a {@link TelephonyConnection} has its underlying original connection configured,
3558      * we need to add it to the correct conference controller.
3559      *
3560      * @param connection The connection to be added to the controller
3561      */
addConnectionToConferenceController(TelephonyConnection connection)3562     public void addConnectionToConferenceController(TelephonyConnection connection) {
3563         // TODO: Need to revisit what happens when the original connection for the
3564         // TelephonyConnection changes.  If going from CDMA --> GSM (for example), the
3565         // instance of TelephonyConnection will still be a CdmaConnection, not a GsmConnection.
3566         // The CDMA conference controller makes the assumption that it will only have CDMA
3567         // connections in it, while the other conference controllers aren't as restrictive.  Really,
3568         // when we go between CDMA and GSM we should replace the TelephonyConnection.
3569         if (connection.isImsConnection()) {
3570             Log.d(this, "Adding IMS connection to conference controller: " + connection);
3571             mImsConferenceController.add(connection);
3572             mTelephonyConferenceController.remove(connection);
3573             if (connection instanceof CdmaConnection) {
3574                 mCdmaConferenceController.remove((CdmaConnection) connection);
3575             }
3576         } else {
3577             int phoneType = connection.getCall().getPhone().getPhoneType();
3578             if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
3579                 Log.d(this, "Adding GSM connection to conference controller: " + connection);
3580                 mTelephonyConferenceController.add(connection);
3581                 if (connection instanceof CdmaConnection) {
3582                     mCdmaConferenceController.remove((CdmaConnection) connection);
3583                 }
3584             } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA &&
3585                     connection instanceof CdmaConnection) {
3586                 Log.d(this, "Adding CDMA connection to conference controller: " + connection);
3587                 mCdmaConferenceController.add((CdmaConnection) connection);
3588                 mTelephonyConferenceController.remove(connection);
3589             }
3590             Log.d(this, "Removing connection from IMS conference controller: " + connection);
3591             mImsConferenceController.remove(connection);
3592         }
3593     }
3594 
3595     /**
3596      * Create a new CDMA connection. CDMA connections have additional limitations when creating
3597      * additional calls which are handled in this method.  Specifically, CDMA has a "FLASH" command
3598      * that can be used for three purposes: merging a call, swapping unmerged calls, and adding
3599      * a new outgoing call. The function of the flash command depends on the context of the current
3600      * set of calls. This method will prevent an outgoing call from being made if it is not within
3601      * the right circumstances to support adding a call.
3602      */
checkAdditionalOutgoingCallLimits(Phone phone)3603     private Connection checkAdditionalOutgoingCallLimits(Phone phone) {
3604         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
3605             // Check to see if any CDMA conference calls exist, and if they do, check them for
3606             // limitations.
3607             for (Conference conference : getAllConferences()) {
3608                 if (conference instanceof CdmaConference) {
3609                     CdmaConference cdmaConf = (CdmaConference) conference;
3610 
3611                     // If the CDMA conference has not been merged, add-call will not work, so fail
3612                     // this request to add a call.
3613                     if ((cdmaConf.getConnectionCapabilities()
3614                             & Connection.CAPABILITY_MERGE_CONFERENCE) != 0) {
3615                         return Connection.createFailedConnection(new DisconnectCause(
3616                                     DisconnectCause.RESTRICTED,
3617                                     null,
3618                                     getResources().getString(R.string.callFailed_cdma_call_limit),
3619                                     "merge-capable call exists, prevent flash command."));
3620                     }
3621                 }
3622             }
3623         }
3624 
3625         return null; // null means nothing went wrong, and call should continue.
3626     }
3627 
3628     /**
3629      * For outgoing dialed calls, potentially send a ConnectionEvent if the user is on WFC and is
3630      * dialing an international number.
3631      * @param telephonyConnection The connection.
3632      */
maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection)3633     private void maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection) {
3634         if (telephonyConnection == null || telephonyConnection.getPhone() == null ||
3635                 telephonyConnection.getPhone().getDefaultPhone() == null) {
3636             return;
3637         }
3638         Phone phone = telephonyConnection.getPhone().getDefaultPhone();
3639         if (phone instanceof GsmCdmaPhone) {
3640             GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone;
3641             if (telephonyConnection.isOutgoingCall() &&
3642                     gsmCdmaPhone.isNotificationOfWfcCallRequired(
3643                             telephonyConnection.getOriginalConnection().getOrigDialString())) {
3644                 // Send connection event to InCall UI to inform the user of the fact they
3645                 // are potentially placing an international call on WFC.
3646                 Log.i(this, "placeOutgoingConnection - sending international call on WFC " +
3647                         "confirmation event");
3648                 telephonyConnection.sendTelephonyConnectionEvent(
3649                         TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null);
3650             }
3651         }
3652     }
3653 
handleTtyModeChange(boolean isTtyEnabled)3654     private void handleTtyModeChange(boolean isTtyEnabled) {
3655         Log.i(this, "handleTtyModeChange; isTtyEnabled=%b", isTtyEnabled);
3656         mIsTtyEnabled = isTtyEnabled;
3657         for (Connection connection : getAllConnections()) {
3658             if (connection instanceof TelephonyConnection) {
3659                 TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
3660                 telephonyConnection.setTtyEnabled(isTtyEnabled);
3661             }
3662         }
3663     }
3664 
closeOrDestroyConnection(Connection connection, DisconnectCause cause)3665     private void closeOrDestroyConnection(Connection connection, DisconnectCause cause) {
3666         if (connection instanceof TelephonyConnection) {
3667             TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
3668             telephonyConnection.setTelephonyConnectionDisconnected(cause);
3669             // Close destroys the connection and notifies TelephonyConnection listeners.
3670             telephonyConnection.close();
3671         } else {
3672             connection.setDisconnected(cause);
3673             connection.destroy();
3674         }
3675     }
3676 
showDataDialog(Phone phone, String number)3677     private boolean showDataDialog(Phone phone, String number) {
3678         boolean ret = false;
3679         final Context context = getApplicationContext();
3680         String suppKey = MmiCodeUtil.getSuppServiceKey(number);
3681         if (suppKey != null) {
3682             boolean clirOverUtPrecautions = false;
3683             boolean cfOverUtPrecautions = false;
3684             boolean cbOverUtPrecautions = false;
3685             boolean cwOverUtPrecautions = false;
3686 
3687             CarrierConfigManager cfgManager = (CarrierConfigManager)
3688                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
3689             if (cfgManager != null) {
3690                 clirOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
3691                     .getBoolean(CarrierConfigManager.KEY_CALLER_ID_OVER_UT_WARNING_BOOL);
3692                 cfOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
3693                     .getBoolean(CarrierConfigManager.KEY_CALL_FORWARDING_OVER_UT_WARNING_BOOL);
3694                 cbOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
3695                     .getBoolean(CarrierConfigManager.KEY_CALL_BARRING_OVER_UT_WARNING_BOOL);
3696                 cwOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
3697                     .getBoolean(CarrierConfigManager.KEY_CALL_WAITING_OVER_UT_WARNING_BOOL);
3698             }
3699 
3700             boolean isSsOverUtPrecautions = SuppServicesUiUtil
3701                 .isSsOverUtPrecautions(context, phone);
3702             if (isSsOverUtPrecautions) {
3703                 boolean showDialog = false;
3704                 if (suppKey == MmiCodeUtil.BUTTON_CLIR_KEY && clirOverUtPrecautions) {
3705                     showDialog = true;
3706                 } else if (suppKey == MmiCodeUtil.CALL_FORWARDING_KEY && cfOverUtPrecautions) {
3707                     showDialog = true;
3708                 } else if (suppKey == MmiCodeUtil.CALL_BARRING_KEY && cbOverUtPrecautions) {
3709                     showDialog = true;
3710                 } else if (suppKey == MmiCodeUtil.BUTTON_CW_KEY && cwOverUtPrecautions) {
3711                     showDialog = true;
3712                 }
3713 
3714                 if (showDialog) {
3715                     Log.d(this, "Creating UT Data enable dialog");
3716                     String message = SuppServicesUiUtil.makeMessage(context, suppKey, phone);
3717                     AlertDialog.Builder builder = FrameworksUtils.makeAlertDialogBuilder(context);
3718                     DialogInterface.OnClickListener networkSettingsClickListener =
3719                             new Dialog.OnClickListener() {
3720                                 @Override
3721                                 public void onClick(DialogInterface dialog, int which) {
3722                                     Intent intent = new Intent(Intent.ACTION_MAIN);
3723                                     ComponentName mobileNetworkSettingsComponent
3724                                         = new ComponentName(
3725                                                 context.getString(
3726                                                     R.string.mobile_network_settings_package),
3727                                                 context.getString(
3728                                                     R.string.mobile_network_settings_class));
3729                                     intent.setComponent(mobileNetworkSettingsComponent);
3730                                     context.startActivity(intent);
3731                                 }
3732                             };
3733                     Dialog dialog = builder.setMessage(message)
3734                         .setNeutralButton(context.getResources().getString(
3735                                 R.string.settings_label),
3736                                 networkSettingsClickListener)
3737                         .setPositiveButton(context.getResources().getString(
3738                                 R.string.supp_service_over_ut_precautions_dialog_dismiss), null)
3739                         .create();
3740                     dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
3741                     dialog.show();
3742                     ret = true;
3743                 }
3744             }
3745         }
3746         return ret;
3747     }
3748 
3749     /**
3750      * Adds a {@link Conference} to the telephony ConnectionService and registers a listener for
3751      * changes to the conference.  Should be used instead of {@link #addConference(Conference)}.
3752      * @param conference The conference.
3753      */
addTelephonyConference(@onNull TelephonyConferenceBase conference)3754     public void addTelephonyConference(@NonNull TelephonyConferenceBase conference) {
3755         addConference(conference);
3756         conference.addTelephonyConferenceListener(mTelephonyConferenceListener);
3757     }
3758 
3759     /**
3760      * Sends a test device to device message on the active call which supports it.
3761      * Used exclusively from the telephony shell command to send a test message.
3762      *
3763      * @param message the message
3764      * @param value the value
3765      */
sendTestDeviceToDeviceMessage(int message, int value)3766     public void sendTestDeviceToDeviceMessage(int message, int value) {
3767        getAllConnections().stream()
3768                .filter(f -> f instanceof TelephonyConnection)
3769                .forEach(t -> {
3770                    TelephonyConnection tc = (TelephonyConnection) t;
3771                    if (!tc.isImsConnection()) {
3772                        Log.w(this, "sendTestDeviceToDeviceMessage: not an IMS connection");
3773                        return;
3774                    }
3775                    Communicator c = tc.getCommunicator();
3776                    if (c == null) {
3777                        Log.w(this, "sendTestDeviceToDeviceMessage: D2D not enabled");
3778                        return;
3779                    }
3780 
3781                    c.sendMessages(Set.of(new Communicator.Message(message, value)));
3782 
3783                });
3784     }
3785 
3786     /**
3787      * Overrides the current D2D transport, forcing the specified one to be active.  Used for test.
3788      * @param transport The class simple name of the transport to make active.
3789      */
setActiveDeviceToDeviceTransport(@onNull String transport)3790     public void setActiveDeviceToDeviceTransport(@NonNull String transport) {
3791         getAllConnections().stream()
3792                 .filter(f -> f instanceof TelephonyConnection)
3793                 .forEach(t -> {
3794                     TelephonyConnection tc = (TelephonyConnection) t;
3795                     Communicator c = tc.getCommunicator();
3796                     if (c == null) {
3797                         Log.w(this, "setActiveDeviceToDeviceTransport: D2D not enabled");
3798                         return;
3799                     }
3800                     Log.i(this, "setActiveDeviceToDeviceTransport: callId=%s, set to: %s",
3801                             tc.getTelecomCallId(), transport);
3802                     c.setTransportActive(transport);
3803                 });
3804     }
3805 
adjustAccountHandle(Phone phone, PhoneAccountHandle origAccountHandle)3806     private PhoneAccountHandle adjustAccountHandle(Phone phone,
3807             PhoneAccountHandle origAccountHandle) {
3808         int origSubId = PhoneUtils.getSubIdForPhoneAccountHandle(origAccountHandle);
3809         int subId = phone.getSubId();
3810         if (origSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
3811                 && subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
3812                 && origSubId != subId) {
3813             PhoneAccountHandle handle = TelecomAccountRegistry.getInstance(this)
3814                 .getPhoneAccountHandleForSubId(subId);
3815             if (handle != null) {
3816                 return handle;
3817             }
3818         }
3819         return origAccountHandle;
3820     }
3821 
3822     /**
3823      * For the passed in incoming {@link TelephonyConnection}, for non- dual active voice devices,
3824      * adds {@link Connection#EXTRA_ANSWERING_DROPS_FG_CALL} if there are ongoing calls on another
3825      * subscription (ie phone account handle) than the one passed in.
3826      * @param connection The connection.
3827      * @param phoneAccountHandle The {@link PhoneAccountHandle} the incoming call originated on;
3828      *                           this is passed in because
3829      *                           {@link Connection#getPhoneAccountHandle()} is not set until after
3830      *                           {@link ConnectionService#onCreateIncomingConnection(
3831      *                           PhoneAccountHandle, ConnectionRequest)} returns.
3832      */
maybeIndicateAnsweringWillDisconnect(@onNull TelephonyConnection connection, @NonNull PhoneAccountHandle phoneAccountHandle)3833     public void maybeIndicateAnsweringWillDisconnect(@NonNull TelephonyConnection connection,
3834             @NonNull PhoneAccountHandle phoneAccountHandle) {
3835         if (mTelephonyManagerProxy.isConcurrentCallsPossible()) {
3836             return;
3837         }
3838         if (isCallPresentOnOtherSub(phoneAccountHandle)) {
3839             Log.i(this, "maybeIndicateAnsweringWillDisconnect; answering call %s will cause a call "
3840                     + "on another subscription to drop.", connection.getTelecomCallId());
3841             Bundle extras = new Bundle();
3842             extras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
3843             connection.putExtras(extras);
3844         }
3845     }
3846 
3847     /**
3848      * Checks to see if there are calls present on a sub other than the one passed in.
3849      * @param incomingHandle The new incoming connection {@link PhoneAccountHandle}
3850      */
isCallPresentOnOtherSub(@onNull PhoneAccountHandle incomingHandle)3851     private boolean isCallPresentOnOtherSub(@NonNull PhoneAccountHandle incomingHandle) {
3852         return getAllConnections().stream()
3853                 .filter(c ->
3854                         // Exclude multiendpoint calls as they're not on this device.
3855                         (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
3856                         // Include any calls not on same sub as current connection.
3857                         && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle))
3858                 .count() > 0;
3859     }
3860 
3861     /**
3862      * Where there are ongoing calls on another subscription other than the one specified,
3863      * disconnect these calls for non-DSDA devices. This is used where there is an incoming call on
3864      * one sub, but there are ongoing calls on another sub which need to be disconnected.
3865      * @param incomingHandle The incoming {@link PhoneAccountHandle}.
3866      */
maybeDisconnectCallsOnOtherSubs(@onNull PhoneAccountHandle incomingHandle)3867     public void maybeDisconnectCallsOnOtherSubs(@NonNull PhoneAccountHandle incomingHandle) {
3868         Log.i(this, "maybeDisconnectCallsOnOtherSubs: check for calls not on %s", incomingHandle);
3869         maybeDisconnectCallsOnOtherSubs(getAllConnections(), incomingHandle,
3870                 mTelephonyManagerProxy);
3871     }
3872 
3873     /**
3874      * Used by {@link #maybeDisconnectCallsOnOtherSubs(PhoneAccountHandle)} to evaluate and perform
3875      * call disconnection. This method exists as a convenience so that it is possible to unit test
3876      * the core functionality.
3877      * @param connections the calls to check.
3878      * @param incomingHandle the incoming handle.
3879      * @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance.
3880      */
3881     @VisibleForTesting
maybeDisconnectCallsOnOtherSubs(@onNull Collection<Connection> connections, @NonNull PhoneAccountHandle incomingHandle, TelephonyManagerProxy telephonyManagerProxy)3882     public static void maybeDisconnectCallsOnOtherSubs(@NonNull Collection<Connection> connections,
3883             @NonNull PhoneAccountHandle incomingHandle,
3884             TelephonyManagerProxy telephonyManagerProxy) {
3885         if (telephonyManagerProxy.isConcurrentCallsPossible()) {
3886             return;
3887         }
3888         connections.stream()
3889                 .filter(c ->
3890                         // Exclude multiendpoint calls as they're not on this device.
3891                         (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL)
3892                                 == 0
3893                                 // Include any calls not on same sub as current connection.
3894                                 && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle))
3895                 .forEach(c -> {
3896                     if (c instanceof TelephonyConnection) {
3897                         TelephonyConnection tc = (TelephonyConnection) c;
3898                         if (!tc.shouldTreatAsEmergencyCall()) {
3899                             Log.i(LOG_TAG,
3900                                     "maybeDisconnectCallsOnOtherSubs: disconnect %s due to "
3901                                             + "incoming call on other sub.",
3902                                     tc.getTelecomCallId());
3903                             // Note: intentionally calling hangup instead of onDisconnect.
3904                             // onDisconnect posts the disconnection to a handle which means that the
3905                             // disconnection will take place AFTER we answer the incoming call.
3906                             tc.hangup(android.telephony.DisconnectCause.LOCAL);
3907                         }
3908                     }
3909                 });
3910     }
3911 
onHold(Conferenceable conferenceable)3912     static void onHold(Conferenceable conferenceable) {
3913         if (conferenceable instanceof Connection) {
3914             Connection connection = (Connection) conferenceable;
3915             connection.onHold();
3916         } else if (conferenceable instanceof Conference) {
3917             Conference conference = (Conference) conferenceable;
3918             conference.onHold();
3919         } else {
3920             throw new IllegalArgumentException(
3921                     "onHold(): Unexpected conferenceable! " + conferenceable);
3922         }
3923     }
3924 
onUnhold(Conferenceable conferenceable)3925     static void onUnhold(Conferenceable conferenceable) {
3926         if (conferenceable instanceof Connection) {
3927             Connection connection = (Connection) conferenceable;
3928             connection.onUnhold();
3929         } else if (conferenceable instanceof Conference) {
3930             Conference conference = (Conference) conferenceable;
3931             conference.onUnhold();
3932         } else {
3933             throw new IllegalArgumentException(
3934                     "onUnhold(): Unexpected conferenceable! " + conferenceable);
3935         }
3936     }
3937 
hangup(Conferenceable conferenceable, int code)3938     private static void hangup(Conferenceable conferenceable, int code) {
3939         if (conferenceable instanceof TelephonyConnection) {
3940             ((TelephonyConnection) conferenceable).hangup(code);
3941         } else if (conferenceable instanceof Conference) {
3942             ((Conference) conferenceable).onDisconnect();
3943         } else {
3944             Log.w(LOG_TAG, "hangup(): Unexpected conferenceable! " + conferenceable);
3945         }
3946     }
3947 
3948      /**
3949      * Evaluates whether a connection or conference exists on subscriptions other than the one
3950      * corresponding to the existing {@link PhoneAccountHandle}.
3951      * @param connections all individual connections, including conference participants.
3952      * @param conferences all conferences.
3953      * @param currentHandle the existing call handle;
3954      * @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance.
3955      */
maybeGetFirstConferenceableFromOtherSubscription( @onNull Collection<Connection> connections, @NonNull Collection<Conference> conferences, @NonNull PhoneAccountHandle currentHandle, TelephonyManagerProxy telephonyManagerProxy)3956     private static @Nullable Conferenceable maybeGetFirstConferenceableFromOtherSubscription(
3957             @NonNull Collection<Connection> connections,
3958             @NonNull Collection<Conference> conferences,
3959             @NonNull PhoneAccountHandle currentHandle,
3960             TelephonyManagerProxy telephonyManagerProxy) {
3961         if (!telephonyManagerProxy.isConcurrentCallsPossible()) {
3962             return null;
3963         }
3964 
3965         List<Conference> otherSubConferences = conferences.stream()
3966                 .filter(c ->
3967                         // Exclude multiendpoint calls as they're not on this device.
3968                         (c.getConnectionProperties()
3969                                 & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
3970                                 // Include any conferences not on same sub as current connection.
3971                                 && !Objects.equals(c.getPhoneAccountHandle(),
3972                                 currentHandle))
3973                 .toList();
3974         if (!otherSubConferences.isEmpty()) {
3975             Log.i(LOG_TAG, "maybeGetFirstConferenceable: found "
3976                     + otherSubConferences.get(0).getTelecomCallId() + " on "
3977                     + otherSubConferences.get(0).getPhoneAccountHandle());
3978             return otherSubConferences.get(0);
3979         }
3980 
3981         // Considers Connections (including conference participants) only if no conferences.
3982         List<Connection> otherSubConnections = connections.stream()
3983                 .filter(c ->
3984                         // Exclude multiendpoint calls as they're not on this device.
3985                         (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
3986                                 // Include any calls not on same sub as current connection.
3987                                 && !Objects.equals(c.getPhoneAccountHandle(),
3988                                 currentHandle)).toList();
3989 
3990         if (!otherSubConnections.isEmpty()) {
3991             if (otherSubConnections.size() > 1) {
3992                 Log.w(LOG_TAG, "Unexpected number of connections: "
3993                         + otherSubConnections.size() + " on other sub!");
3994             }
3995             Log.i(LOG_TAG, "maybeGetFirstConferenceable: found "
3996                     + otherSubConnections.get(0).getTelecomCallId() + " on "
3997                     + otherSubConnections.get(0).getPhoneAccountHandle());
3998             return otherSubConnections.get(0);
3999         }
4000         return null;
4001     }
4002 
4003     /**
4004      * Where there are ongoing calls on multiple subscriptions for DSDA devices, let the 'hold'
4005      * button perform an unhold on the other sub's Connection or Conference. This covers for Dialer
4006      * apps that may not have a dedicated 'swap' button for calls across different subs.
4007      * @param currentHandle The {@link PhoneAccountHandle} of the current active voice call.
4008      */
maybeUnholdCallsOnOtherSubs( @onNull PhoneAccountHandle currentHandle)4009     public void maybeUnholdCallsOnOtherSubs(
4010             @NonNull PhoneAccountHandle currentHandle) {
4011         Log.i(this, "maybeUnholdCallsOnOtherSubs: check for calls not on %s",
4012                 currentHandle);
4013         maybeUnholdCallsOnOtherSubs(getAllConnections(), getAllConferences(),
4014                 currentHandle, mTelephonyManagerProxy);
4015     }
4016 
4017     /**
4018      * Where there are ongoing calls on multiple subscriptions for DSDA devices, let the 'hold'
4019      * button perform an unhold on the other sub's Connection or Conference. This is a convenience
4020      * method to unit test the core functionality.
4021      *
4022      * @param connections all individual connections, including conference participants.
4023      * @param conferences all conferences.
4024      * @param currentHandle The {@link PhoneAccountHandle} of the current active call.
4025      * @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance.
4026      */
4027     @VisibleForTesting
maybeUnholdCallsOnOtherSubs(@onNull Collection<Connection> connections, @NonNull Collection<Conference> conferences, @NonNull PhoneAccountHandle currentHandle, TelephonyManagerProxy telephonyManagerProxy)4028     protected static void maybeUnholdCallsOnOtherSubs(@NonNull Collection<Connection> connections,
4029             @NonNull Collection<Conference> conferences,
4030             @NonNull PhoneAccountHandle currentHandle,
4031             TelephonyManagerProxy telephonyManagerProxy) {
4032         Conferenceable c = maybeGetFirstConferenceableFromOtherSubscription(
4033                 connections, conferences, currentHandle, telephonyManagerProxy);
4034         if (c != null) {
4035             onUnhold(c);
4036         }
4037     }
4038 
4039     /**
4040      * For DSDA devices, when an outgoing call is dialed out from the 2nd sub, holds the first call.
4041      *
4042      * @param outgoingHandle The outgoing {@link PhoneAccountHandle}.
4043      * @return the Conferenceable representing the Connection or Conference to be held.
4044      */
maybeHoldCallsOnOtherSubs( @onNull PhoneAccountHandle outgoingHandle)4045     private @Nullable Conferenceable maybeHoldCallsOnOtherSubs(
4046             @NonNull PhoneAccountHandle outgoingHandle) {
4047         Log.i(this, "maybeHoldCallsOnOtherSubs: check for calls not on %s",
4048                 outgoingHandle);
4049         return maybeHoldCallsOnOtherSubs(getAllConnections(), getAllConferences(),
4050                 outgoingHandle, mTelephonyManagerProxy);
4051     }
4052 
4053     /**
4054      * For DSDA devices, when an outgoing call is dialed out from the 2nd sub, holds the first call.
4055      * This is a convenience method to unit test the core functionality.
4056      *
4057      * @param connections all individual connections, including conference participants.
4058      * @param conferences all conferences.
4059      * @param outgoingHandle The outgoing {@link PhoneAccountHandle}.
4060      * @param telephonyManagerProxy the proxy to the {@link TelephonyManager} instance.
4061      * @return the {@link Conferenceable} representing the Connection or Conference to be held.
4062      */
4063     @VisibleForTesting
maybeHoldCallsOnOtherSubs( @onNull Collection<Connection> connections, @NonNull Collection<Conference> conferences, @NonNull PhoneAccountHandle outgoingHandle, TelephonyManagerProxy telephonyManagerProxy)4064     protected static @Nullable Conferenceable maybeHoldCallsOnOtherSubs(
4065             @NonNull Collection<Connection> connections,
4066             @NonNull Collection<Conference> conferences,
4067             @NonNull PhoneAccountHandle outgoingHandle,
4068             TelephonyManagerProxy telephonyManagerProxy) {
4069         Conferenceable c = maybeGetFirstConferenceableFromOtherSubscription(
4070                 connections, conferences, outgoingHandle, telephonyManagerProxy);
4071         if (c != null) {
4072             onHold(c);
4073             return c;
4074         }
4075         return null;
4076     }
4077 
disconnectAllCallsOnOtherSubs(@onNull PhoneAccountHandle handle)4078     private void disconnectAllCallsOnOtherSubs (@NonNull PhoneAccountHandle handle) {
4079         Collection<Connection>connections = getAllConnections();
4080         connections.stream()
4081                 .filter(c ->
4082                         (c.getState() == Connection.STATE_ACTIVE
4083                                 || c.getState() == Connection.STATE_HOLDING)
4084                                 // Include any calls not on same sub as current connection.
4085                                 && !Objects.equals(c.getPhoneAccountHandle(), handle))
4086                 .forEach(c -> {
4087                     if (c instanceof TelephonyConnection) {
4088                         TelephonyConnection tc = (TelephonyConnection) c;
4089                         Log.i(LOG_TAG, "disconnectAllCallsOnOtherSubs: disconnect" +
4090                                 " %s due to redial happened on other sub.",
4091                                 tc.getTelecomCallId());
4092                         tc.hangup(android.telephony.DisconnectCause.LOCAL);
4093                     }
4094                 });
4095     }
4096 
getActiveCallDomain(int subId)4097     private @NetworkRegistrationInfo.Domain int getActiveCallDomain(int subId) {
4098         for (Connection c: getAllConnections()) {
4099             if ((c instanceof TelephonyConnection)) {
4100                 TelephonyConnection connection = (TelephonyConnection) c;
4101                 Phone phone = connection.getPhone();
4102                 if (phone == null) {
4103                     continue;
4104                 }
4105 
4106                 if (phone.getSubId() == subId) {
4107                     if (phone instanceof GsmCdmaPhone) {
4108                         return NetworkRegistrationInfo.DOMAIN_CS;
4109                     } else if (phone instanceof ImsPhone) {
4110                         return NetworkRegistrationInfo.DOMAIN_PS;
4111                     }
4112                 }
4113             }
4114         }
4115         return NetworkRegistrationInfo.DOMAIN_UNKNOWN;
4116     }
4117 
handleEmergencyCallStartedForSatelliteSOSMessageRecommender( @onNull TelephonyConnection connection, @NonNull Phone phone)4118     private void handleEmergencyCallStartedForSatelliteSOSMessageRecommender(
4119             @NonNull TelephonyConnection connection, @NonNull Phone phone) {
4120         if (mSatelliteSOSMessageRecommender == null) {
4121             mSatelliteSOSMessageRecommender = new SatelliteSOSMessageRecommender(
4122                     phone.getContext().getMainLooper());
4123         }
4124         connection.addTelephonyConnectionListener(mEmergencyConnectionSatelliteListener);
4125         mSatelliteSOSMessageRecommender.onEmergencyCallStarted(connection, phone);
4126     }
4127 
4128     /**
4129      * Check whether making a call is disallowed while using satellite
4130      * @param phone phone object whose supported services needs to be checked
4131      * @return {@code true} if network does not support calls while using satellite
4132      * else {@code false}.
4133      */
isCallDisallowedDueToSatellite(Phone phone)4134     private boolean isCallDisallowedDueToSatellite(Phone phone) {
4135         if (phone == null) {
4136             return false;
4137         }
4138 
4139         ServiceState serviceState = phone.getServiceState();
4140         if (!serviceState.isUsingNonTerrestrialNetwork()) {
4141             // Device is not connected to satellite
4142             return false;
4143         }
4144 
4145         for (NetworkRegistrationInfo nri : serviceState.getNetworkRegistrationInfoList()) {
4146             if (nri.isNonTerrestrialNetwork()
4147                     && nri.getAvailableServices().contains(
4148                     NetworkRegistrationInfo.SERVICE_TYPE_VOICE)) {
4149                 // Call is supported while using satellite
4150                 return false;
4151             }
4152         }
4153 
4154         // Call is disallowed while using satellite
4155         return true;
4156     }
4157 }
4158