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