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