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