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