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 android.annotation.NonNull;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.content.ActivityNotFoundException;
23 import android.content.BroadcastReceiver;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.ParcelUuid;
32 import android.telecom.Conference;
33 import android.telecom.Connection;
34 import android.telecom.ConnectionRequest;
35 import android.telecom.ConnectionService;
36 import android.telecom.DisconnectCause;
37 import android.telecom.PhoneAccount;
38 import android.telecom.PhoneAccountHandle;
39 import android.telecom.TelecomManager;
40 import android.telecom.VideoProfile;
41 import android.telephony.CarrierConfigManager;
42 import android.telephony.PhoneNumberUtils;
43 import android.telephony.RadioAccessFamily;
44 import android.telephony.ServiceState;
45 import android.telephony.SubscriptionManager;
46 import android.telephony.TelephonyManager;
47 import android.telephony.emergency.EmergencyNumber;
48 import android.text.TextUtils;
49 import android.util.Pair;
50 import android.view.WindowManager;
51 
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.internal.telephony.Call;
54 import com.android.internal.telephony.CallStateException;
55 import com.android.internal.telephony.GsmCdmaPhone;
56 import com.android.internal.telephony.IccCard;
57 import com.android.internal.telephony.IccCardConstants;
58 import com.android.internal.telephony.Phone;
59 import com.android.internal.telephony.PhoneConstants;
60 import com.android.internal.telephony.PhoneFactory;
61 import com.android.internal.telephony.PhoneSwitcher;
62 import com.android.internal.telephony.RIL;
63 import com.android.internal.telephony.SubscriptionController;
64 import com.android.internal.telephony.d2d.Communicator;
65 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
66 import com.android.internal.telephony.imsphone.ImsPhone;
67 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
68 import com.android.phone.MMIDialogActivity;
69 import com.android.phone.PhoneUtils;
70 import com.android.phone.R;
71 import com.android.phone.callcomposer.CallComposerPictureManager;
72 import com.android.phone.settings.SuppServicesUiUtil;
73 
74 import java.lang.ref.WeakReference;
75 import java.util.ArrayList;
76 import java.util.Arrays;
77 import java.util.Collection;
78 import java.util.Collections;
79 import java.util.HashMap;
80 import java.util.HashSet;
81 import java.util.LinkedList;
82 import java.util.List;
83 import java.util.Map;
84 import java.util.Objects;
85 import java.util.Queue;
86 import java.util.concurrent.CompletableFuture;
87 import java.util.function.Consumer;
88 import java.util.regex.Pattern;
89 
90 import javax.annotation.Nullable;
91 
92 /**
93  * Service for making GSM and CDMA connections.
94  */
95 public class TelephonyConnectionService extends ConnectionService {
96     private static final String LOG_TAG = TelephonyConnectionService.class.getSimpleName();
97     // Timeout before we continue with the emergency call without waiting for DDS switch response
98     // from the modem.
99     private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000;
100 
101     // If configured, reject attempts to dial numbers matching this pattern.
102     private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
103             Pattern.compile("\\*228[0-9]{0,2}");
104 
105     private final TelephonyConnectionServiceProxy mTelephonyConnectionServiceProxy =
106             new TelephonyConnectionServiceProxy() {
107         @Override
108         public Collection<Connection> getAllConnections() {
109             return TelephonyConnectionService.this.getAllConnections();
110         }
111         @Override
112         public void addConference(TelephonyConference mTelephonyConference) {
113             TelephonyConnectionService.this.addTelephonyConference(mTelephonyConference);
114         }
115         @Override
116         public void addConference(ImsConference mImsConference) {
117             TelephonyConnectionService.this.addTelephonyConference(mImsConference);
118         }
119         @Override
120         public void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
121                                           Connection connection) {
122             TelephonyConnectionService.this
123                     .addExistingConnection(phoneAccountHandle, connection);
124         }
125         @Override
126         public void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
127                 Connection connection, Conference conference) {
128             TelephonyConnectionService.this
129                     .addExistingConnection(phoneAccountHandle, connection, conference);
130         }
131         @Override
132         public void addConnectionToConferenceController(TelephonyConnection connection) {
133             TelephonyConnectionService.this.addConnectionToConferenceController(connection);
134         }
135     };
136 
137     private final BroadcastReceiver mTtyBroadcastReceiver = new BroadcastReceiver() {
138         @Override
139         public void onReceive(Context context, Intent intent) {
140             String action = intent.getAction();
141             Log.v(this, "onReceive, action: %s", action);
142             if (action.equals(TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED)) {
143                 int newPreferredTtyMode = intent.getIntExtra(
144                         TelecomManager.EXTRA_TTY_PREFERRED_MODE, TelecomManager.TTY_MODE_OFF);
145 
146                 boolean isTtyNowEnabled = newPreferredTtyMode != TelecomManager.TTY_MODE_OFF;
147                 if (isTtyNowEnabled != mIsTtyEnabled) {
148                     handleTtyModeChange(isTtyNowEnabled);
149                 }
150             }
151         }
152     };
153 
154     private final TelephonyConferenceController mTelephonyConferenceController =
155             new TelephonyConferenceController(mTelephonyConnectionServiceProxy);
156     private final CdmaConferenceController mCdmaConferenceController =
157             new CdmaConferenceController(this);
158     private ImsConferenceController mImsConferenceController;
159 
160     private ComponentName mExpectedComponentName = null;
161     private RadioOnHelper mRadioOnHelper;
162     private EmergencyTonePlayer mEmergencyTonePlayer;
163     private HoldTracker mHoldTracker;
164     private boolean mIsTtyEnabled;
165     /** Set to true when there is an emergency call pending which will potential trigger a dial.
166      * This must be set to false when the call is dialed. */
167     private volatile boolean mIsEmergencyCallPending;
168 
169     // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has
170     // already tried to connect with. There should be only one TelephonyConnection trying to place a
171     // call at one time. We also only access this cache from a TelephonyConnection that wishes to
172     // redial, so we use a WeakReference that will become stale once the TelephonyConnection is
173     // destroyed.
174     @VisibleForTesting
175     public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
176     private DeviceState mDeviceState = new DeviceState();
177 
178     /**
179      * Keeps track of the status of a SIM slot.
180      */
181     private static class SlotStatus {
182         public int slotId;
183         // RAT capabilities
184         public int capabilities;
185         // By default, we will assume that the slots are not locked.
186         public boolean isLocked = false;
187         // Is the emergency number associated with the slot
188         public boolean hasDialedEmergencyNumber = false;
189         //SimState
190         public int simState;
191 
SlotStatus(int slotId, int capabilities)192         public SlotStatus(int slotId, int capabilities) {
193             this.slotId = slotId;
194             this.capabilities = capabilities;
195         }
196     }
197 
198     /**
199      * SubscriptionManager dependencies for testing.
200      */
201     @VisibleForTesting
202     public interface SubscriptionManagerProxy {
getDefaultVoicePhoneId()203         int getDefaultVoicePhoneId();
getSimStateForSlotIdx(int slotId)204         int getSimStateForSlotIdx(int slotId);
getPhoneId(int subId)205         int getPhoneId(int subId);
206     }
207 
208     private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() {
209         @Override
210         public int getDefaultVoicePhoneId() {
211             return SubscriptionManager.getDefaultVoicePhoneId();
212         }
213 
214         @Override
215         public int getSimStateForSlotIdx(int slotId) {
216             return SubscriptionManager.getSimStateForSlotIndex(slotId);
217         }
218 
219         @Override
220         public int getPhoneId(int subId) {
221             return SubscriptionManager.getPhoneId(subId);
222         }
223     };
224 
225     /**
226      * TelephonyManager dependencies for testing.
227      */
228     @VisibleForTesting
229     public interface TelephonyManagerProxy {
getPhoneCount()230         int getPhoneCount();
hasIccCard(int slotId)231         boolean hasIccCard(int slotId);
isCurrentEmergencyNumber(String number)232         boolean isCurrentEmergencyNumber(String number);
getCurrentEmergencyNumberList()233         Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList();
234     }
235 
236     private TelephonyManagerProxy mTelephonyManagerProxy;
237 
238     private class TelephonyManagerProxyImpl implements TelephonyManagerProxy {
239         private final TelephonyManager mTelephonyManager;
240 
241 
TelephonyManagerProxyImpl(Context context)242         TelephonyManagerProxyImpl(Context context) {
243             mTelephonyManager = new TelephonyManager(context);
244         }
245 
246         @Override
getPhoneCount()247         public int getPhoneCount() {
248             return mTelephonyManager.getPhoneCount();
249         }
250 
251         @Override
hasIccCard(int slotId)252         public boolean hasIccCard(int slotId) {
253             return mTelephonyManager.hasIccCard(slotId);
254         }
255 
256         @Override
isCurrentEmergencyNumber(String number)257         public boolean isCurrentEmergencyNumber(String number) {
258             try {
259                 return mTelephonyManager.isEmergencyNumber(number);
260             } catch (IllegalStateException ise) {
261                 return false;
262             }
263         }
264 
265         @Override
getCurrentEmergencyNumberList()266         public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList() {
267             try {
268                 return mTelephonyManager.getEmergencyNumberList();
269             } catch (IllegalStateException ise) {
270                 return new HashMap<>();
271             }
272         }
273     }
274 
275     /**
276      * PhoneFactory Dependencies for testing.
277      */
278     @VisibleForTesting
279     public interface PhoneFactoryProxy {
getPhone(int index)280         Phone getPhone(int index);
getDefaultPhone()281         Phone getDefaultPhone();
getPhones()282         Phone[] getPhones();
283     }
284 
285     private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
286         @Override
287         public Phone getPhone(int index) {
288             return PhoneFactory.getPhone(index);
289         }
290 
291         @Override
292         public Phone getDefaultPhone() {
293             return PhoneFactory.getDefaultPhone();
294         }
295 
296         @Override
297         public Phone[] getPhones() {
298             return PhoneFactory.getPhones();
299         }
300     };
301 
302     /**
303      * PhoneUtils dependencies for testing.
304      */
305     @VisibleForTesting
306     public interface PhoneUtilsProxy {
getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle)307         int getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle);
makePstnPhoneAccountHandle(Phone phone)308         PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone);
makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix, boolean isEmergency)309         PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix,
310                 boolean isEmergency);
311     }
312 
313     private PhoneUtilsProxy mPhoneUtilsProxy = new PhoneUtilsProxy() {
314         @Override
315         public int getSubIdForPhoneAccountHandle(PhoneAccountHandle accountHandle) {
316             return PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
317         }
318 
319         @Override
320         public PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
321             return PhoneUtils.makePstnPhoneAccountHandle(phone);
322         }
323 
324         @Override
325         public PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(Phone phone, String prefix,
326                 boolean isEmergency) {
327             return PhoneUtils.makePstnPhoneAccountHandleWithPrefix(phone, prefix, isEmergency);
328         }
329     };
330 
331     /**
332      * PhoneNumberUtils dependencies for testing.
333      */
334     @VisibleForTesting
335     public interface PhoneNumberUtilsProxy {
convertToEmergencyNumber(Context context, String number)336         String convertToEmergencyNumber(Context context, String number);
337     }
338 
339     private PhoneNumberUtilsProxy mPhoneNumberUtilsProxy = new PhoneNumberUtilsProxy() {
340         @Override
341         public String convertToEmergencyNumber(Context context, String number) {
342             return PhoneNumberUtils.convertToEmergencyNumber(context, number);
343         }
344     };
345 
346     /**
347      * PhoneSwitcher dependencies for testing.
348      */
349     @VisibleForTesting
350     public interface PhoneSwitcherProxy {
getPhoneSwitcher()351         PhoneSwitcher getPhoneSwitcher();
352     }
353 
354     private PhoneSwitcherProxy mPhoneSwitcherProxy = new PhoneSwitcherProxy() {
355         @Override
356         public PhoneSwitcher getPhoneSwitcher() {
357             return PhoneSwitcher.getInstance();
358         }
359     };
360 
361     /**
362      * DisconnectCause depends on PhoneGlobals in order to get a system context. Mock out
363      * dependency for testing.
364      */
365     @VisibleForTesting
366     public interface DisconnectCauseFactory {
toTelecomDisconnectCause(int telephonyDisconnectCause, String reason)367         DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, String reason);
toTelecomDisconnectCause(int telephonyDisconnectCause, String reason, int phoneId)368         DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause,
369                 String reason, int phoneId);
370     }
371 
372     private DisconnectCauseFactory mDisconnectCauseFactory = new DisconnectCauseFactory() {
373         @Override
374         public DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause,
375                 String reason) {
376             return DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause, reason);
377         }
378 
379         @Override
380         public DisconnectCause toTelecomDisconnectCause(int telephonyDisconnectCause, String reason,
381                 int phoneId) {
382             return DisconnectCauseUtil.toTelecomDisconnectCause(telephonyDisconnectCause, reason,
383                     phoneId);
384         }
385     };
386 
387     /**
388      * Overrides SubscriptionManager dependencies for testing.
389      */
390     @VisibleForTesting
setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)391     public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) {
392         mSubscriptionManagerProxy = proxy;
393     }
394 
395     /**
396      * Overrides TelephonyManager dependencies for testing.
397      */
398     @VisibleForTesting
setTelephonyManagerProxy(TelephonyManagerProxy proxy)399     public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) {
400         mTelephonyManagerProxy = proxy;
401     }
402 
403     /**
404      * Overrides PhoneFactory dependencies for testing.
405      */
406     @VisibleForTesting
setPhoneFactoryProxy(PhoneFactoryProxy proxy)407     public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) {
408         mPhoneFactoryProxy = proxy;
409     }
410 
411     /**
412      * Overrides configuration and settings dependencies for testing.
413      */
414     @VisibleForTesting
setDeviceState(DeviceState state)415     public void setDeviceState(DeviceState state) {
416         mDeviceState = state;
417     }
418 
419     /**
420      * Overrides radioOnHelper for testing.
421      */
422     @VisibleForTesting
setRadioOnHelper(RadioOnHelper radioOnHelper)423     public void setRadioOnHelper(RadioOnHelper radioOnHelper) {
424         mRadioOnHelper = radioOnHelper;
425     }
426 
427     /**
428      * Overrides PhoneSwitcher dependencies for testing.
429      */
430     @VisibleForTesting
setPhoneSwitcherProxy(PhoneSwitcherProxy proxy)431     public void setPhoneSwitcherProxy(PhoneSwitcherProxy proxy) {
432         mPhoneSwitcherProxy = proxy;
433     }
434 
435     /**
436      * Overrides PhoneNumberUtils dependencies for testing.
437      */
438     @VisibleForTesting
setPhoneNumberUtilsProxy(PhoneNumberUtilsProxy proxy)439     public void setPhoneNumberUtilsProxy(PhoneNumberUtilsProxy proxy) {
440         mPhoneNumberUtilsProxy = proxy;
441     }
442 
443     /**
444      * Overrides PhoneUtils dependencies for testing.
445      */
446     @VisibleForTesting
setPhoneUtilsProxy(PhoneUtilsProxy proxy)447     public void setPhoneUtilsProxy(PhoneUtilsProxy proxy) {
448         mPhoneUtilsProxy = proxy;
449     }
450 
451     /**
452      * Override DisconnectCause creation for testing.
453      */
454     @VisibleForTesting
setDisconnectCauseFactory(DisconnectCauseFactory factory)455     public void setDisconnectCauseFactory(DisconnectCauseFactory factory) {
456         mDisconnectCauseFactory = factory;
457     }
458 
459     /**
460      * A listener to actionable events specific to the TelephonyConnection.
461      */
462     private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
463             new TelephonyConnection.TelephonyConnectionListener() {
464         @Override
465         public void onOriginalConnectionConfigured(TelephonyConnection c) {
466             addConnectionToConferenceController(c);
467         }
468 
469         @Override
470         public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) {
471             retryOutgoingOriginalConnection(c, isPermanentFailure);
472         }
473     };
474 
475     private final TelephonyConferenceBase.TelephonyConferenceListener mTelephonyConferenceListener =
476             new TelephonyConferenceBase.TelephonyConferenceListener() {
477         @Override
478         public void onConferenceMembershipChanged(Connection connection) {
479             mHoldTracker.updateHoldCapability(connection.getPhoneAccountHandle());
480         }
481     };
482 
483     @Override
onCreate()484     public void onCreate() {
485         super.onCreate();
486         mImsConferenceController = new ImsConferenceController(
487                 TelecomAccountRegistry.getInstance(this),
488                 mTelephonyConnectionServiceProxy,
489                 // FeatureFlagProxy; used to determine if standalone call emulation is enabled.
490                 // TODO: Move to carrier config
491                 () -> true);
492         setTelephonyManagerProxy(new TelephonyManagerProxyImpl(getApplicationContext()));
493         mExpectedComponentName = new ComponentName(this, this.getClass());
494         mEmergencyTonePlayer = new EmergencyTonePlayer(this);
495         TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
496         mHoldTracker = new HoldTracker();
497         mIsTtyEnabled = mDeviceState.isTtyModeEnabled(this);
498 
499         IntentFilter intentFilter = new IntentFilter(
500                 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
501         registerReceiver(mTtyBroadcastReceiver, intentFilter,
502                 android.Manifest.permission.MODIFY_PHONE_STATE, null);
503     }
504 
505     @Override
onUnbind(Intent intent)506     public boolean onUnbind(Intent intent) {
507         unregisterReceiver(mTtyBroadcastReceiver);
508         return super.onUnbind(intent);
509     }
510 
placeOutgoingConference(ConnectionRequest request, Connection resultConnection, Phone phone)511     private Conference placeOutgoingConference(ConnectionRequest request,
512             Connection resultConnection, Phone phone) {
513         if (resultConnection instanceof TelephonyConnection) {
514             return placeOutgoingConference((TelephonyConnection) resultConnection, phone, request);
515         }
516         return null;
517     }
518 
placeOutgoingConference(TelephonyConnection conferenceHostConnection, Phone phone, ConnectionRequest request)519     private Conference placeOutgoingConference(TelephonyConnection conferenceHostConnection,
520             Phone phone, ConnectionRequest request) {
521         updatePhoneAccount(conferenceHostConnection, phone);
522         com.android.internal.telephony.Connection originalConnection = null;
523         try {
524             originalConnection = phone.startConference(
525                     getParticipantsToDial(request.getParticipants()),
526                     new ImsPhone.ImsDialArgs.Builder()
527                     .setVideoState(request.getVideoState())
528                     .setRttTextStream(conferenceHostConnection.getRttTextStream())
529                     .build());
530         } catch (CallStateException e) {
531             Log.e(this, e, "placeOutgoingConference, phone.startConference exception: " + e);
532             handleCallStateException(e, conferenceHostConnection, phone);
533             return null;
534         }
535 
536         if (originalConnection == null) {
537             Log.d(this, "placeOutgoingConference, phone.startConference returned null");
538             conferenceHostConnection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
539                     android.telephony.DisconnectCause.OUTGOING_FAILURE,
540                     "conferenceHostConnection is null",
541                     phone.getPhoneId()));
542             conferenceHostConnection.clearOriginalConnection();
543             conferenceHostConnection.destroy();
544         } else {
545             conferenceHostConnection.setOriginalConnection(originalConnection);
546         }
547 
548         return prepareConference(conferenceHostConnection, request.getAccountHandle());
549     }
550 
prepareConference(Connection conn, PhoneAccountHandle phoneAccountHandle)551     Conference prepareConference(Connection conn, PhoneAccountHandle phoneAccountHandle) {
552         if (!(conn instanceof TelephonyConnection)) {
553             Log.w(this, "prepareConference returning NULL conference");
554             return null;
555         }
556 
557         TelephonyConnection connection = (TelephonyConnection)conn;
558 
559         ImsConference conference = new ImsConference(TelecomAccountRegistry.getInstance(this),
560                 mTelephonyConnectionServiceProxy, connection,
561                 phoneAccountHandle, () -> true,
562                 ImsConferenceController.getCarrierConfig(connection.getPhone()));
563         mImsConferenceController.addConference(conference);
564         conference.setVideoState(connection,
565                 connection.getVideoState());
566         conference.setVideoProvider(connection,
567                 connection.getVideoProvider());
568         conference.setStatusHints(connection.getStatusHints());
569         conference.setAddress(connection.getAddress(),
570                 connection.getAddressPresentation());
571         conference.setCallerDisplayName(connection.getCallerDisplayName(),
572                 connection.getCallerDisplayNamePresentation());
573         conference.setParticipants(connection.getParticipants());
574         return conference;
575     }
576 
577     @Override
onCreateIncomingConference( @ullable PhoneAccountHandle connectionManagerPhoneAccount, @NonNull final ConnectionRequest request)578     public @Nullable Conference onCreateIncomingConference(
579             @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
580             @NonNull final ConnectionRequest request) {
581         Log.i(this, "onCreateIncomingConference, request: " + request);
582         Connection connection = onCreateIncomingConnection(connectionManagerPhoneAccount, request);
583         Log.d(this, "onCreateIncomingConference, connection: %s", connection);
584         if (connection == null) {
585             Log.i(this, "onCreateIncomingConference, implementation returned null connection.");
586             return Conference.createFailedConference(
587                     new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"),
588                     request.getAccountHandle());
589         }
590 
591         final Phone phone = getPhoneForAccount(request.getAccountHandle(),
592                 false /* isEmergencyCall*/, null /* not an emergency call */);
593         if (phone == null) {
594             Log.d(this, "onCreateIncomingConference, phone is null");
595             return Conference.createFailedConference(
596                     DisconnectCauseUtil.toTelecomDisconnectCause(
597                             android.telephony.DisconnectCause.OUT_OF_SERVICE,
598                             "Phone is null"),
599                     request.getAccountHandle());
600         }
601 
602         return prepareConference(connection, request.getAccountHandle());
603     }
604 
605     @Override
onCreateOutgoingConference( @ullable PhoneAccountHandle connectionManagerPhoneAccount, @NonNull final ConnectionRequest request)606     public @Nullable Conference onCreateOutgoingConference(
607             @Nullable PhoneAccountHandle connectionManagerPhoneAccount,
608             @NonNull final ConnectionRequest request) {
609         Log.i(this, "onCreateOutgoingConference, request: " + request);
610         Connection connection = onCreateOutgoingConnection(connectionManagerPhoneAccount, request);
611         Log.d(this, "onCreateOutgoingConference, connection: %s", connection);
612         if (connection == null) {
613             Log.i(this, "onCreateOutgoingConference, implementation returned null connection.");
614             return Conference.createFailedConference(
615                     new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"),
616                     request.getAccountHandle());
617         }
618 
619         final Phone phone = getPhoneForAccount(request.getAccountHandle(),
620                 false /* isEmergencyCall*/, null /* not an emergency call */);
621         if (phone == null) {
622             Log.d(this, "onCreateOutgoingConference, phone is null");
623             return Conference.createFailedConference(
624                     DisconnectCauseUtil.toTelecomDisconnectCause(
625                             android.telephony.DisconnectCause.OUT_OF_SERVICE,
626                             "Phone is null"),
627                     request.getAccountHandle());
628         }
629 
630         return placeOutgoingConference(request, connection, phone);
631     }
632 
getParticipantsToDial(List<Uri> participants)633     private String[] getParticipantsToDial(List<Uri> participants) {
634         String[] participantsToDial = new String[participants.size()];
635         int i = 0;
636         for (Uri participant : participants) {
637            participantsToDial[i] = participant.getSchemeSpecificPart();
638            i++;
639         }
640         return participantsToDial;
641     }
642 
643     @Override
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)644     public Connection onCreateOutgoingConnection(
645             PhoneAccountHandle connectionManagerPhoneAccount,
646             final ConnectionRequest request) {
647         Log.i(this, "onCreateOutgoingConnection, request: " + request);
648 
649         Uri handle = request.getAddress();
650         boolean isAdhocConference = request.isAdhocConferenceCall();
651 
652         if (!isAdhocConference && handle == null) {
653             Log.d(this, "onCreateOutgoingConnection, handle is null");
654             return Connection.createFailedConnection(
655                     mDisconnectCauseFactory.toTelecomDisconnectCause(
656                             android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
657                             "No phone number supplied"));
658         }
659 
660         String scheme = handle.getScheme();
661         String number;
662         if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
663             // TODO: We don't check for SecurityException here (requires
664             // CALL_PRIVILEGED permission).
665             final Phone phone = getPhoneForAccount(request.getAccountHandle(),
666                     false /* isEmergencyCall */, null /* not an emergency call */);
667             if (phone == null) {
668                 Log.d(this, "onCreateOutgoingConnection, phone is null");
669                 return Connection.createFailedConnection(
670                         mDisconnectCauseFactory.toTelecomDisconnectCause(
671                                 android.telephony.DisconnectCause.OUT_OF_SERVICE,
672                                 "Phone is null"));
673             }
674             number = phone.getVoiceMailNumber();
675             if (TextUtils.isEmpty(number)) {
676                 Log.d(this, "onCreateOutgoingConnection, no voicemail number set.");
677                 return Connection.createFailedConnection(
678                         mDisconnectCauseFactory.toTelecomDisconnectCause(
679                                 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING,
680                                 "Voicemail scheme provided but no voicemail number set.",
681                                 phone.getPhoneId()));
682             }
683 
684             // Convert voicemail: to tel:
685             handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
686         } else {
687             if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
688                 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme);
689                 return Connection.createFailedConnection(
690                         mDisconnectCauseFactory.toTelecomDisconnectCause(
691                                 android.telephony.DisconnectCause.INVALID_NUMBER,
692                                 "Handle scheme is not type tel"));
693             }
694 
695             number = handle.getSchemeSpecificPart();
696             if (TextUtils.isEmpty(number)) {
697                 Log.d(this, "onCreateOutgoingConnection, unable to parse number");
698                 return Connection.createFailedConnection(
699                         mDisconnectCauseFactory.toTelecomDisconnectCause(
700                                 android.telephony.DisconnectCause.INVALID_NUMBER,
701                                 "Unable to parse number"));
702             }
703 
704             final Phone phone = getPhoneForAccount(request.getAccountHandle(),
705                     false /* isEmergencyCall*/, null /* not an emergency call */);
706             if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) {
707                 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number
708                 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and
709                 // when dialed could lock LTE SIMs to 3G if not prohibited..
710                 boolean disableActivation = false;
711                 CarrierConfigManager cfgManager = (CarrierConfigManager)
712                         phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
713                 if (cfgManager != null) {
714                     disableActivation = cfgManager.getConfigForSubId(phone.getSubId())
715                             .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL);
716                 }
717 
718                 if (disableActivation) {
719                     return Connection.createFailedConnection(
720                             mDisconnectCauseFactory.toTelecomDisconnectCause(
721                                     android.telephony.DisconnectCause
722                                             .CDMA_ALREADY_ACTIVATED,
723                                     "Tried to dial *228",
724                                     phone.getPhoneId()));
725                 }
726             }
727         }
728 
729         final boolean isEmergencyNumber = mTelephonyManagerProxy.isCurrentEmergencyNumber(number);
730         // Find out if this is a test emergency number
731         final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number);
732 
733         // Convert into emergency number if necessary
734         // This is required in some regions (e.g. Taiwan).
735         if (isEmergencyNumber) {
736             final Phone phone = getPhoneForAccount(request.getAccountHandle(), false,
737                     handle.getSchemeSpecificPart());
738             // We only do the conversion if the phone is not in service. The un-converted
739             // emergency numbers will go to the correct destination when the phone is in-service,
740             // so they will only need the special emergency call setup when the phone is out of
741             // service.
742             if (phone == null || phone.getServiceState().getState()
743                     != ServiceState.STATE_IN_SERVICE) {
744                 String convertedNumber = mPhoneNumberUtilsProxy.convertToEmergencyNumber(this,
745                         number);
746                 if (!TextUtils.equals(convertedNumber, number)) {
747                     Log.i(this, "onCreateOutgoingConnection, converted to emergency number");
748                     number = convertedNumber;
749                     handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
750                 }
751             }
752         }
753         final String numberToDial = number;
754 
755         final boolean isAirplaneModeOn = mDeviceState.isAirplaneModeOn(this);
756 
757         boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn))
758                 || isRadioPowerDownOnBluetooth();
759 
760         // Get the right phone object from the account data passed in.
761         final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
762                 /* Note: when not an emergency, handle can be null for unknown callers */
763                 handle == null ? null : handle.getSchemeSpecificPart());
764 
765         if (needToTurnOnRadio) {
766             final Uri resultHandle = handle;
767             final int originalPhoneType = phone.getPhoneType();
768             final Connection resultConnection = getTelephonyConnection(request, numberToDial,
769                     isEmergencyNumber, resultHandle, phone);
770             if (mRadioOnHelper == null) {
771                 mRadioOnHelper = new RadioOnHelper(this);
772             }
773 
774             if (isEmergencyNumber) {
775                 mIsEmergencyCallPending = true;
776             }
777             mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
778                 @Override
779                 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
780                     handleOnComplete(isRadioReady, isEmergencyNumber, resultConnection, request,
781                             numberToDial, resultHandle, originalPhoneType, phone);
782                 }
783 
784                 @Override
785                 public boolean isOkToCall(Phone phone, int serviceState) {
786                     // HAL 1.4 introduced a new variant of dial for emergency calls, which includes
787                     // an isTesting parameter. For HAL 1.4+, do not wait for IN_SERVICE, this will
788                     // be handled at the RIL/vendor level by emergencyDial(...).
789                     boolean waitForInServiceToDialEmergency = isTestEmergencyNumber
790                             && phone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_4);
791                     if (isEmergencyNumber && !waitForInServiceToDialEmergency) {
792                         // We currently only look to make sure that the radio is on before dialing.
793                         // We should be able to make emergency calls at any time after the radio has
794                         // been powered on and isn't in the UNAVAILABLE state, even if it is
795                         // reporting the OUT_OF_SERVICE state.
796                         return (phone.getState() == PhoneConstants.State.OFFHOOK)
797                             || phone.getServiceStateTracker().isRadioOn();
798                     } else {
799                         // Wait until we are in service and ready to make calls. This can happen
800                         // when we power down the radio on bluetooth to save power on watches or if
801                         // it is a test emergency number and we have to wait for the device to move
802                         // IN_SERVICE before the call can take place over normal routing.
803                         return (phone.getState() == PhoneConstants.State.OFFHOOK)
804                                 // Do not wait for voice in service on opportunistic SIMs.
805                                 || SubscriptionController.getInstance().isOpportunistic(
806                                         phone.getSubId())
807                                 || serviceState == ServiceState.STATE_IN_SERVICE;
808                     }
809                 }
810             }, isEmergencyNumber && !isTestEmergencyNumber, phone, isTestEmergencyNumber);
811             // Return the still unconnected GsmConnection and wait for the Radios to boot before
812             // connecting it to the underlying Phone.
813             return resultConnection;
814         } else {
815             if (!canAddCall() && !isEmergencyNumber) {
816                 Log.d(this, "onCreateOutgoingConnection, cannot add call .");
817                 return Connection.createFailedConnection(
818                         new DisconnectCause(DisconnectCause.ERROR,
819                                 getApplicationContext().getText(
820                                         R.string.incall_error_cannot_add_call),
821                                 getApplicationContext().getText(
822                                         R.string.incall_error_cannot_add_call),
823                                 "Add call restricted due to ongoing video call"));
824             }
825 
826             if (!isEmergencyNumber) {
827                 final Connection resultConnection = getTelephonyConnection(request, numberToDial,
828                         false, handle, phone);
829                 if (isAdhocConference) {
830                     if (resultConnection instanceof TelephonyConnection) {
831                         TelephonyConnection conn = (TelephonyConnection)resultConnection;
832                         conn.setParticipants(request.getParticipants());
833                     }
834                     return resultConnection;
835                 } else {
836                     return placeOutgoingConnection(request, resultConnection, phone);
837                 }
838             } else {
839                 final Connection resultConnection = getTelephonyConnection(request, numberToDial,
840                         true, handle, phone);
841                 delayDialForDdsSwitch(phone, (result) -> {
842                     Log.i(this, "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result);
843                         placeOutgoingConnection(request, resultConnection, phone);
844                 });
845                 return resultConnection;
846             }
847         }
848     }
849 
placeOutgoingConnection(ConnectionRequest request, Connection resultConnection, Phone phone)850     private Connection placeOutgoingConnection(ConnectionRequest request,
851             Connection resultConnection, Phone phone) {
852         // If there was a failure, the resulting connection will not be a TelephonyConnection,
853         // so don't place the call!
854         if (resultConnection instanceof TelephonyConnection) {
855             if (request.getExtras() != null && request.getExtras().getBoolean(
856                     TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) {
857                 ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true);
858             }
859             placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
860         }
861         return resultConnection;
862     }
863 
isEmergencyNumberTestNumber(String number)864     private boolean isEmergencyNumberTestNumber(String number) {
865         number = PhoneNumberUtils.stripSeparators(number);
866         Map<Integer, List<EmergencyNumber>> list =
867                 mTelephonyManagerProxy.getCurrentEmergencyNumberList();
868         // Do not worry about which subscription the test emergency call is on yet, only detect that
869         // it is an emergency.
870         for (Integer sub : list.keySet()) {
871             for (EmergencyNumber eNumber : list.get(sub)) {
872                 if (number.equals(eNumber.getNumber())
873                         && eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)) {
874                     Log.i(this, "isEmergencyNumberTestNumber: " + number + " has been detected as "
875                             + "a test emergency number.,");
876                     return true;
877                 }
878             }
879         }
880         return false;
881     }
882 
883     /**
884      * @return whether radio has recently been turned on for emergency call but hasn't actually
885      * dialed the call yet.
886      */
isEmergencyCallPending()887     public boolean isEmergencyCallPending() {
888         return mIsEmergencyCallPending;
889     }
890 
891     /**
892      * Whether the cellular radio is power off because the device is on Bluetooth.
893      */
isRadioPowerDownOnBluetooth()894     private boolean isRadioPowerDownOnBluetooth() {
895         final boolean allowed = mDeviceState.isRadioPowerDownAllowedOnBluetooth(this);
896         final int cellOn = mDeviceState.getCellOnStatus(this);
897         return (allowed && cellOn == PhoneConstants.CELL_ON_FLAG && !isRadioOn());
898     }
899 
900     /**
901      * Handle the onComplete callback of RadioOnStateListener.
902      */
handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, Connection originalConnection, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, Phone phone)903     private void handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber,
904             Connection originalConnection, ConnectionRequest request, String numberToDial,
905             Uri handle, int originalPhoneType, Phone phone) {
906         // Make sure the Call has not already been canceled by the user.
907         if (originalConnection.getState() == Connection.STATE_DISCONNECTED) {
908             Log.i(this, "Call disconnected before the outgoing call was placed. Skipping call "
909                     + "placement.");
910             if (isEmergencyNumber) {
911                 // If call is already canceled by the user, notify modem to exit emergency call
912                 // mode by sending radio on with forEmergencyCall=false.
913                 for (Phone curPhone : mPhoneFactoryProxy.getPhones()) {
914                     curPhone.setRadioPower(true, false, false, true);
915                 }
916                 mIsEmergencyCallPending = false;
917             }
918             return;
919         }
920         if (isRadioReady) {
921             if (!isEmergencyNumber) {
922                 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial,
923                         handle, originalPhoneType, false);
924             } else {
925                 delayDialForDdsSwitch(phone, result -> {
926                     Log.i(this, "handleOnComplete - delayDialForDdsSwitch "
927                             + "result = " + result);
928                     adjustAndPlaceOutgoingConnection(phone, originalConnection, request,
929                             numberToDial, handle, originalPhoneType, true);
930                     mIsEmergencyCallPending = false;
931                 });
932             }
933         } else {
934             Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
935             closeOrDestroyConnection(originalConnection,
936                     mDisconnectCauseFactory.toTelecomDisconnectCause(
937                             android.telephony.DisconnectCause.POWER_OFF,
938                             "Failed to turn on radio."));
939             mIsEmergencyCallPending = false;
940         }
941     }
942 
adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, boolean isEmergencyNumber)943     private void adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate,
944             ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType,
945             boolean isEmergencyNumber) {
946         // If the PhoneType of the Phone being used is different than the Default Phone, then we
947         // need to create a new Connection using that PhoneType and replace it in Telecom.
948         if (phone.getPhoneType() != originalPhoneType) {
949             Connection repConnection = getTelephonyConnection(request, numberToDial,
950                     isEmergencyNumber, handle, phone);
951             // If there was a failure, the resulting connection will not be a TelephonyConnection,
952             // so don't place the call, just return!
953             if (repConnection instanceof TelephonyConnection) {
954                 placeOutgoingConnection((TelephonyConnection) repConnection, phone, request);
955             }
956             // Notify Telecom of the new Connection type.
957             // TODO: Switch out the underlying connection instead of creating a new
958             // one and causing UI Jank.
959             boolean noActiveSimCard = SubscriptionController.getInstance()
960                     .getActiveSubInfoCount(phone.getContext().getOpPackageName(),
961                             phone.getContext().getAttributionTag()) == 0;
962             // If there's no active sim card and the device is in emergency mode, use E account.
963             addExistingConnection(mPhoneUtilsProxy.makePstnPhoneAccountHandleWithPrefix(
964                     phone, "", isEmergencyNumber && noActiveSimCard), repConnection);
965             // Remove the old connection from Telecom after.
966             closeOrDestroyConnection(connectionToEvaluate,
967                     mDisconnectCauseFactory.toTelecomDisconnectCause(
968                             android.telephony.DisconnectCause.OUTGOING_CANCELED,
969                             "Reconnecting outgoing Emergency Call.",
970                             phone.getPhoneId()));
971         } else {
972             placeOutgoingConnection((TelephonyConnection) connectionToEvaluate, phone, request);
973         }
974     }
975 
976     /**
977      * @return {@code true} if any other call is disabling the ability to add calls, {@code false}
978      *      otherwise.
979      */
canAddCall()980     private boolean canAddCall() {
981         Collection<Connection> connections = getAllConnections();
982         for (Connection connection : connections) {
983             if (connection.getExtras() != null &&
984                     connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) {
985                 return false;
986             }
987         }
988         return true;
989     }
990 
getTelephonyConnection(final ConnectionRequest request, final String number, boolean isEmergencyNumber, final Uri handle, Phone phone)991     private Connection getTelephonyConnection(final ConnectionRequest request, final String number,
992             boolean isEmergencyNumber, final Uri handle, Phone phone) {
993 
994         if (phone == null) {
995             final Context context = getApplicationContext();
996             if (mDeviceState.shouldCheckSimStateBeforeOutgoingCall(this)) {
997                 // Check SIM card state before the outgoing call.
998                 // Start the SIM unlock activity if PIN_REQUIRED.
999                 final Phone defaultPhone = mPhoneFactoryProxy.getDefaultPhone();
1000                 final IccCard icc = defaultPhone.getIccCard();
1001                 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN;
1002                 if (icc != null) {
1003                     simState = icc.getState();
1004                 }
1005                 if (simState == IccCardConstants.State.PIN_REQUIRED) {
1006                     final String simUnlockUiPackage = context.getResources().getString(
1007                             R.string.config_simUnlockUiPackage);
1008                     final String simUnlockUiClass = context.getResources().getString(
1009                             R.string.config_simUnlockUiClass);
1010                     if (simUnlockUiPackage != null && simUnlockUiClass != null) {
1011                         Intent simUnlockIntent = new Intent().setComponent(new ComponentName(
1012                                 simUnlockUiPackage, simUnlockUiClass));
1013                         simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1014                         try {
1015                             context.startActivity(simUnlockIntent);
1016                         } catch (ActivityNotFoundException exception) {
1017                             Log.e(this, exception, "Unable to find SIM unlock UI activity.");
1018                         }
1019                     }
1020                     return Connection.createFailedConnection(
1021                             mDisconnectCauseFactory.toTelecomDisconnectCause(
1022                                     android.telephony.DisconnectCause.OUT_OF_SERVICE,
1023                                     "SIM_STATE_PIN_REQUIRED"));
1024                 }
1025             }
1026 
1027             Log.d(this, "onCreateOutgoingConnection, phone is null");
1028             return Connection.createFailedConnection(
1029                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1030                             android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null"));
1031         }
1032 
1033         // Check both voice & data RAT to enable normal CS call,
1034         // when voice RAT is OOS but Data RAT is present.
1035         int state = phone.getServiceState().getState();
1036         if (state == ServiceState.STATE_OUT_OF_SERVICE) {
1037             int dataNetType = phone.getServiceState().getDataNetworkType();
1038             if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
1039                     dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA ||
1040                     dataNetType == TelephonyManager.NETWORK_TYPE_NR) {
1041                 state = phone.getServiceState().getDataRegistrationState();
1042             }
1043         }
1044 
1045         // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if
1046         // carrier configuration specifies that we cannot make non-emergency calls in ECM mode.
1047         if (!isEmergencyNumber && phone.isInEcm()) {
1048             boolean allowNonEmergencyCalls = true;
1049             CarrierConfigManager cfgManager = (CarrierConfigManager)
1050                     phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1051             if (cfgManager != null) {
1052                 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId())
1053                         .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL);
1054             }
1055 
1056             if (!allowNonEmergencyCalls) {
1057                 return Connection.createFailedConnection(
1058                         mDisconnectCauseFactory.toTelecomDisconnectCause(
1059                                 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY,
1060                                 "Cannot make non-emergency call in ECM mode.",
1061                                 phone.getPhoneId()));
1062             }
1063         }
1064 
1065         if (!isEmergencyNumber) {
1066             switch (state) {
1067                 case ServiceState.STATE_IN_SERVICE:
1068                 case ServiceState.STATE_EMERGENCY_ONLY:
1069                     break;
1070                 case ServiceState.STATE_OUT_OF_SERVICE:
1071                     if (phone.isUtEnabled() && number.endsWith("#")) {
1072                         Log.d(this, "onCreateOutgoingConnection dial for UT");
1073                         break;
1074                     } else {
1075                         return Connection.createFailedConnection(
1076                                 mDisconnectCauseFactory.toTelecomDisconnectCause(
1077                                         android.telephony.DisconnectCause.OUT_OF_SERVICE,
1078                                         "ServiceState.STATE_OUT_OF_SERVICE",
1079                                         phone.getPhoneId()));
1080                     }
1081                 case ServiceState.STATE_POWER_OFF:
1082                     // Don't disconnect if radio is power off because the device is on Bluetooth.
1083                     if (isRadioPowerDownOnBluetooth()) {
1084                         break;
1085                     }
1086                     return Connection.createFailedConnection(
1087                             mDisconnectCauseFactory.toTelecomDisconnectCause(
1088                                     android.telephony.DisconnectCause.POWER_OFF,
1089                                     "ServiceState.STATE_POWER_OFF",
1090                                     phone.getPhoneId()));
1091                 default:
1092                     Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state);
1093                     return Connection.createFailedConnection(
1094                             mDisconnectCauseFactory.toTelecomDisconnectCause(
1095                                     android.telephony.DisconnectCause.OUTGOING_FAILURE,
1096                                     "Unknown service state " + state,
1097                                     phone.getPhoneId()));
1098             }
1099         }
1100 
1101         final boolean isTtyModeEnabled = mDeviceState.isTtyModeEnabled(this);
1102         if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled
1103                 && !isEmergencyNumber) {
1104             return Connection.createFailedConnection(mDisconnectCauseFactory.toTelecomDisconnectCause(
1105                     android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED,
1106                     null, phone.getPhoneId()));
1107         }
1108 
1109         // Check for additional limits on CDMA phones.
1110         final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone);
1111         if (failedConnection != null) {
1112             return failedConnection;
1113         }
1114 
1115         // Check roaming status to see if we should block custom call forwarding codes
1116         if (blockCallForwardingNumberWhileRoaming(phone, number)) {
1117             return Connection.createFailedConnection(
1118                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1119                             android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING,
1120                             "Call forwarding while roaming",
1121                             phone.getPhoneId()));
1122         }
1123 
1124         PhoneAccountHandle accountHandle = adjustAccountHandle(phone, request.getAccountHandle());
1125         final TelephonyConnection connection =
1126                 createConnectionFor(phone, null, true /* isOutgoing */, accountHandle,
1127                         request.getTelecomCallId(), request.isAdhocConferenceCall());
1128         if (connection == null) {
1129             return Connection.createFailedConnection(
1130                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1131                             android.telephony.DisconnectCause.OUTGOING_FAILURE,
1132                             "Invalid phone type",
1133                             phone.getPhoneId()));
1134         }
1135         connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
1136         connection.setTelephonyConnectionInitializing();
1137         connection.setTelephonyVideoState(request.getVideoState());
1138         connection.setRttTextStream(request.getRttTextStream());
1139         connection.setTtyEnabled(isTtyModeEnabled);
1140         connection.setIsAdhocConferenceCall(request.isAdhocConferenceCall());
1141         connection.setParticipants(request.getParticipants());
1142         return connection;
1143     }
1144 
1145     @Override
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1146     public Connection onCreateIncomingConnection(
1147             PhoneAccountHandle connectionManagerPhoneAccount,
1148             ConnectionRequest request) {
1149         Log.i(this, "onCreateIncomingConnection, request: " + request);
1150         // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM),
1151         // make sure the PhoneAccount lookup retrieves the default Emergency Phone.
1152         PhoneAccountHandle accountHandle = request.getAccountHandle();
1153         boolean isEmergency = false;
1154         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
1155                 accountHandle.getId())) {
1156             Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " +
1157                     "Treat as an Emergency Call.");
1158             isEmergency = true;
1159         }
1160         Phone phone = getPhoneForAccount(accountHandle, isEmergency,
1161                 /* Note: when not an emergency, handle can be null for unknown callers */
1162                 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart());
1163         if (phone == null) {
1164             return Connection.createFailedConnection(
1165                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1166                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
1167                             "Phone is null"));
1168         }
1169 
1170         Bundle extras = request.getExtras();
1171         String disconnectMessage = null;
1172         if (extras.containsKey(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE)) {
1173             disconnectMessage = extras.getString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE);
1174             Log.i(this, "onCreateIncomingConnection Disconnect message " + disconnectMessage);
1175         }
1176 
1177         Call call = phone.getRingingCall();
1178         if (!call.getState().isRinging()
1179                 || (disconnectMessage != null
1180                 && disconnectMessage.equals(TelecomManager.CALL_AUTO_DISCONNECT_MESSAGE_STRING))) {
1181             Log.i(this, "onCreateIncomingConnection, no ringing call");
1182             Connection connection = Connection.createFailedConnection(
1183                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1184                             android.telephony.DisconnectCause.INCOMING_MISSED,
1185                             "Found no ringing call",
1186                             phone.getPhoneId()));
1187 
1188             long time = extras.getLong(TelecomManager.EXTRA_CALL_CREATED_EPOCH_TIME_MILLIS);
1189             if (time != 0) {
1190                 Log.i(this, "onCreateIncomingConnection. Set connect time info.");
1191                 connection.setConnectTimeMillis(time);
1192             }
1193 
1194             Uri address = extras.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS);
1195             if (address != null) {
1196                 Log.i(this, "onCreateIncomingConnection. Set caller id info.");
1197                 connection.setAddress(address, TelecomManager.PRESENTATION_ALLOWED);
1198             }
1199 
1200             return connection;
1201         }
1202 
1203         // If there are multiple Connections tracked in a call, grab the latest, since it is most
1204         // likely to be the incoming call.
1205         com.android.internal.telephony.Connection originalConnection = call.getLatestConnection();
1206         if (isOriginalConnectionKnown(originalConnection)) {
1207             Log.i(this, "onCreateIncomingConnection, original connection already registered");
1208             return Connection.createCanceledConnection();
1209         }
1210 
1211         TelephonyConnection connection =
1212                 createConnectionFor(phone, originalConnection, false /* isOutgoing */,
1213                         request.getAccountHandle(), request.getTelecomCallId(),
1214                         request.isAdhocConferenceCall());
1215 
1216         handleIncomingRtt(request, originalConnection);
1217         if (connection == null) {
1218             return Connection.createCanceledConnection();
1219         } else {
1220             // Add extra to call if answering this incoming call would cause an in progress call on
1221             // another subscription to be disconnected.
1222             maybeIndicateAnsweringWillDisconnect(connection, request.getAccountHandle());
1223 
1224             connection.setTtyEnabled(mDeviceState.isTtyModeEnabled(getApplicationContext()));
1225             return connection;
1226         }
1227     }
1228 
handleIncomingRtt(ConnectionRequest request, com.android.internal.telephony.Connection originalConnection)1229     private void handleIncomingRtt(ConnectionRequest request,
1230             com.android.internal.telephony.Connection originalConnection) {
1231         if (originalConnection == null
1232                 || originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) {
1233             if (request.isRequestingRtt()) {
1234                 Log.w(this, "Requesting RTT on non-IMS call, ignoring");
1235             }
1236             return;
1237         }
1238 
1239         ImsPhoneConnection imsOriginalConnection = (ImsPhoneConnection) originalConnection;
1240         if (!request.isRequestingRtt()) {
1241             if (imsOriginalConnection.isRttEnabledForCall()) {
1242                 Log.w(this, "Incoming call requested RTT but we did not get a RttTextStream");
1243             }
1244             return;
1245         }
1246 
1247         Log.i(this, "Setting RTT stream on ImsPhoneConnection in case we need it later");
1248         imsOriginalConnection.setCurrentRttTextStream(request.getRttTextStream());
1249 
1250         if (!imsOriginalConnection.isRttEnabledForCall()) {
1251             if (request.isRequestingRtt()) {
1252                 Log.w(this, "Incoming call processed as RTT but did not come in as one. Ignoring");
1253             }
1254             return;
1255         }
1256 
1257         Log.i(this, "Setting the call to be answered with RTT on.");
1258         imsOriginalConnection.getImsCall().setAnswerWithRtt();
1259     }
1260 
1261     /**
1262      * Called by the {@link ConnectionService} when a newly created {@link Connection} has been
1263      * added to the {@link ConnectionService} and sent to Telecom.  Here it is safe to send
1264      * connection events.
1265      *
1266      * @param connection the {@link Connection}.
1267      */
1268     @Override
onCreateConnectionComplete(Connection connection)1269     public void onCreateConnectionComplete(Connection connection) {
1270         if (connection instanceof TelephonyConnection) {
1271             TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
1272             maybeSendInternationalCallEvent(telephonyConnection);
1273         }
1274     }
1275 
1276     @Override
onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1277     public void onCreateIncomingConnectionFailed(PhoneAccountHandle connectionManagerPhoneAccount,
1278             ConnectionRequest request) {
1279         Log.i(this, "onCreateIncomingConnectionFailed, request: " + request);
1280         // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM),
1281         // make sure the PhoneAccount lookup retrieves the default Emergency Phone.
1282         PhoneAccountHandle accountHandle = request.getAccountHandle();
1283         boolean isEmergency = false;
1284         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
1285                 accountHandle.getId())) {
1286             Log.w(this, "onCreateIncomingConnectionFailed:Emergency call failed... ");
1287             isEmergency = true;
1288         }
1289         Phone phone = getPhoneForAccount(accountHandle, isEmergency,
1290                 /* Note: when not an emergency, handle can be null for unknown callers */
1291                 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart());
1292         if (phone == null) {
1293             Log.w(this, "onCreateIncomingConnectionFailed: can not find corresponding phone.");
1294             return;
1295         }
1296 
1297         Call call = phone.getRingingCall();
1298         if (!call.getState().isRinging()) {
1299             Log.w(this, "onCreateIncomingConnectionFailed, no ringing call found for failed call");
1300             return;
1301         }
1302 
1303         com.android.internal.telephony.Connection originalConnection =
1304                 call.getState() == Call.State.WAITING
1305                         ? call.getLatestConnection() : call.getEarliestConnection();
1306         TelephonyConnection knownConnection =
1307                 getConnectionForOriginalConnection(originalConnection);
1308         if (knownConnection != null) {
1309             Log.w(this, "onCreateIncomingConnectionFailed, original connection already registered."
1310                     + " Hanging it up.");
1311             knownConnection.onAbort();
1312             return;
1313         }
1314 
1315         TelephonyConnection connection =
1316                 createConnectionFor(phone, originalConnection, false /* isOutgoing */,
1317                         request.getAccountHandle(), request.getTelecomCallId());
1318         if (connection == null) {
1319             Log.w(this, "onCreateIncomingConnectionFailed, TelephonyConnection created as null, "
1320                     + "ignoring.");
1321             return;
1322         }
1323 
1324         // We have to do all of this work because in some cases, hanging up the call maps to
1325         // different underlying signaling (CDMA), which is already encapsulated in
1326         // TelephonyConnection.
1327         connection.onReject();
1328         connection.close();
1329     }
1330 
1331     /**
1332      * Called by the {@link ConnectionService} when a newly created {@link Conference} has been
1333      * added to the {@link ConnectionService} and sent to Telecom.  Here it is safe to send
1334      * connection events.
1335      *
1336      * @param conference the {@link Conference}.
1337      */
1338     @Override
onCreateConferenceComplete(Conference conference)1339     public void onCreateConferenceComplete(Conference conference) {
1340         if (conference instanceof ImsConference) {
1341             ImsConference imsConference = (ImsConference)conference;
1342             TelephonyConnection telephonyConnection =
1343                     (TelephonyConnection)(imsConference.getConferenceHost());
1344             maybeSendInternationalCallEvent(telephonyConnection);
1345         }
1346     }
1347 
onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1348     public void onCreateIncomingConferenceFailed(PhoneAccountHandle connectionManagerPhoneAccount,
1349             ConnectionRequest request) {
1350         Log.i(this, "onCreateIncomingConferenceFailed, request: " + request);
1351         onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request);
1352     }
1353 
1354     @Override
triggerConferenceRecalculate()1355     public void triggerConferenceRecalculate() {
1356         if (mTelephonyConferenceController.shouldRecalculate()) {
1357             mTelephonyConferenceController.recalculate();
1358         }
1359     }
1360 
1361     @Override
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)1362     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
1363             ConnectionRequest request) {
1364         Log.i(this, "onCreateUnknownConnection, request: " + request);
1365         // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's
1366         // Emergency PhoneAccount
1367         PhoneAccountHandle accountHandle = request.getAccountHandle();
1368         boolean isEmergency = false;
1369         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
1370                 accountHandle.getId())) {
1371             Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " +
1372                     "Treat as an Emergency Call.");
1373             isEmergency = true;
1374         }
1375         Phone phone = getPhoneForAccount(accountHandle, isEmergency,
1376                 /* Note: when not an emergency, handle can be null for unknown callers */
1377                 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart());
1378         if (phone == null) {
1379             return Connection.createFailedConnection(
1380                     mDisconnectCauseFactory.toTelecomDisconnectCause(
1381                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
1382                             "Phone is null"));
1383         }
1384         Bundle extras = request.getExtras();
1385 
1386         final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>();
1387 
1388         // Handle the case where an unknown connection has an IMS external call ID specified; we can
1389         // skip the rest of the guesswork and just grad that unknown call now.
1390         if (phone.getImsPhone() != null && extras != null &&
1391                 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) {
1392 
1393             ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
1394             ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker();
1395             int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID,
1396                     -1);
1397 
1398             if (externalCallTracker != null) {
1399                 com.android.internal.telephony.Connection connection =
1400                         externalCallTracker.getConnectionById(externalCallId);
1401 
1402                 if (connection != null) {
1403                     allConnections.add(connection);
1404                 }
1405             }
1406         }
1407 
1408         if (allConnections.isEmpty()) {
1409             final Call ringingCall = phone.getRingingCall();
1410             if (ringingCall.hasConnections()) {
1411                 allConnections.addAll(ringingCall.getConnections());
1412             }
1413             final Call foregroundCall = phone.getForegroundCall();
1414             if ((foregroundCall.getState() != Call.State.DISCONNECTED)
1415                     && (foregroundCall.hasConnections())) {
1416                 allConnections.addAll(foregroundCall.getConnections());
1417             }
1418             if (phone.getImsPhone() != null) {
1419                 final Call imsFgCall = phone.getImsPhone().getForegroundCall();
1420                 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall
1421                         .hasConnections()) {
1422                     allConnections.addAll(imsFgCall.getConnections());
1423                 }
1424             }
1425             final Call backgroundCall = phone.getBackgroundCall();
1426             if (backgroundCall.hasConnections()) {
1427                 allConnections.addAll(phone.getBackgroundCall().getConnections());
1428             }
1429         }
1430 
1431         com.android.internal.telephony.Connection unknownConnection = null;
1432         for (com.android.internal.telephony.Connection telephonyConnection : allConnections) {
1433             if (!isOriginalConnectionKnown(telephonyConnection)) {
1434                 unknownConnection = telephonyConnection;
1435                 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection);
1436                 break;
1437             }
1438         }
1439 
1440         if (unknownConnection == null) {
1441             Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection.");
1442             return Connection.createCanceledConnection();
1443         }
1444 
1445         // We should rely on the originalConnection to get the video state.  The request coming
1446         // from Telecom does not know the video state of the unknown call.
1447         int videoState = unknownConnection != null ? unknownConnection.getVideoState() :
1448                 VideoProfile.STATE_AUDIO_ONLY;
1449 
1450         TelephonyConnection connection =
1451                 createConnectionFor(phone, unknownConnection,
1452                         !unknownConnection.isIncoming() /* isOutgoing */,
1453                         request.getAccountHandle(), request.getTelecomCallId()
1454                 );
1455 
1456         if (connection == null) {
1457             return Connection.createCanceledConnection();
1458         } else {
1459             connection.updateState();
1460             return connection;
1461         }
1462     }
1463 
1464     /**
1465      * Conferences two connections.
1466      *
1467      * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has
1468      * a limitation in that it can only specify conferenceables which are instances of
1469      * {@link android.telecom.RemoteConnection}.  In the case of an {@link ImsConference}, the
1470      * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge
1471      * a {@link Conference} and a {@link Connection}.  As a result when, merging a
1472      * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference}
1473      * require merging a {@link ConferenceParticipantConnection} which is a child of the
1474      * {@link Conference} with a {@link TelephonyConnection}.  The
1475      * {@link ConferenceParticipantConnection} class does not have the capability to initiate a
1476      * conference merge, so we need to call
1477      * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or
1478      * {@code connection2}, one of which is an instance of {@link TelephonyConnection}.
1479      *
1480      * @param connection1 A connection to merge into a conference call.
1481      * @param connection2 A connection to merge into a conference call.
1482      */
1483     @Override
onConference(Connection connection1, Connection connection2)1484     public void onConference(Connection connection1, Connection connection2) {
1485         if (connection1 instanceof TelephonyConnection) {
1486             ((TelephonyConnection) connection1).performConference(connection2);
1487         } else if (connection2 instanceof TelephonyConnection) {
1488             ((TelephonyConnection) connection2).performConference(connection1);
1489         } else {
1490             Log.w(this, "onConference - cannot merge connections " +
1491                     "Connection1: %s, Connection2: %2", connection1, connection2);
1492         }
1493     }
1494 
1495     @Override
onConnectionAdded(Connection connection)1496     public void onConnectionAdded(Connection connection) {
1497         if (connection instanceof Holdable && !isExternalConnection(connection)) {
1498             mHoldTracker.addHoldable(
1499                     connection.getPhoneAccountHandle(), (Holdable) connection);
1500         }
1501     }
1502 
1503     @Override
onConnectionRemoved(Connection connection)1504     public void onConnectionRemoved(Connection connection) {
1505         if (connection instanceof Holdable && !isExternalConnection(connection)) {
1506             mHoldTracker.removeHoldable(connection.getPhoneAccountHandle(), (Holdable) connection);
1507         }
1508     }
1509 
1510     @Override
onConferenceAdded(Conference conference)1511     public void onConferenceAdded(Conference conference) {
1512         if (conference instanceof Holdable) {
1513             mHoldTracker.addHoldable(conference.getPhoneAccountHandle(), (Holdable) conference);
1514         }
1515     }
1516 
1517     @Override
onConferenceRemoved(Conference conference)1518     public void onConferenceRemoved(Conference conference) {
1519         if (conference instanceof Holdable) {
1520             mHoldTracker.removeHoldable(conference.getPhoneAccountHandle(), (Holdable) conference);
1521         }
1522     }
1523 
isExternalConnection(Connection connection)1524     private boolean isExternalConnection(Connection connection) {
1525         return (connection.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL)
1526                 == Connection.PROPERTY_IS_EXTERNAL_CALL;
1527     }
1528 
blockCallForwardingNumberWhileRoaming(Phone phone, String number)1529     private boolean blockCallForwardingNumberWhileRoaming(Phone phone, String number) {
1530         if (phone == null || TextUtils.isEmpty(number) || !phone.getServiceState().getRoaming()) {
1531             return false;
1532         }
1533         boolean allowPrefixIms = true;
1534         String[] blockPrefixes = null;
1535         CarrierConfigManager cfgManager = (CarrierConfigManager)
1536                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1537         if (cfgManager != null) {
1538             allowPrefixIms = cfgManager.getConfigForSubId(phone.getSubId()).getBoolean(
1539                     CarrierConfigManager.KEY_SUPPORT_IMS_CALL_FORWARDING_WHILE_ROAMING_BOOL,
1540                     true);
1541             if (allowPrefixIms && useImsForAudioOnlyCall(phone)) {
1542                 return false;
1543             }
1544             blockPrefixes = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray(
1545                     CarrierConfigManager.KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY);
1546         }
1547 
1548         if (blockPrefixes != null) {
1549             for (String prefix : blockPrefixes) {
1550                 if (number.startsWith(prefix)) {
1551                     return true;
1552                 }
1553             }
1554         }
1555         return false;
1556     }
1557 
useImsForAudioOnlyCall(Phone phone)1558     private boolean useImsForAudioOnlyCall(Phone phone) {
1559         Phone imsPhone = phone.getImsPhone();
1560 
1561         return imsPhone != null
1562                 && (imsPhone.isVoiceOverCellularImsEnabled() || imsPhone.isWifiCallingEnabled())
1563                 && (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE);
1564     }
1565 
isRadioOn()1566     private boolean isRadioOn() {
1567         boolean result = false;
1568         for (Phone phone : mPhoneFactoryProxy.getPhones()) {
1569             result |= phone.isRadioOn();
1570         }
1571         return result;
1572     }
1573 
makeCachedConnectionPhonePair( TelephonyConnection c)1574     private Pair<WeakReference<TelephonyConnection>, Queue<Phone>> makeCachedConnectionPhonePair(
1575             TelephonyConnection c) {
1576         Queue<Phone> phones = new LinkedList<>(Arrays.asList(mPhoneFactoryProxy.getPhones()));
1577         return new Pair<>(new WeakReference<>(c), phones);
1578     }
1579 
1580     // Update the mEmergencyRetryCache by removing the Phone used to call the last failed emergency
1581     // number and then moving it to the back of the queue if it is not a permanent failure cause
1582     // from the modem.
updateCachedConnectionPhonePair(TelephonyConnection c, boolean isPermanentFailure)1583     private void updateCachedConnectionPhonePair(TelephonyConnection c,
1584             boolean isPermanentFailure) {
1585         // No cache exists, create a new one.
1586         if (mEmergencyRetryCache == null) {
1587             Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache");
1588             mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
1589         // Cache is stale, create a new one with the new TelephonyConnection.
1590         } else if (mEmergencyRetryCache.first.get() != c) {
1591             Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating.");
1592             mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
1593         }
1594 
1595         Queue<Phone> cachedPhones = mEmergencyRetryCache.second;
1596         // Need to refer default phone considering ImsPhone because
1597         // cachedPhones is a list that contains default phones.
1598         Phone phoneUsed = c.getPhone().getDefaultPhone();
1599         if (phoneUsed == null) {
1600             return;
1601         }
1602         // Remove phone used from the list, but for temporary fail cause, it will be added
1603         // back to list further in this method. However in case of permanent failure, the
1604         // phone shouldn't be reused, hence it will not be added back again.
1605         cachedPhones.remove(phoneUsed);
1606         Log.i(this, "updateCachedConnectionPhonePair, isPermanentFailure:" + isPermanentFailure);
1607         if (!isPermanentFailure) {
1608             // In case of temporary failure, add the phone back, this will result adding it
1609             // to tail of list mEmergencyRetryCache.second, giving other phone more
1610             // priority and that is what we want.
1611             cachedPhones.offer(phoneUsed);
1612         }
1613     }
1614 
1615     /**
1616      * Updates a cache containing all of the slots that are available for redial at any point.
1617      *
1618      * - If a Connection returns with the disconnect cause EMERGENCY_TEMP_FAILURE, keep that phone
1619      * in the cache, but move it to the lowest priority in the list. Then, place the emergency call
1620      * on the next phone in the list.
1621      * - If a Connection returns with the disconnect cause EMERGENCY_PERM_FAILURE, remove that phone
1622      * from the cache and pull another phone from the cache to place the emergency call.
1623      *
1624      * This will continue until there are no more slots to dial on.
1625      */
1626     @VisibleForTesting
retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure)1627     public void retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure) {
1628         int phoneId = (c.getPhone() == null) ? -1 : c.getPhone().getPhoneId();
1629         updateCachedConnectionPhonePair(c, isPermanentFailure);
1630         // Pull next phone to use from the cache or null if it is empty
1631         Phone newPhoneToUse = (mEmergencyRetryCache.second != null)
1632                 ? mEmergencyRetryCache.second.peek() : null;
1633         if (newPhoneToUse != null) {
1634             int videoState = c.getVideoState();
1635             Bundle connExtras = c.getExtras();
1636             Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse);
1637             c.clearOriginalConnection();
1638             if (phoneId != newPhoneToUse.getPhoneId()) updatePhoneAccount(c, newPhoneToUse);
1639             placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
1640         } else {
1641             // We have run out of Phones to use. Disconnect the call and destroy the connection.
1642             Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting.");
1643             closeOrDestroyConnection(c, new DisconnectCause(DisconnectCause.ERROR));
1644         }
1645     }
1646 
updatePhoneAccount(TelephonyConnection connection, Phone phone)1647     private void updatePhoneAccount(TelephonyConnection connection, Phone phone) {
1648         PhoneAccountHandle pHandle = mPhoneUtilsProxy.makePstnPhoneAccountHandle(phone);
1649         // For ECall handling on MSIM, until the request reaches here (i.e PhoneApp), we don't know
1650         // on which phone account ECall can be placed. After deciding, we should notify Telecom of
1651         // the change so that the proper PhoneAccount can be displayed.
1652         Log.i(this, "updatePhoneAccount setPhoneAccountHandle, account = " + pHandle);
1653         connection.setPhoneAccountHandle(pHandle);
1654     }
1655 
placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)1656     private void placeOutgoingConnection(
1657             TelephonyConnection connection, Phone phone, ConnectionRequest request) {
1658         placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras());
1659     }
1660 
placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras)1661     private void placeOutgoingConnection(
1662             TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
1663 
1664         String number = (connection.getAddress() != null)
1665                 ? connection.getAddress().getSchemeSpecificPart()
1666                 : "";
1667 
1668         if (showDataDialog(phone, number)) {
1669             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
1670                         android.telephony.DisconnectCause.DIALED_MMI, "UT is not available"));
1671             return;
1672         }
1673 
1674         if (extras != null && extras.containsKey(TelecomManager.EXTRA_OUTGOING_PICTURE)) {
1675             ParcelUuid uuid = extras.getParcelable(TelecomManager.EXTRA_OUTGOING_PICTURE);
1676             CallComposerPictureManager.getInstance(phone.getContext(), phone.getSubId())
1677                     .storeUploadedPictureToCallLog(uuid.getUuid(), (uri) -> {
1678                         if (uri != null) {
1679                             try {
1680                                 Bundle b = new Bundle();
1681                                 b.putParcelable(TelecomManager.EXTRA_PICTURE_URI, uri);
1682                                 connection.putTelephonyExtras(b);
1683                             } catch (Exception e) {
1684                                 Log.e(this, e, "Couldn't set picture extra on outgoing call");
1685                             }
1686                         }
1687                     });
1688         }
1689 
1690         final com.android.internal.telephony.Connection originalConnection;
1691         try {
1692             if (phone != null) {
1693                 EmergencyNumber emergencyNumber =
1694                         phone.getEmergencyNumberTracker().getEmergencyNumber(number);
1695                 if (emergencyNumber != null) {
1696                     if (!getAllConnections().isEmpty()) {
1697                         if (!shouldHoldForEmergencyCall(phone)) {
1698                             // If we do not support holding ongoing calls for an outgoing
1699                             // emergency call, disconnect the ongoing calls.
1700                             for (Connection c : getAllConnections()) {
1701                                 if (!c.equals(connection)
1702                                         && c.getState() != Connection.STATE_DISCONNECTED
1703                                         && c instanceof TelephonyConnection) {
1704                                     ((TelephonyConnection) c).hangup(
1705                                             android.telephony.DisconnectCause
1706                                                     .OUTGOING_EMERGENCY_CALL_PLACED);
1707                                 }
1708                             }
1709                             for (Conference c : getAllConferences()) {
1710                                 if (c.getState() != Connection.STATE_DISCONNECTED
1711                                         && c instanceof Conference) {
1712                                     ((Conference) c).onDisconnect();
1713                                 }
1714                             }
1715                         } else if (!isVideoCallHoldAllowed(phone)) {
1716                             // If we do not support holding ongoing video call for an outgoing
1717                             // emergency call, disconnect the ongoing video call.
1718                             for (Connection c : getAllConnections()) {
1719                                 if (!c.equals(connection)
1720                                         && c.getState() == Connection.STATE_ACTIVE
1721                                         && VideoProfile.isVideo(c.getVideoState())
1722                                         && c instanceof TelephonyConnection) {
1723                                     ((TelephonyConnection) c).hangup(
1724                                             android.telephony.DisconnectCause
1725                                                     .OUTGOING_EMERGENCY_CALL_PLACED);
1726                                     break;
1727                                 }
1728                             }
1729                         }
1730                     }
1731                 }
1732                 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
1733                         .setVideoState(videoState)
1734                         .setIntentExtras(extras)
1735                         .setRttTextStream(connection.getRttTextStream())
1736                         .build(),
1737                         // We need to wait until the phone has been chosen in GsmCdmaPhone to
1738                         // register for the associated TelephonyConnection call event listeners.
1739                         connection::registerForCallEvents);
1740             } else {
1741                 originalConnection = null;
1742             }
1743         } catch (CallStateException e) {
1744             Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
1745             connection.unregisterForCallEvents();
1746             handleCallStateException(e, connection, phone);
1747             return;
1748         }
1749 
1750         if (originalConnection == null) {
1751             int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
1752             // On GSM phones, null connection means that we dialed an MMI code
1753             if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM ||
1754                 phone.isUtEnabled()) {
1755                 Log.d(this, "dialed MMI code");
1756                 int subId = phone.getSubId();
1757                 Log.d(this, "subId: "+subId);
1758                 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
1759                 final Intent intent = new Intent(this, MMIDialogActivity.class);
1760                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1761                         Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1762                 if (SubscriptionManager.isValidSubscriptionId(subId)) {
1763                     SubscriptionManager.putSubscriptionIdExtra(intent, subId);
1764                 }
1765                 startActivity(intent);
1766             }
1767             Log.d(this, "placeOutgoingConnection, phone.dial returned null");
1768             connection.setTelephonyConnectionDisconnected(
1769                     mDisconnectCauseFactory.toTelecomDisconnectCause(telephonyDisconnectCause,
1770                             "Connection is null", phone.getPhoneId()));
1771             connection.close();
1772         } else {
1773             if (!getMainThreadHandler().getLooper().isCurrentThread()) {
1774                 Log.w(this, "placeOriginalConnection - Unexpected, this call "
1775                         + "should always be on the main thread.");
1776                 getMainThreadHandler().post(() -> {
1777                     if (connection.getOriginalConnection() == null) {
1778                         connection.setOriginalConnection(originalConnection);
1779                     }
1780                 });
1781             } else {
1782                 connection.setOriginalConnection(originalConnection);
1783             }
1784         }
1785     }
1786 
isVideoCallHoldAllowed(Phone phone)1787     private boolean isVideoCallHoldAllowed(Phone phone) {
1788          CarrierConfigManager cfgManager = (CarrierConfigManager)
1789                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1790         if (cfgManager == null) {
1791             // For some reason CarrierConfigManager is unavailable, return default
1792             Log.w(this, "isVideoCallHoldAllowed: couldn't get CarrierConfigManager");
1793             return true;
1794         }
1795         return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean(
1796                 CarrierConfigManager.KEY_ALLOW_HOLD_VIDEO_CALL_BOOL, true);
1797     }
1798 
shouldHoldForEmergencyCall(Phone phone)1799     private boolean shouldHoldForEmergencyCall(Phone phone) {
1800         CarrierConfigManager cfgManager = (CarrierConfigManager)
1801                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1802         if (cfgManager == null) {
1803             // For some reason CarrierConfigManager is unavailable, return default
1804             Log.w(this, "shouldHoldForEmergencyCall: couldn't get CarrierConfigManager");
1805             return true;
1806         }
1807         return cfgManager.getConfigForSubId(phone.getSubId()).getBoolean(
1808                 CarrierConfigManager.KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL, true);
1809     }
1810 
handleCallStateException(CallStateException e, TelephonyConnection connection, Phone phone)1811     private void handleCallStateException(CallStateException e, TelephonyConnection connection,
1812             Phone phone) {
1813         int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
1814         switch (e.getError()) {
1815             case CallStateException.ERROR_OUT_OF_SERVICE:
1816                 cause = android.telephony.DisconnectCause.OUT_OF_SERVICE;
1817                 break;
1818             case CallStateException.ERROR_POWER_OFF:
1819                  cause = android.telephony.DisconnectCause.POWER_OFF;
1820                  break;
1821             case CallStateException.ERROR_ALREADY_DIALING:
1822                  cause = android.telephony.DisconnectCause.ALREADY_DIALING;
1823                  break;
1824             case CallStateException.ERROR_CALL_RINGING:
1825                  cause = android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING;
1826                  break;
1827             case CallStateException.ERROR_CALLING_DISABLED:
1828                  cause = android.telephony.DisconnectCause.CALLING_DISABLED;
1829                  break;
1830             case CallStateException.ERROR_TOO_MANY_CALLS:
1831                  cause = android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS;
1832                  break;
1833             case CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS:
1834                  cause = android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS;
1835                  break;
1836         }
1837         connection.setTelephonyConnectionDisconnected(
1838                 DisconnectCauseUtil.toTelecomDisconnectCause(cause, e.getMessage(),
1839                         phone.getPhoneId()));
1840         connection.close();
1841     }
1842 
createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId)1843     private TelephonyConnection createConnectionFor(
1844             Phone phone,
1845             com.android.internal.telephony.Connection originalConnection,
1846             boolean isOutgoing,
1847             PhoneAccountHandle phoneAccountHandle,
1848             String telecomCallId) {
1849             return createConnectionFor(phone, originalConnection, isOutgoing, phoneAccountHandle,
1850                     telecomCallId, false);
1851     }
1852 
createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, boolean isAdhocConference)1853     private TelephonyConnection createConnectionFor(
1854             Phone phone,
1855             com.android.internal.telephony.Connection originalConnection,
1856             boolean isOutgoing,
1857             PhoneAccountHandle phoneAccountHandle,
1858             String telecomCallId,
1859             boolean isAdhocConference) {
1860         TelephonyConnection returnConnection = null;
1861         int phoneType = phone.getPhoneType();
1862         int callDirection = isOutgoing ? android.telecom.Call.Details.DIRECTION_OUTGOING
1863                 : android.telecom.Call.Details.DIRECTION_INCOMING;
1864         if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
1865             returnConnection = new GsmConnection(originalConnection, telecomCallId, callDirection);
1866         } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
1867             boolean allowsMute = allowsMute(phone);
1868             returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer,
1869                     allowsMute, callDirection, telecomCallId);
1870         }
1871         if (returnConnection != null) {
1872             if (!isAdhocConference) {
1873                 // Listen to Telephony specific callbacks from the connection
1874                 returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener);
1875             }
1876             returnConnection.setVideoPauseSupported(
1877                     TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
1878                             phoneAccountHandle));
1879             returnConnection.setManageImsConferenceCallSupported(
1880                     TelecomAccountRegistry.getInstance(this).isManageImsConferenceCallSupported(
1881                             phoneAccountHandle));
1882             returnConnection.setShowPreciseFailedCause(
1883                     TelecomAccountRegistry.getInstance(this).isShowPreciseFailedCause(
1884                             phoneAccountHandle));
1885             returnConnection.setTelephonyConnectionService(this);
1886         }
1887         return returnConnection;
1888     }
1889 
isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)1890     private boolean isOriginalConnectionKnown(
1891             com.android.internal.telephony.Connection originalConnection) {
1892         return (getConnectionForOriginalConnection(originalConnection) != null);
1893     }
1894 
getConnectionForOriginalConnection( com.android.internal.telephony.Connection originalConnection)1895     private TelephonyConnection getConnectionForOriginalConnection(
1896             com.android.internal.telephony.Connection originalConnection) {
1897         for (Connection connection : getAllConnections()) {
1898             if (connection instanceof TelephonyConnection) {
1899                 TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
1900                 if (telephonyConnection.getOriginalConnection() == originalConnection) {
1901                     return telephonyConnection;
1902                 }
1903             }
1904         }
1905         return null;
1906     }
1907 
1908     /**
1909      * Determines which {@link Phone} will be used to place the call.
1910      * @param accountHandle The {@link PhoneAccountHandle} which was sent from Telecom to place the
1911      *      call on.
1912      * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise.
1913      * @param emergencyNumberAddress When {@code isEmergency} is {@code true}, will be the phone
1914      *      of the emergency call.  Otherwise, this can be {@code null}  .
1915      * @return
1916      */
getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, @Nullable String emergencyNumberAddress)1917     private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency,
1918                                      @Nullable String emergencyNumberAddress) {
1919         Phone chosenPhone = null;
1920         int subId = mPhoneUtilsProxy.getSubIdForPhoneAccountHandle(accountHandle);
1921         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1922             int phoneId = mSubscriptionManagerProxy.getPhoneId(subId);
1923             chosenPhone = mPhoneFactoryProxy.getPhone(phoneId);
1924         }
1925         // If this is an emergency call and the phone we originally planned to make this call
1926         // with is not in service or was invalid, try to find one that is in service, using the
1927         // default as a last chance backup.
1928         if (isEmergency && (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone))) {
1929             Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
1930                     + "or invalid for emergency call.", accountHandle);
1931             chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress);
1932             Log.d(this, "getPhoneForAccount: using subId: " +
1933                     (chosenPhone == null ? "null" : chosenPhone.getSubId()));
1934         }
1935         return chosenPhone;
1936     }
1937 
1938     /**
1939      * If needed, block until the the default data is is switched for outgoing emergency call, or
1940      * timeout expires.
1941      * @param phone The Phone to switch the DDS on.
1942      * @param completeConsumer The consumer to call once the default data subscription has been
1943      *                         switched, provides {@code true} result if the switch happened
1944      *                         successfully or {@code false} if the operation timed out/failed.
1945      */
delayDialForDdsSwitch(Phone phone, Consumer<Boolean> completeConsumer)1946     private void delayDialForDdsSwitch(Phone phone, Consumer<Boolean> completeConsumer) {
1947         if (phone == null) {
1948             // Do not block indefinitely.
1949             completeConsumer.accept(false);
1950         }
1951         try {
1952             // Waiting for PhoneSwitcher to complete the operation.
1953             CompletableFuture<Boolean> future = possiblyOverrideDefaultDataForEmergencyCall(phone);
1954             // In the case that there is an issue or bug in PhoneSwitcher logic, do not wait
1955             // indefinitely for the future to complete. Instead, set a timeout that will complete
1956             // the future as to not block the outgoing call indefinitely.
1957             CompletableFuture<Boolean> timeout = new CompletableFuture<>();
1958             phone.getContext().getMainThreadHandler().postDelayed(
1959                     () -> timeout.complete(false), DEFAULT_DATA_SWITCH_TIMEOUT_MS);
1960             // Also ensure that the Consumer is completed on the main thread.
1961             future.acceptEitherAsync(timeout, completeConsumer,
1962                     phone.getContext().getMainExecutor());
1963         } catch (Exception e) {
1964             Log.w(this, "delayDialForDdsSwitch - exception= "
1965                     + e.getMessage());
1966 
1967         }
1968     }
1969 
1970     /**
1971      * If needed, block until Default Data subscription is switched for outgoing emergency call.
1972      *
1973      * In some cases, we need to try to switch the Default Data subscription before placing the
1974      * emergency call on DSDS devices. This includes the following situation:
1975      * - The modem does not support processing GNSS SUPL requests on the non-default data
1976      * subscription. For some carriers that do not provide a control plane fallback mechanism, the
1977      * SUPL request will be dropped and we will not be able to get the user's location for the
1978      * emergency call. In this case, we need to swap default data temporarily.
1979      * @param phone Evaluates whether or not the default data should be moved to the phone
1980      *              specified. Should not be null.
1981      */
possiblyOverrideDefaultDataForEmergencyCall( @onNull Phone phone)1982     private CompletableFuture<Boolean> possiblyOverrideDefaultDataForEmergencyCall(
1983             @NonNull Phone phone) {
1984         int phoneCount = mTelephonyManagerProxy.getPhoneCount();
1985         // Do not override DDS if this is a single SIM device.
1986         if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) {
1987             return CompletableFuture.completedFuture(Boolean.TRUE);
1988         }
1989 
1990         // Do not switch Default data if this device supports emergency SUPL on non-DDS.
1991         final boolean gnssSuplRequiresDefaultData =
1992                 mDeviceState.isSuplDdsSwitchRequiredForEmergencyCall(this);
1993         if (!gnssSuplRequiresDefaultData) {
1994             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not "
1995                     + "require DDS switch.");
1996             return CompletableFuture.completedFuture(Boolean.TRUE);
1997         }
1998 
1999         CarrierConfigManager cfgManager = (CarrierConfigManager)
2000                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
2001         if (cfgManager == null) {
2002             // For some reason CarrierConfigManager is unavailable. Do not block emergency call.
2003             Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: couldn't get"
2004                     + "CarrierConfigManager");
2005             return CompletableFuture.completedFuture(Boolean.TRUE);
2006         }
2007 
2008         // Only override default data if we are IN_SERVICE already.
2009         if (!isAvailableForEmergencyCalls(phone)) {
2010             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS");
2011             return CompletableFuture.completedFuture(Boolean.TRUE);
2012         }
2013 
2014         // Only override default data if we are not roaming, we do not want to switch onto a network
2015         // that only supports data plane only (if we do not know).
2016         boolean isRoaming = phone.getServiceState().getVoiceRoaming();
2017         // In some roaming conditions, we know the roaming network doesn't support control plane
2018         // fallback even though the home operator does. For these operators we will need to do a DDS
2019         // switch anyway to make sure the SUPL request doesn't fail.
2020         boolean roamingNetworkSupportsControlPlaneFallback = true;
2021         String[] dataPlaneRoamPlmns = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray(
2022                 CarrierConfigManager.Gps.KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY);
2023         if (dataPlaneRoamPlmns != null && Arrays.asList(dataPlaneRoamPlmns).contains(
2024                 phone.getServiceState().getOperatorNumeric())) {
2025             roamingNetworkSupportsControlPlaneFallback = false;
2026         }
2027         if (isRoaming && roamingNetworkSupportsControlPlaneFallback) {
2028             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: roaming network is assumed "
2029                     + "to support CP fallback, not switching DDS.");
2030             return CompletableFuture.completedFuture(Boolean.TRUE);
2031         }
2032         // Do not try to swap default data if we support CS fallback or it is assumed that the
2033         // roaming network supports control plane fallback, we do not want to introduce
2034         // a lag in emergency call setup time if possible.
2035         final boolean supportsCpFallback = cfgManager.getConfigForSubId(phone.getSubId())
2036                 .getInt(CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
2037                         CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY)
2038                 != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY;
2039         if (supportsCpFallback && roamingNetworkSupportsControlPlaneFallback) {
2040             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier "
2041                     + "supports CP fallback.");
2042             return CompletableFuture.completedFuture(Boolean.TRUE);
2043         }
2044 
2045         // Get extension time, may be 0 for some carriers that support ECBM as well. Use
2046         // CarrierConfig default if format fails.
2047         int extensionTime = 0;
2048         try {
2049             extensionTime = Integer.parseInt(cfgManager.getConfigForSubId(phone.getSubId())
2050                     .getString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0"));
2051         } catch (NumberFormatException e) {
2052             // Just use default.
2053         }
2054         CompletableFuture<Boolean> modemResultFuture = new CompletableFuture<>();
2055         try {
2056             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for "
2057                     + extensionTime + "seconds");
2058             mPhoneSwitcherProxy.getPhoneSwitcher().overrideDefaultDataForEmergency(
2059                     phone.getPhoneId(), extensionTime, modemResultFuture);
2060             // Catch all exceptions, we want to continue with emergency call if possible.
2061         } catch (Exception e) {
2062             Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: exception = "
2063                     + e.getMessage());
2064             modemResultFuture = CompletableFuture.completedFuture(Boolean.FALSE);
2065         }
2066         return modemResultFuture;
2067     }
2068 
2069     /**
2070      * Get the Phone to use for an emergency call of the given emergency number address:
2071      *  a) If there are multiple Phones with the Subscriptions that support the emergency number
2072      *     address, and one of them is the default voice Phone, consider the default voice phone
2073      *     if 1.4 HAL is supported, or if it is available for emergency call.
2074      *  b) If there are multiple Phones with the Subscriptions that support the emergency number
2075      *     address, and none of them is the default voice Phone, use one of these Phones if 1.4 HAL
2076      *     is supported, or if it is available for emergency call.
2077      *  c) If there is no Phone that supports the emergency call for the address, use the defined
2078      *     Priority list to select the Phone via {@link #getFirstPhoneForEmergencyCall}.
2079      */
getPhoneForEmergencyCall(String emergencyNumberAddress)2080     public Phone getPhoneForEmergencyCall(String emergencyNumberAddress) {
2081         // Find the list of available Phones for the given emergency number address
2082         List<Phone> potentialEmergencyPhones = new ArrayList<>();
2083         int defaultVoicePhoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId();
2084         for (Phone phone : mPhoneFactoryProxy.getPhones()) {
2085             if (phone.getEmergencyNumberTracker() != null) {
2086                 if (phone.getEmergencyNumberTracker().isEmergencyNumber(
2087                         emergencyNumberAddress, true)) {
2088                     if (isAvailableForEmergencyCalls(phone)) {
2089                         // a)
2090                         if (phone.getPhoneId() == defaultVoicePhoneId) {
2091                             Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports"
2092                                     + " emergency number: " + phone.getPhoneId());
2093                             return phone;
2094                         }
2095                         potentialEmergencyPhones.add(phone);
2096                     }
2097                 }
2098             }
2099         }
2100         // b)
2101         if (potentialEmergencyPhones.size() > 0) {
2102             Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports emergency number:"
2103                     + potentialEmergencyPhones.get(0).getPhoneId());
2104             return getFirstPhoneForEmergencyCall(potentialEmergencyPhones);
2105         }
2106         // c)
2107         return getFirstPhoneForEmergencyCall();
2108     }
2109 
2110     @VisibleForTesting
getFirstPhoneForEmergencyCall()2111     public Phone getFirstPhoneForEmergencyCall() {
2112         return getFirstPhoneForEmergencyCall(null);
2113     }
2114 
2115     /**
2116      * Retrieves the most sensible Phone to use for an emergency call using the following Priority
2117      *  list (for multi-SIM devices):
2118      *  1) The User's SIM preference for Voice calling
2119      *  2) The First Phone that is currently IN_SERVICE or is available for emergency calling
2120      *  3) Prioritize phones that have the dialed emergency number as part of their emergency
2121      *     number list
2122      *  4) If there is a PUK locked SIM, compare the SIMs that are not PUK locked. If all the SIMs
2123      *     are locked, skip to condition 5).
2124      *  5) The Phone with more Capabilities.
2125      *  6) The First Phone that has a SIM card in it (Starting from Slot 0...N)
2126      *  7) The Default Phone (Currently set as Slot 0)
2127      */
2128     @VisibleForTesting
getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber)2129     public Phone getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber) {
2130         // 1)
2131         int phoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId();
2132         if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
2133             Phone defaultPhone = mPhoneFactoryProxy.getPhone(phoneId);
2134             if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) {
2135                 if (phonesWithEmergencyNumber == null
2136                         || phonesWithEmergencyNumber.contains(defaultPhone)) {
2137                     return defaultPhone;
2138                 }
2139             }
2140         }
2141 
2142         Phone firstPhoneWithSim = null;
2143         int phoneCount = mTelephonyManagerProxy.getPhoneCount();
2144         List<SlotStatus> phoneSlotStatus = new ArrayList<>(phoneCount);
2145         for (int i = 0; i < phoneCount; i++) {
2146             Phone phone = mPhoneFactoryProxy.getPhone(i);
2147             if (phone == null) {
2148                 continue;
2149             }
2150             // 2)
2151             if (isAvailableForEmergencyCalls(phone)) {
2152                 if (phonesWithEmergencyNumber == null
2153                         || phonesWithEmergencyNumber.contains(phone)) {
2154                     // the slot has the radio on & state is in service.
2155                     Log.i(this,
2156                             "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i);
2157                     return phone;
2158                 }
2159             }
2160             // 5)
2161             // Store the RAF Capabilities for sorting later.
2162             int radioAccessFamily = phone.getRadioAccessFamily();
2163             SlotStatus status = new SlotStatus(i, radioAccessFamily);
2164             phoneSlotStatus.add(status);
2165             Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" +
2166                     Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i);
2167             // 4)
2168             // Report Slot's PIN/PUK lock status for sorting later.
2169             int simState = mSubscriptionManagerProxy.getSimStateForSlotIdx(i);
2170             // Record SimState.
2171             status.simState = simState;
2172             if (simState == TelephonyManager.SIM_STATE_PIN_REQUIRED ||
2173                     simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) {
2174                 status.isLocked = true;
2175             }
2176             // 3) Store if the Phone has the corresponding emergency number
2177             if (phonesWithEmergencyNumber != null) {
2178                 for (Phone phoneWithEmergencyNumber : phonesWithEmergencyNumber) {
2179                     if (phoneWithEmergencyNumber != null
2180                             && phoneWithEmergencyNumber.getPhoneId() == i) {
2181                         status.hasDialedEmergencyNumber = true;
2182                     }
2183                 }
2184             }
2185             // 6)
2186             if (firstPhoneWithSim == null && mTelephonyManagerProxy.hasIccCard(i)) {
2187                 // The slot has a SIM card inserted, but is not in service, so keep track of this
2188                 // Phone. Do not return because we want to make sure that none of the other Phones
2189                 // are in service (because that is always faster).
2190                 firstPhoneWithSim = phone;
2191                 Log.i(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" +
2192                         firstPhoneWithSim.getPhoneId());
2193             }
2194         }
2195         // 7)
2196         if (firstPhoneWithSim == null && phoneSlotStatus.isEmpty()) {
2197             if (phonesWithEmergencyNumber == null || phonesWithEmergencyNumber.isEmpty()) {
2198                 // No Phones available, get the default
2199                 Log.i(this, "getFirstPhoneForEmergencyCall, return default phone");
2200                 return  mPhoneFactoryProxy.getDefaultPhone();
2201             }
2202             return phonesWithEmergencyNumber.get(0);
2203         } else {
2204             // 5)
2205             final int defaultPhoneId = mPhoneFactoryProxy.getDefaultPhone().getPhoneId();
2206             final Phone firstOccupiedSlot = firstPhoneWithSim;
2207             if (!phoneSlotStatus.isEmpty()) {
2208                 // Only sort if there are enough elements to do so.
2209                 if (phoneSlotStatus.size() > 1) {
2210                     Collections.sort(phoneSlotStatus, (o1, o2) -> {
2211                         // Sort by non-absent SIM.
2212                         if (o1.simState == TelephonyManager.SIM_STATE_ABSENT
2213                                 && o2.simState != TelephonyManager.SIM_STATE_ABSENT) {
2214                             return -1;
2215                         }
2216                         if (o2.simState == TelephonyManager.SIM_STATE_ABSENT
2217                                 && o1.simState != TelephonyManager.SIM_STATE_ABSENT) {
2218                             return 1;
2219                         }
2220                         // First start by seeing if either of the phone slots are locked. If they
2221                         // are, then sort by non-locked SIM first. If they are both locked, sort
2222                         // by capability instead.
2223                         if (o1.isLocked && !o2.isLocked) {
2224                             return -1;
2225                         }
2226                         if (o2.isLocked && !o1.isLocked) {
2227                             return 1;
2228                         }
2229                         // Prefer slots where the number is considered emergency.
2230                         if (!o1.hasDialedEmergencyNumber && o2.hasDialedEmergencyNumber) {
2231                             return -1;
2232                         }
2233                         if (o1.hasDialedEmergencyNumber && !o2.hasDialedEmergencyNumber) {
2234                             return 1;
2235                         }
2236                         // sort by number of RadioAccessFamily Capabilities.
2237                         int compare = RadioAccessFamily.compare(o1.capabilities, o2.capabilities);
2238                         if (compare == 0) {
2239                             if (firstOccupiedSlot != null) {
2240                                 // If the RAF capability is the same, choose based on whether or
2241                                 // not any of the slots are occupied with a SIM card (if both
2242                                 // are, always choose the first).
2243                                 if (o1.slotId == firstOccupiedSlot.getPhoneId()) {
2244                                     return 1;
2245                                 } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) {
2246                                     return -1;
2247                                 }
2248                             } else {
2249                                 // No slots have SIMs detected in them, so weight the default
2250                                 // Phone Id greater than the others.
2251                                 if (o1.slotId == defaultPhoneId) {
2252                                     return 1;
2253                                 } else if (o2.slotId == defaultPhoneId) {
2254                                     return -1;
2255                                 }
2256                             }
2257                         }
2258                         return compare;
2259                     });
2260                 }
2261                 int mostCapablePhoneId = phoneSlotStatus.get(phoneSlotStatus.size() - 1).slotId;
2262                 Log.i(this, "getFirstPhoneForEmergencyCall, Using Phone Id: " + mostCapablePhoneId +
2263                         "with highest capability");
2264                 return mPhoneFactoryProxy.getPhone(mostCapablePhoneId);
2265             } else {
2266                 // 6)
2267                 return firstPhoneWithSim;
2268             }
2269         }
2270     }
2271 
2272     /**
2273      * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only.
2274      */
isAvailableForEmergencyCalls(Phone phone)2275     private boolean isAvailableForEmergencyCalls(Phone phone) {
2276         return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() ||
2277                 phone.getServiceState().isEmergencyOnly();
2278     }
2279 
2280     /**
2281      * Determines if the connection should allow mute.
2282      *
2283      * @param phone The current phone.
2284      * @return {@code True} if the connection should allow mute.
2285      */
allowsMute(Phone phone)2286     private boolean allowsMute(Phone phone) {
2287         // For CDMA phones, check if we are in Emergency Callback Mode (ECM).  Mute is disallowed
2288         // in ECM mode.
2289         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
2290             if (phone.isInEcm()) {
2291                 return false;
2292             }
2293         }
2294 
2295         return true;
2296     }
2297 
getTelephonyConnectionListener()2298     TelephonyConnection.TelephonyConnectionListener getTelephonyConnectionListener() {
2299         return mTelephonyConnectionListener;
2300     }
2301 
2302     /**
2303      * When a {@link TelephonyConnection} has its underlying original connection configured,
2304      * we need to add it to the correct conference controller.
2305      *
2306      * @param connection The connection to be added to the controller
2307      */
addConnectionToConferenceController(TelephonyConnection connection)2308     public void addConnectionToConferenceController(TelephonyConnection connection) {
2309         // TODO: Need to revisit what happens when the original connection for the
2310         // TelephonyConnection changes.  If going from CDMA --> GSM (for example), the
2311         // instance of TelephonyConnection will still be a CdmaConnection, not a GsmConnection.
2312         // The CDMA conference controller makes the assumption that it will only have CDMA
2313         // connections in it, while the other conference controllers aren't as restrictive.  Really,
2314         // when we go between CDMA and GSM we should replace the TelephonyConnection.
2315         if (connection.isImsConnection()) {
2316             Log.d(this, "Adding IMS connection to conference controller: " + connection);
2317             mImsConferenceController.add(connection);
2318             mTelephonyConferenceController.remove(connection);
2319             if (connection instanceof CdmaConnection) {
2320                 mCdmaConferenceController.remove((CdmaConnection) connection);
2321             }
2322         } else {
2323             int phoneType = connection.getCall().getPhone().getPhoneType();
2324             if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
2325                 Log.d(this, "Adding GSM connection to conference controller: " + connection);
2326                 mTelephonyConferenceController.add(connection);
2327                 if (connection instanceof CdmaConnection) {
2328                     mCdmaConferenceController.remove((CdmaConnection) connection);
2329                 }
2330             } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA &&
2331                     connection instanceof CdmaConnection) {
2332                 Log.d(this, "Adding CDMA connection to conference controller: " + connection);
2333                 mCdmaConferenceController.add((CdmaConnection) connection);
2334                 mTelephonyConferenceController.remove(connection);
2335             }
2336             Log.d(this, "Removing connection from IMS conference controller: " + connection);
2337             mImsConferenceController.remove(connection);
2338         }
2339     }
2340 
2341     /**
2342      * Create a new CDMA connection. CDMA connections have additional limitations when creating
2343      * additional calls which are handled in this method.  Specifically, CDMA has a "FLASH" command
2344      * that can be used for three purposes: merging a call, swapping unmerged calls, and adding
2345      * a new outgoing call. The function of the flash command depends on the context of the current
2346      * set of calls. This method will prevent an outgoing call from being made if it is not within
2347      * the right circumstances to support adding a call.
2348      */
checkAdditionalOutgoingCallLimits(Phone phone)2349     private Connection checkAdditionalOutgoingCallLimits(Phone phone) {
2350         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
2351             // Check to see if any CDMA conference calls exist, and if they do, check them for
2352             // limitations.
2353             for (Conference conference : getAllConferences()) {
2354                 if (conference instanceof CdmaConference) {
2355                     CdmaConference cdmaConf = (CdmaConference) conference;
2356 
2357                     // If the CDMA conference has not been merged, add-call will not work, so fail
2358                     // this request to add a call.
2359                     if ((cdmaConf.getConnectionCapabilities()
2360                             & Connection.CAPABILITY_MERGE_CONFERENCE) != 0) {
2361                         return Connection.createFailedConnection(new DisconnectCause(
2362                                     DisconnectCause.RESTRICTED,
2363                                     null,
2364                                     getResources().getString(R.string.callFailed_cdma_call_limit),
2365                                     "merge-capable call exists, prevent flash command."));
2366                     }
2367                 }
2368             }
2369         }
2370 
2371         return null; // null means nothing went wrong, and call should continue.
2372     }
2373 
2374     /**
2375      * For outgoing dialed calls, potentially send a ConnectionEvent if the user is on WFC and is
2376      * dialing an international number.
2377      * @param telephonyConnection The connection.
2378      */
maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection)2379     private void maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection) {
2380         if (telephonyConnection == null || telephonyConnection.getPhone() == null ||
2381                 telephonyConnection.getPhone().getDefaultPhone() == null) {
2382             return;
2383         }
2384         Phone phone = telephonyConnection.getPhone().getDefaultPhone();
2385         if (phone instanceof GsmCdmaPhone) {
2386             GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone;
2387             if (telephonyConnection.isOutgoingCall() &&
2388                     gsmCdmaPhone.isNotificationOfWfcCallRequired(
2389                             telephonyConnection.getOriginalConnection().getOrigDialString())) {
2390                 // Send connection event to InCall UI to inform the user of the fact they
2391                 // are potentially placing an international call on WFC.
2392                 Log.i(this, "placeOutgoingConnection - sending international call on WFC " +
2393                         "confirmation event");
2394                 telephonyConnection.sendTelephonyConnectionEvent(
2395                         TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null);
2396             }
2397         }
2398     }
2399 
handleTtyModeChange(boolean isTtyEnabled)2400     private void handleTtyModeChange(boolean isTtyEnabled) {
2401         Log.i(this, "handleTtyModeChange; isTtyEnabled=%b", isTtyEnabled);
2402         mIsTtyEnabled = isTtyEnabled;
2403         for (Connection connection : getAllConnections()) {
2404             if (connection instanceof TelephonyConnection) {
2405                 TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
2406                 telephonyConnection.setTtyEnabled(isTtyEnabled);
2407             }
2408         }
2409     }
2410 
closeOrDestroyConnection(Connection connection, DisconnectCause cause)2411     private void closeOrDestroyConnection(Connection connection, DisconnectCause cause) {
2412         if (connection instanceof TelephonyConnection) {
2413             TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
2414             telephonyConnection.setTelephonyConnectionDisconnected(cause);
2415             // Close destroys the connection and notifies TelephonyConnection listeners.
2416             telephonyConnection.close();
2417         } else {
2418             connection.setDisconnected(cause);
2419             connection.destroy();
2420         }
2421     }
2422 
showDataDialog(Phone phone, String number)2423     private boolean showDataDialog(Phone phone, String number) {
2424         boolean ret = false;
2425         final Context context = getApplicationContext();
2426         String suppKey = MmiCodeUtil.getSuppServiceKey(number);
2427         if (suppKey != null) {
2428             boolean clirOverUtPrecautions = false;
2429             boolean cfOverUtPrecautions = false;
2430             boolean cbOverUtPrecautions = false;
2431             boolean cwOverUtPrecautions = false;
2432 
2433             CarrierConfigManager cfgManager = (CarrierConfigManager)
2434                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
2435             if (cfgManager != null) {
2436                 clirOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
2437                     .getBoolean(CarrierConfigManager.KEY_CALLER_ID_OVER_UT_WARNING_BOOL);
2438                 cfOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
2439                     .getBoolean(CarrierConfigManager.KEY_CALL_FORWARDING_OVER_UT_WARNING_BOOL);
2440                 cbOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
2441                     .getBoolean(CarrierConfigManager.KEY_CALL_BARRING_OVER_UT_WARNING_BOOL);
2442                 cwOverUtPrecautions = cfgManager.getConfigForSubId(phone.getSubId())
2443                     .getBoolean(CarrierConfigManager.KEY_CALL_WAITING_OVER_UT_WARNING_BOOL);
2444             }
2445 
2446             boolean isSsOverUtPrecautions = SuppServicesUiUtil
2447                 .isSsOverUtPrecautions(context, phone);
2448             if (isSsOverUtPrecautions) {
2449                 boolean showDialog = false;
2450                 if (suppKey == MmiCodeUtil.BUTTON_CLIR_KEY && clirOverUtPrecautions) {
2451                     showDialog = true;
2452                 } else if (suppKey == MmiCodeUtil.CALL_FORWARDING_KEY && cfOverUtPrecautions) {
2453                     showDialog = true;
2454                 } else if (suppKey == MmiCodeUtil.CALL_BARRING_KEY && cbOverUtPrecautions) {
2455                     showDialog = true;
2456                 } else if (suppKey == MmiCodeUtil.BUTTON_CW_KEY && cwOverUtPrecautions) {
2457                     showDialog = true;
2458                 }
2459 
2460                 if (showDialog) {
2461                     Log.d(this, "Creating UT Data enable dialog");
2462                     String message = SuppServicesUiUtil.makeMessage(context, suppKey, phone);
2463                     AlertDialog.Builder builder = new AlertDialog.Builder(context);
2464                     DialogInterface.OnClickListener networkSettingsClickListener =
2465                             new Dialog.OnClickListener() {
2466                                 @Override
2467                                 public void onClick(DialogInterface dialog, int which) {
2468                                     Intent intent = new Intent(Intent.ACTION_MAIN);
2469                                     ComponentName mobileNetworkSettingsComponent
2470                                         = new ComponentName(
2471                                                 context.getString(
2472                                                     R.string.mobile_network_settings_package),
2473                                                 context.getString(
2474                                                     R.string.mobile_network_settings_class));
2475                                     intent.setComponent(mobileNetworkSettingsComponent);
2476                                     context.startActivity(intent);
2477                                 }
2478                             };
2479                     Dialog dialog = builder.setMessage(message)
2480                         .setNeutralButton(context.getResources().getString(
2481                                 R.string.settings_label),
2482                                 networkSettingsClickListener)
2483                         .setPositiveButton(context.getResources().getString(
2484                                 R.string.supp_service_over_ut_precautions_dialog_dismiss), null)
2485                         .create();
2486                     dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
2487                     dialog.show();
2488                     ret = true;
2489                 }
2490             }
2491         }
2492         return ret;
2493     }
2494 
2495     /**
2496      * Adds a {@link Conference} to the telephony ConnectionService and registers a listener for
2497      * changes to the conference.  Should be used instead of {@link #addConference(Conference)}.
2498      * @param conference The conference.
2499      */
addTelephonyConference(@onNull TelephonyConferenceBase conference)2500     public void addTelephonyConference(@NonNull TelephonyConferenceBase conference) {
2501         addConference(conference);
2502         conference.addTelephonyConferenceListener(mTelephonyConferenceListener);
2503     }
2504 
2505     /**
2506      * Sends a test device to device message on the active call which supports it.
2507      * Used exclusively from the telephony shell command to send a test message.
2508      *
2509      * @param message the message
2510      * @param value the value
2511      */
sendTestDeviceToDeviceMessage(int message, int value)2512     public void sendTestDeviceToDeviceMessage(int message, int value) {
2513        getAllConnections().stream()
2514                .filter(f -> f instanceof TelephonyConnection)
2515                .forEach(t -> {
2516                    TelephonyConnection tc = (TelephonyConnection) t;
2517                    if (!tc.isImsConnection()) {
2518                        Log.w(this, "sendTestDeviceToDeviceMessage: not an IMS connection");
2519                        return;
2520                    }
2521                    Communicator c = tc.getCommunicator();
2522                    if (c == null) {
2523                        Log.w(this, "sendTestDeviceToDeviceMessage: D2D not enabled");
2524                        return;
2525                    }
2526 
2527                    c.sendMessages(new HashSet<Communicator.Message>() {{
2528                        add(new Communicator.Message(message, value));
2529                    }});
2530 
2531                });
2532     }
2533 
2534     /**
2535      * Overrides the current D2D transport, forcing the specified one to be active.  Used for test.
2536      * @param transport The class simple name of the transport to make active.
2537      */
setActiveDeviceToDeviceTransport(@onNull String transport)2538     public void setActiveDeviceToDeviceTransport(@NonNull String transport) {
2539         getAllConnections().stream()
2540                 .filter(f -> f instanceof TelephonyConnection)
2541                 .forEach(t -> {
2542                     TelephonyConnection tc = (TelephonyConnection) t;
2543                     Communicator c = tc.getCommunicator();
2544                     if (c == null) {
2545                         Log.w(this, "setActiveDeviceToDeviceTransport: D2D not enabled");
2546                         return;
2547                     }
2548                     Log.i(this, "setActiveDeviceToDeviceTransport: callId=%s, set to: %s",
2549                             tc.getTelecomCallId(), transport);
2550                     c.setTransportActive(transport);
2551                 });
2552     }
2553 
adjustAccountHandle(Phone phone, PhoneAccountHandle origAccountHandle)2554     private PhoneAccountHandle adjustAccountHandle(Phone phone,
2555             PhoneAccountHandle origAccountHandle) {
2556         int origSubId = PhoneUtils.getSubIdForPhoneAccountHandle(origAccountHandle);
2557         int subId = phone.getSubId();
2558         if (origSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
2559                 && subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID
2560                 && origSubId != subId) {
2561             PhoneAccountHandle handle = TelecomAccountRegistry.getInstance(this)
2562                 .getPhoneAccountHandleForSubId(subId);
2563             if (handle != null) {
2564                 return handle;
2565             }
2566         }
2567         return origAccountHandle;
2568     }
2569 
2570     /**
2571      * For the passed in incoming {@link TelephonyConnection}, add
2572      * {@link Connection#EXTRA_ANSWERING_DROPS_FG_CALL} if there are ongoing calls on another
2573      * subscription (ie phone account handle) than the one passed in.
2574      * @param connection The connection.
2575      * @param phoneAccountHandle The {@link PhoneAccountHandle} the incoming call originated on;
2576      *                           this is passed in because
2577      *                           {@link Connection#getPhoneAccountHandle()} is not set until after
2578      *                           {@link ConnectionService#onCreateIncomingConnection(
2579      *                           PhoneAccountHandle, ConnectionRequest)} returns.
2580      */
maybeIndicateAnsweringWillDisconnect(@onNull TelephonyConnection connection, @NonNull PhoneAccountHandle phoneAccountHandle)2581     public void maybeIndicateAnsweringWillDisconnect(@NonNull TelephonyConnection connection,
2582             @NonNull PhoneAccountHandle phoneAccountHandle) {
2583         if (isCallPresentOnOtherSub(phoneAccountHandle)) {
2584             Log.i(this, "maybeIndicateAnsweringWillDisconnect; answering call %s will cause a call "
2585                     + "on another subscription to drop.", connection.getTelecomCallId());
2586             Bundle extras = new Bundle();
2587             extras.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
2588             connection.putExtras(extras);
2589         }
2590     }
2591 
2592     /**
2593      * Checks to see if there are calls present on a sub other than the one passed in.
2594      * @param incomingHandle The new incoming connection {@link PhoneAccountHandle}
2595      */
isCallPresentOnOtherSub(@onNull PhoneAccountHandle incomingHandle)2596     private boolean isCallPresentOnOtherSub(@NonNull PhoneAccountHandle incomingHandle) {
2597         return getAllConnections().stream()
2598                 .filter(c ->
2599                         // Exclude multiendpoint calls as they're not on this device.
2600                         (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
2601                         // Include any calls not on same sub as current connection.
2602                         && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle))
2603                 .count() > 0;
2604     }
2605 
2606     /**
2607      * Where there are ongoing calls on another subscription other than the one specified,
2608      * disconnect these calls.  This is used where there is an incoming call on one sub, but there
2609      * are ongoing calls on another sub which need to be disconnected.
2610      * @param incomingHandle The incoming {@link PhoneAccountHandle}.
2611      */
maybeDisconnectCallsOnOtherSubs(@onNull PhoneAccountHandle incomingHandle)2612     public void maybeDisconnectCallsOnOtherSubs(@NonNull PhoneAccountHandle incomingHandle) {
2613         Log.i(this, "maybeDisconnectCallsOnOtherSubs: check for calls not on %s", incomingHandle);
2614         maybeDisconnectCallsOnOtherSubs(getAllConnections(), incomingHandle);
2615     }
2616 
2617     /**
2618      * Used by {@link #maybeDisconnectCallsOnOtherSubs(PhoneAccountHandle)} to perform call
2619      * disconnection.  This method exists as a convenience so that it is possible to unit test
2620      * the core functionality.
2621      * @param connections the calls to check.
2622      * @param incomingHandle the incoming handle.
2623      */
2624     @VisibleForTesting
maybeDisconnectCallsOnOtherSubs(@onNull Collection<Connection> connections, @NonNull PhoneAccountHandle incomingHandle)2625     public static void maybeDisconnectCallsOnOtherSubs(@NonNull Collection<Connection> connections,
2626             @NonNull PhoneAccountHandle incomingHandle) {
2627         connections.stream()
2628                 .filter(c ->
2629                         // Exclude multiendpoint calls as they're not on this device.
2630                         (c.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL) == 0
2631                                 // Include any calls not on same sub as current connection.
2632                                 && !Objects.equals(c.getPhoneAccountHandle(), incomingHandle))
2633                 .forEach(c -> {
2634                     if (c instanceof TelephonyConnection) {
2635                         TelephonyConnection tc = (TelephonyConnection) c;
2636                         if (!tc.shouldTreatAsEmergencyCall()) {
2637                             Log.i(LOG_TAG, "maybeDisconnectCallsOnOtherSubs: disconnect %s due to "
2638                                     + "incoming call on other sub.", tc.getTelecomCallId());
2639                             // Note: intentionally calling hangup instead of onDisconnect.
2640                             // onDisconnect posts the disconnection to a handle which means that the
2641                             // disconnection will take place AFTER we answer the incoming call.
2642                             tc.hangup(android.telephony.DisconnectCause.LOCAL);
2643                         }
2644                     }
2645                 });
2646     }
2647 }
2648