• 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.content.ActivityNotFoundException;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.provider.Settings;
29 import android.telecom.Conference;
30 import android.telecom.Connection;
31 import android.telecom.ConnectionRequest;
32 import android.telecom.ConnectionService;
33 import android.telecom.DisconnectCause;
34 import android.telecom.PhoneAccount;
35 import android.telecom.PhoneAccountHandle;
36 import android.telecom.TelecomManager;
37 import android.telecom.VideoProfile;
38 import android.telephony.CarrierConfigManager;
39 import android.telephony.PhoneNumberUtils;
40 import android.telephony.RadioAccessFamily;
41 import android.telephony.ServiceState;
42 import android.telephony.SubscriptionManager;
43 import android.telephony.TelephonyManager;
44 import android.telephony.emergency.EmergencyNumber;
45 import android.text.TextUtils;
46 import android.util.Pair;
47 
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.internal.telephony.Call;
50 import com.android.internal.telephony.CallStateException;
51 import com.android.internal.telephony.GsmCdmaPhone;
52 import com.android.internal.telephony.IccCard;
53 import com.android.internal.telephony.IccCardConstants;
54 import com.android.internal.telephony.Phone;
55 import com.android.internal.telephony.PhoneConstants;
56 import com.android.internal.telephony.PhoneFactory;
57 import com.android.internal.telephony.PhoneSwitcher;
58 import com.android.internal.telephony.RIL;
59 import com.android.internal.telephony.SubscriptionController;
60 import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
61 import com.android.internal.telephony.imsphone.ImsPhone;
62 import com.android.internal.telephony.imsphone.ImsPhoneConnection;
63 import com.android.phone.MMIDialogActivity;
64 import com.android.phone.PhoneUtils;
65 import com.android.phone.R;
66 
67 import java.lang.ref.WeakReference;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.Collection;
71 import java.util.Collections;
72 import java.util.LinkedList;
73 import java.util.List;
74 import java.util.Map;
75 import java.util.Queue;
76 import java.util.concurrent.CompletableFuture;
77 import java.util.concurrent.TimeUnit;
78 import java.util.regex.Pattern;
79 
80 import javax.annotation.Nullable;
81 
82 /**
83  * Service for making GSM and CDMA connections.
84  */
85 public class TelephonyConnectionService extends ConnectionService {
86 
87     // Timeout before we continue with the emergency call without waiting for DDS switch response
88     // from the modem.
89     private static final int DEFAULT_DATA_SWITCH_TIMEOUT_MS = 1000;
90 
91     // If configured, reject attempts to dial numbers matching this pattern.
92     private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
93             Pattern.compile("\\*228[0-9]{0,2}");
94 
95     private final TelephonyConnectionServiceProxy mTelephonyConnectionServiceProxy =
96             new TelephonyConnectionServiceProxy() {
97         @Override
98         public Collection<Connection> getAllConnections() {
99             return TelephonyConnectionService.this.getAllConnections();
100         }
101         @Override
102         public void addConference(TelephonyConference mTelephonyConference) {
103             TelephonyConnectionService.this.addConference(mTelephonyConference);
104         }
105         @Override
106         public void addConference(ImsConference mImsConference) {
107             TelephonyConnectionService.this.addConference(mImsConference);
108         }
109         @Override
110         public void removeConnection(Connection connection) {
111             TelephonyConnectionService.this.removeConnection(connection);
112         }
113         @Override
114         public void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
115                                           Connection connection) {
116             TelephonyConnectionService.this
117                     .addExistingConnection(phoneAccountHandle, connection);
118         }
119         @Override
120         public void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
121                 Connection connection, Conference conference) {
122             TelephonyConnectionService.this
123                     .addExistingConnection(phoneAccountHandle, connection, conference);
124         }
125         @Override
126         public void addConnectionToConferenceController(TelephonyConnection connection) {
127             TelephonyConnectionService.this.addConnectionToConferenceController(connection);
128         }
129     };
130 
131     private final Connection.Listener mConnectionListener = new Connection.Listener() {
132         @Override
133         public void onConferenceChanged(Connection connection, Conference conference) {
134             mHoldTracker.updateHoldCapability(connection.getPhoneAccountHandle());
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 final ImsConferenceController mImsConferenceController =
160             new ImsConferenceController(TelecomAccountRegistry.getInstance(this),
161                     mTelephonyConnectionServiceProxy,
162                     // FeatureFlagProxy; used to determine if standalone call emulation is enabled.
163                     // TODO: Move to carrier config
164                     () -> true);
165 
166     private ComponentName mExpectedComponentName = null;
167     private RadioOnHelper mRadioOnHelper;
168     private EmergencyTonePlayer mEmergencyTonePlayer;
169     private HoldTracker mHoldTracker;
170     private boolean mIsTtyEnabled;
171 
172     // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has
173     // already tried to connect with. There should be only one TelephonyConnection trying to place a
174     // call at one time. We also only access this cache from a TelephonyConnection that wishes to
175     // redial, so we use a WeakReference that will become stale once the TelephonyConnection is
176     // destroyed.
177     @VisibleForTesting
178     public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
179 
180     /**
181      * Keeps track of the status of a SIM slot.
182      */
183     private static class SlotStatus {
184         public int slotId;
185         // RAT capabilities
186         public int capabilities;
187         // By default, we will assume that the slots are not locked.
188         public boolean isLocked = false;
189         // Is the emergency number associated with the slot
190         public boolean hasDialedEmergencyNumber = false;
191 
SlotStatus(int slotId, int capabilities)192         public SlotStatus(int slotId, int capabilities) {
193             this.slotId = slotId;
194             this.capabilities = capabilities;
195         }
196     }
197 
198     // SubscriptionManager Proxy interface for testing
199     public interface SubscriptionManagerProxy {
getDefaultVoicePhoneId()200         int getDefaultVoicePhoneId();
getSimStateForSlotIdx(int slotId)201         int getSimStateForSlotIdx(int slotId);
getPhoneId(int subId)202         int getPhoneId(int subId);
203     }
204 
205     private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() {
206         @Override
207         public int getDefaultVoicePhoneId() {
208             return SubscriptionManager.getDefaultVoicePhoneId();
209         }
210 
211         @Override
212         public int getSimStateForSlotIdx(int slotId) {
213             return SubscriptionManager.getSimStateForSlotIndex(slotId);
214         }
215 
216         @Override
217         public int getPhoneId(int subId) {
218             return SubscriptionManager.getPhoneId(subId);
219         }
220     };
221 
222     // TelephonyManager Proxy interface for testing
223     @VisibleForTesting
224     public interface TelephonyManagerProxy {
getPhoneCount()225         int getPhoneCount();
hasIccCard(int slotId)226         boolean hasIccCard(int slotId);
isCurrentEmergencyNumber(String number)227         boolean isCurrentEmergencyNumber(String number);
getCurrentEmergencyNumberList()228         Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList();
229     }
230 
231     private TelephonyManagerProxy mTelephonyManagerProxy;
232 
233     private class TelephonyManagerProxyImpl implements TelephonyManagerProxy {
234         private final TelephonyManager mTelephonyManager;
235 
236 
TelephonyManagerProxyImpl(Context context)237         TelephonyManagerProxyImpl(Context context) {
238             mTelephonyManager = new TelephonyManager(context);
239         }
240 
241         @Override
getPhoneCount()242         public int getPhoneCount() {
243             return mTelephonyManager.getPhoneCount();
244         }
245 
246         @Override
hasIccCard(int slotId)247         public boolean hasIccCard(int slotId) {
248             return mTelephonyManager.hasIccCard(slotId);
249         }
250 
251         @Override
isCurrentEmergencyNumber(String number)252         public boolean isCurrentEmergencyNumber(String number) {
253             return mTelephonyManager.isEmergencyNumber(number);
254         }
255 
256         @Override
getCurrentEmergencyNumberList()257         public Map<Integer, List<EmergencyNumber>> getCurrentEmergencyNumberList() {
258             return mTelephonyManager.getEmergencyNumberList();
259         }
260     }
261 
262     //PhoneFactory proxy interface for testing
263     @VisibleForTesting
264     public interface PhoneFactoryProxy {
getPhone(int index)265         Phone getPhone(int index);
getDefaultPhone()266         Phone getDefaultPhone();
getPhones()267         Phone[] getPhones();
268     }
269 
270     private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
271         @Override
272         public Phone getPhone(int index) {
273             return PhoneFactory.getPhone(index);
274         }
275 
276         @Override
277         public Phone getDefaultPhone() {
278             return PhoneFactory.getDefaultPhone();
279         }
280 
281         @Override
282         public Phone[] getPhones() {
283             return PhoneFactory.getPhones();
284         }
285     };
286 
287     @VisibleForTesting
setSubscriptionManagerProxy(SubscriptionManagerProxy proxy)288     public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) {
289         mSubscriptionManagerProxy = proxy;
290     }
291 
292     @VisibleForTesting
setTelephonyManagerProxy(TelephonyManagerProxy proxy)293     public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) {
294         mTelephonyManagerProxy = proxy;
295     }
296 
297     @VisibleForTesting
setPhoneFactoryProxy(PhoneFactoryProxy proxy)298     public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) {
299         mPhoneFactoryProxy = proxy;
300     }
301 
302     /**
303      * A listener to actionable events specific to the TelephonyConnection.
304      */
305     private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
306             new TelephonyConnection.TelephonyConnectionListener() {
307         @Override
308         public void onOriginalConnectionConfigured(TelephonyConnection c) {
309             addConnectionToConferenceController(c);
310         }
311 
312         @Override
313         public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) {
314             retryOutgoingOriginalConnection(c, isPermanentFailure);
315         }
316     };
317 
318     @Override
onCreate()319     public void onCreate() {
320         super.onCreate();
321         Log.initLogging(this);
322         setTelephonyManagerProxy(new TelephonyManagerProxyImpl(getApplicationContext()));
323         mExpectedComponentName = new ComponentName(this, this.getClass());
324         mEmergencyTonePlayer = new EmergencyTonePlayer(this);
325         TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
326         mHoldTracker = new HoldTracker();
327         mIsTtyEnabled = isTtyModeEnabled(getApplicationContext());
328 
329         IntentFilter intentFilter = new IntentFilter(
330                 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
331         registerReceiver(mTtyBroadcastReceiver, intentFilter);
332     }
333 
334     @Override
onUnbind(Intent intent)335     public boolean onUnbind(Intent intent) {
336         unregisterReceiver(mTtyBroadcastReceiver);
337         return super.onUnbind(intent);
338     }
339 
340     @Override
onCreateOutgoingConnection( PhoneAccountHandle connectionManagerPhoneAccount, final ConnectionRequest request)341     public Connection onCreateOutgoingConnection(
342             PhoneAccountHandle connectionManagerPhoneAccount,
343             final ConnectionRequest request) {
344         Log.i(this, "onCreateOutgoingConnection, request: " + request);
345 
346         Uri handle = request.getAddress();
347         if (handle == null) {
348             Log.d(this, "onCreateOutgoingConnection, handle is null");
349             return Connection.createFailedConnection(
350                     DisconnectCauseUtil.toTelecomDisconnectCause(
351                             android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
352                             "No phone number supplied"));
353         }
354 
355         String scheme = handle.getScheme();
356         String number;
357         if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
358             // TODO: We don't check for SecurityException here (requires
359             // CALL_PRIVILEGED permission).
360             final Phone phone = getPhoneForAccount(request.getAccountHandle(),
361                     false /* isEmergencyCall */, null /* not an emergency call */);
362             if (phone == null) {
363                 Log.d(this, "onCreateOutgoingConnection, phone is null");
364                 return Connection.createFailedConnection(
365                         DisconnectCauseUtil.toTelecomDisconnectCause(
366                                 android.telephony.DisconnectCause.OUT_OF_SERVICE,
367                                 "Phone is null"));
368             }
369             number = phone.getVoiceMailNumber();
370             if (TextUtils.isEmpty(number)) {
371                 Log.d(this, "onCreateOutgoingConnection, no voicemail number set.");
372                 return Connection.createFailedConnection(
373                         DisconnectCauseUtil.toTelecomDisconnectCause(
374                                 android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING,
375                                 "Voicemail scheme provided but no voicemail number set.",
376                                 phone.getPhoneId()));
377             }
378 
379             // Convert voicemail: to tel:
380             handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
381         } else {
382             if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
383                 Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme);
384                 return Connection.createFailedConnection(
385                         DisconnectCauseUtil.toTelecomDisconnectCause(
386                                 android.telephony.DisconnectCause.INVALID_NUMBER,
387                                 "Handle scheme is not type tel"));
388             }
389 
390             number = handle.getSchemeSpecificPart();
391             if (TextUtils.isEmpty(number)) {
392                 Log.d(this, "onCreateOutgoingConnection, unable to parse number");
393                 return Connection.createFailedConnection(
394                         DisconnectCauseUtil.toTelecomDisconnectCause(
395                                 android.telephony.DisconnectCause.INVALID_NUMBER,
396                                 "Unable to parse number"));
397             }
398 
399             final Phone phone = getPhoneForAccount(request.getAccountHandle(),
400                     false /* isEmergencyCall*/, null /* not an emergency call */);
401             if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) {
402                 // Obtain the configuration for the outgoing phone's SIM. If the outgoing number
403                 // matches the *228 regex pattern, fail the call. This number is used for OTASP, and
404                 // when dialed could lock LTE SIMs to 3G if not prohibited..
405                 boolean disableActivation = false;
406                 CarrierConfigManager cfgManager = (CarrierConfigManager)
407                         phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
408                 if (cfgManager != null) {
409                     disableActivation = cfgManager.getConfigForSubId(phone.getSubId())
410                             .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL);
411                 }
412 
413                 if (disableActivation) {
414                     return Connection.createFailedConnection(
415                             DisconnectCauseUtil.toTelecomDisconnectCause(
416                                     android.telephony.DisconnectCause
417                                             .CDMA_ALREADY_ACTIVATED,
418                                     "Tried to dial *228",
419                                     phone.getPhoneId()));
420                 }
421             }
422         }
423 
424         final boolean isEmergencyNumber = mTelephonyManagerProxy.isCurrentEmergencyNumber(number);
425         // Find out if this is a test emergency number
426         final boolean isTestEmergencyNumber = isEmergencyNumberTestNumber(number);
427 
428         // Convert into emergency number if necessary
429         // This is required in some regions (e.g. Taiwan).
430         if (isEmergencyNumber) {
431             final Phone phone = getPhoneForAccount(request.getAccountHandle(), false,
432                     handle.getSchemeSpecificPart());
433             // We only do the conversion if the phone is not in service. The un-converted
434             // emergency numbers will go to the correct destination when the phone is in-service,
435             // so they will only need the special emergency call setup when the phone is out of
436             // service.
437             if (phone == null || phone.getServiceState().getState()
438                     != ServiceState.STATE_IN_SERVICE) {
439                 String convertedNumber = PhoneNumberUtils.convertToEmergencyNumber(this, number);
440                 if (!TextUtils.equals(convertedNumber, number)) {
441                     Log.i(this, "onCreateOutgoingConnection, converted to emergency number");
442                     number = convertedNumber;
443                     handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
444                 }
445             }
446         }
447         final String numberToDial = number;
448 
449 
450         final boolean isAirplaneModeOn = Settings.Global.getInt(getContentResolver(),
451                 Settings.Global.AIRPLANE_MODE_ON, 0) > 0;
452 
453         boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn))
454                 || isRadioPowerDownOnBluetooth();
455 
456         if (needToTurnOnRadio) {
457             final Uri resultHandle = handle;
458             // By default, Connection based on the default Phone, since we need to return to Telecom
459             // now.
460             final int originalPhoneType = PhoneFactory.getDefaultPhone().getPhoneType();
461             final Connection resultConnection = getTelephonyConnection(request, numberToDial,
462                     isEmergencyNumber, resultHandle, PhoneFactory.getDefaultPhone());
463             if (mRadioOnHelper == null) {
464                 mRadioOnHelper = new RadioOnHelper(this);
465             }
466             mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
467                 @Override
468                 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
469                     handleOnComplete(isRadioReady, isEmergencyNumber, resultConnection, request,
470                             numberToDial, resultHandle, originalPhoneType);
471                 }
472 
473                 @Override
474                 public boolean isOkToCall(Phone phone, int serviceState) {
475                     // HAL 1.4 introduced a new variant of dial for emergency calls, which includes
476                     // an isTesting parameter. For HAL 1.4+, do not wait for IN_SERVICE, this will
477                     // be handled at the RIL/vendor level by emergencyDial(...).
478                     boolean waitForInServiceToDialEmergency = isTestEmergencyNumber
479                             && phone.getHalVersion().less(RIL.RADIO_HAL_VERSION_1_4);
480                     if (isEmergencyNumber && !waitForInServiceToDialEmergency) {
481                         // We currently only look to make sure that the radio is on before dialing.
482                         // We should be able to make emergency calls at any time after the radio has
483                         // been powered on and isn't in the UNAVAILABLE state, even if it is
484                         // reporting the OUT_OF_SERVICE state.
485                         return (phone.getState() == PhoneConstants.State.OFFHOOK)
486                             || phone.getServiceState().getState() != ServiceState.STATE_POWER_OFF;
487                     } else {
488                         // Wait until we are in service and ready to make calls. This can happen
489                         // when we power down the radio on bluetooth to save power on watches or if
490                         // it is a test emergency number and we have to wait for the device to move
491                         // IN_SERVICE before the call can take place over normal routing.
492                         return (phone.getState() == PhoneConstants.State.OFFHOOK)
493                             || serviceState == ServiceState.STATE_IN_SERVICE;
494                     }
495                 }
496             });
497             // Return the still unconnected GsmConnection and wait for the Radios to boot before
498             // connecting it to the underlying Phone.
499             return resultConnection;
500         } else {
501             if (!canAddCall() && !isEmergencyNumber) {
502                 Log.d(this, "onCreateOutgoingConnection, cannot add call .");
503                 return Connection.createFailedConnection(
504                         new DisconnectCause(DisconnectCause.ERROR,
505                                 getApplicationContext().getText(
506                                         R.string.incall_error_cannot_add_call),
507                                 getApplicationContext().getText(
508                                         R.string.incall_error_cannot_add_call),
509                                 "Add call restricted due to ongoing video call"));
510             }
511 
512             // Get the right phone object from the account data passed in.
513             final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
514                     /* Note: when not an emergency, handle can be null for unknown callers */
515                     handle == null ? null : handle.getSchemeSpecificPart());
516             if (!isEmergencyNumber) {
517                 final Connection resultConnection = getTelephonyConnection(request, numberToDial,
518                         false, handle, phone);
519                 return placeOutgoingConnection(request, resultConnection, phone);
520             } else {
521                 final Connection resultConnection = getTelephonyConnection(request, numberToDial,
522                         true, handle, phone);
523                 CompletableFuture<Boolean> phoneFuture = delayDialForDdsSwitch(phone);
524                 phoneFuture.whenComplete((result, error) -> {
525                     if (error != null) {
526                         Log.w(this, "onCreateOutgoingConn - delayDialForDdsSwitch exception= "
527                                 + error.getMessage());
528                     }
529                     Log.i(this, "onCreateOutgoingConn - delayDialForDdsSwitch result = " + result);
530                     placeOutgoingConnection(request, resultConnection, phone);
531                 });
532                 return resultConnection;
533             }
534         }
535     }
536 
placeOutgoingConnection(ConnectionRequest request, Connection resultConnection, Phone phone)537     private Connection placeOutgoingConnection(ConnectionRequest request,
538             Connection resultConnection, Phone phone) {
539         // If there was a failure, the resulting connection will not be a TelephonyConnection,
540         // so don't place the call!
541         if (resultConnection instanceof TelephonyConnection) {
542             if (request.getExtras() != null && request.getExtras().getBoolean(
543                     TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) {
544                 ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true);
545             }
546             placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
547         }
548         return resultConnection;
549     }
550 
isEmergencyNumberTestNumber(String number)551     private boolean isEmergencyNumberTestNumber(String number) {
552         number = PhoneNumberUtils.stripSeparators(number);
553         Map<Integer, List<EmergencyNumber>> list =
554                 mTelephonyManagerProxy.getCurrentEmergencyNumberList();
555         // Do not worry about which subscription the test emergency call is on yet, only detect that
556         // it is an emergency.
557         for (Integer sub : list.keySet()) {
558             for (EmergencyNumber eNumber : list.get(sub)) {
559                 if (number.equals(eNumber.getNumber())
560                         && eNumber.isFromSources(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_TEST)) {
561                     Log.i(this, "isEmergencyNumberTestNumber: " + number + " has been detected as "
562                             + "a test emergency number.,");
563                     return true;
564                 }
565             }
566         }
567         return false;
568     }
569 
570     /**
571      * Whether the cellular radio is power off because the device is on Bluetooth.
572      */
isRadioPowerDownOnBluetooth()573     private boolean isRadioPowerDownOnBluetooth() {
574         final Context context = getApplicationContext();
575         final boolean allowed = context.getResources().getBoolean(
576                 R.bool.config_allowRadioPowerDownOnBluetooth);
577         final int cellOn = Settings.Global.getInt(context.getContentResolver(),
578                 Settings.Global.CELL_ON,
579                 PhoneConstants.CELL_ON_FLAG);
580         return (allowed && cellOn == PhoneConstants.CELL_ON_FLAG && !isRadioOn());
581     }
582 
583     /**
584      * Handle the onComplete callback of RadioOnStateListener.
585      */
handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber, Connection originalConnection, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType)586     private void handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber,
587             Connection originalConnection, ConnectionRequest request, String numberToDial,
588             Uri handle, int originalPhoneType) {
589         // Make sure the Call has not already been canceled by the user.
590         if (originalConnection.getState() == Connection.STATE_DISCONNECTED) {
591             Log.i(this, "Call disconnected before the outgoing call was placed. Skipping call "
592                     + "placement.");
593             return;
594         }
595         // Get the right phone object since the radio has been turned on successfully.
596         if (isRadioReady) {
597             final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber,
598                     /* Note: when not an emergency, handle can be null for unknown callers */
599                     handle == null ? null : handle.getSchemeSpecificPart());
600             if (!isEmergencyNumber) {
601                 adjustAndPlaceOutgoingConnection(phone, originalConnection, request, numberToDial,
602                         handle, originalPhoneType, false);
603             } else {
604                 delayDialForDdsSwitch(phone).whenComplete((result, error) -> {
605                     if (error != null) {
606                         Log.w(this, "handleOnComplete - delayDialForDdsSwitch exception= "
607                                 + error.getMessage());
608                     }
609                     Log.i(this, "handleOnComplete - delayDialForDdsSwitch result = " + result);
610                     adjustAndPlaceOutgoingConnection(phone, originalConnection, request,
611                             numberToDial, handle, originalPhoneType, true);
612                 });
613             }
614 
615         } else {
616             Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
617             originalConnection.setDisconnected(
618                     DisconnectCauseUtil.toTelecomDisconnectCause(
619                             android.telephony.DisconnectCause.POWER_OFF,
620                             "Failed to turn on radio."));
621             originalConnection.destroy();
622         }
623     }
624 
adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate, ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType, boolean isEmergencyNumber)625     private void adjustAndPlaceOutgoingConnection(Phone phone, Connection connectionToEvaluate,
626             ConnectionRequest request, String numberToDial, Uri handle, int originalPhoneType,
627             boolean isEmergencyNumber) {
628         // If the PhoneType of the Phone being used is different than the Default Phone, then we
629         // need to create a new Connection using that PhoneType and replace it in Telecom.
630         if (phone.getPhoneType() != originalPhoneType) {
631             Connection repConnection = getTelephonyConnection(request, numberToDial,
632                     isEmergencyNumber, handle, phone);
633             // If there was a failure, the resulting connection will not be a TelephonyConnection,
634             // so don't place the call, just return!
635             if (repConnection instanceof TelephonyConnection) {
636                 placeOutgoingConnection((TelephonyConnection) repConnection, phone, request);
637             }
638             // Notify Telecom of the new Connection type.
639             // TODO: Switch out the underlying connection instead of creating a new
640             // one and causing UI Jank.
641             boolean noActiveSimCard = SubscriptionController.getInstance()
642                     .getActiveSubInfoCount(phone.getContext().getOpPackageName()) == 0;
643             // If there's no active sim card and the device is in emergency mode, use E account.
644             addExistingConnection(PhoneUtils.makePstnPhoneAccountHandleWithPrefix(
645                     phone, "", isEmergencyNumber && noActiveSimCard), repConnection);
646             // Remove the old connection from Telecom after.
647             connectionToEvaluate.setDisconnected(
648                     DisconnectCauseUtil.toTelecomDisconnectCause(
649                             android.telephony.DisconnectCause.OUTGOING_CANCELED,
650                             "Reconnecting outgoing Emergency Call.",
651                             phone.getPhoneId()));
652             connectionToEvaluate.destroy();
653         } else {
654             placeOutgoingConnection((TelephonyConnection) connectionToEvaluate, phone, request);
655         }
656     }
657 
658     /**
659      * @return {@code true} if any other call is disabling the ability to add calls, {@code false}
660      *      otherwise.
661      */
canAddCall()662     private boolean canAddCall() {
663         Collection<Connection> connections = getAllConnections();
664         for (Connection connection : connections) {
665             if (connection.getExtras() != null &&
666                     connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) {
667                 return false;
668             }
669         }
670         return true;
671     }
672 
getTelephonyConnection(final ConnectionRequest request, final String number, boolean isEmergencyNumber, final Uri handle, Phone phone)673     private Connection getTelephonyConnection(final ConnectionRequest request, final String number,
674             boolean isEmergencyNumber, final Uri handle, Phone phone) {
675 
676         if (phone == null) {
677             final Context context = getApplicationContext();
678             if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) {
679                 // Check SIM card state before the outgoing call.
680                 // Start the SIM unlock activity if PIN_REQUIRED.
681                 final Phone defaultPhone = mPhoneFactoryProxy.getDefaultPhone();
682                 final IccCard icc = defaultPhone.getIccCard();
683                 IccCardConstants.State simState = IccCardConstants.State.UNKNOWN;
684                 if (icc != null) {
685                     simState = icc.getState();
686                 }
687                 if (simState == IccCardConstants.State.PIN_REQUIRED) {
688                     final String simUnlockUiPackage = context.getResources().getString(
689                             R.string.config_simUnlockUiPackage);
690                     final String simUnlockUiClass = context.getResources().getString(
691                             R.string.config_simUnlockUiClass);
692                     if (simUnlockUiPackage != null && simUnlockUiClass != null) {
693                         Intent simUnlockIntent = new Intent().setComponent(new ComponentName(
694                                 simUnlockUiPackage, simUnlockUiClass));
695                         simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
696                         try {
697                             context.startActivity(simUnlockIntent);
698                         } catch (ActivityNotFoundException exception) {
699                             Log.e(this, exception, "Unable to find SIM unlock UI activity.");
700                         }
701                     }
702                     return Connection.createFailedConnection(
703                             DisconnectCauseUtil.toTelecomDisconnectCause(
704                                     android.telephony.DisconnectCause.OUT_OF_SERVICE,
705                                     "SIM_STATE_PIN_REQUIRED"));
706                 }
707             }
708 
709             Log.d(this, "onCreateOutgoingConnection, phone is null");
710             return Connection.createFailedConnection(
711                     DisconnectCauseUtil.toTelecomDisconnectCause(
712                             android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null"));
713         }
714 
715         // Check both voice & data RAT to enable normal CS call,
716         // when voice RAT is OOS but Data RAT is present.
717         int state = phone.getServiceState().getState();
718         if (state == ServiceState.STATE_OUT_OF_SERVICE) {
719             int dataNetType = phone.getServiceState().getDataNetworkType();
720             if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
721                     dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
722                 state = phone.getServiceState().getDataRegState();
723             }
724         }
725 
726         // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if
727         // carrier configuration specifies that we cannot make non-emergency calls in ECM mode.
728         if (!isEmergencyNumber && phone.isInEcm()) {
729             boolean allowNonEmergencyCalls = true;
730             CarrierConfigManager cfgManager = (CarrierConfigManager)
731                     phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
732             if (cfgManager != null) {
733                 allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId())
734                         .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL);
735             }
736 
737             if (!allowNonEmergencyCalls) {
738                 return Connection.createFailedConnection(
739                         DisconnectCauseUtil.toTelecomDisconnectCause(
740                                 android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY,
741                                 "Cannot make non-emergency call in ECM mode.",
742                                 phone.getPhoneId()));
743             }
744         }
745 
746         if (!isEmergencyNumber) {
747             switch (state) {
748                 case ServiceState.STATE_IN_SERVICE:
749                 case ServiceState.STATE_EMERGENCY_ONLY:
750                     break;
751                 case ServiceState.STATE_OUT_OF_SERVICE:
752                     if (phone.isUtEnabled() && number.endsWith("#")) {
753                         Log.d(this, "onCreateOutgoingConnection dial for UT");
754                         break;
755                     } else {
756                         return Connection.createFailedConnection(
757                                 DisconnectCauseUtil.toTelecomDisconnectCause(
758                                         android.telephony.DisconnectCause.OUT_OF_SERVICE,
759                                         "ServiceState.STATE_OUT_OF_SERVICE",
760                                         phone.getPhoneId()));
761                     }
762                 case ServiceState.STATE_POWER_OFF:
763                     // Don't disconnect if radio is power off because the device is on Bluetooth.
764                     if (isRadioPowerDownOnBluetooth()) {
765                         break;
766                     }
767                     return Connection.createFailedConnection(
768                             DisconnectCauseUtil.toTelecomDisconnectCause(
769                                     android.telephony.DisconnectCause.POWER_OFF,
770                                     "ServiceState.STATE_POWER_OFF",
771                                     phone.getPhoneId()));
772                 default:
773                     Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state);
774                     return Connection.createFailedConnection(
775                             DisconnectCauseUtil.toTelecomDisconnectCause(
776                                     android.telephony.DisconnectCause.OUTGOING_FAILURE,
777                                     "Unknown service state " + state,
778                                     phone.getPhoneId()));
779             }
780         }
781 
782         final Context context = getApplicationContext();
783         final boolean isTtyModeEnabled = isTtyModeEnabled(context);
784         if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled
785                 && !isEmergencyNumber) {
786             return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
787                     android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED,
788                     null, phone.getPhoneId()));
789         }
790 
791         // Check for additional limits on CDMA phones.
792         final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone);
793         if (failedConnection != null) {
794             return failedConnection;
795         }
796 
797         // Check roaming status to see if we should block custom call forwarding codes
798         if (blockCallForwardingNumberWhileRoaming(phone, number)) {
799             return Connection.createFailedConnection(
800                     DisconnectCauseUtil.toTelecomDisconnectCause(
801                             android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING,
802                             "Call forwarding while roaming",
803                             phone.getPhoneId()));
804         }
805 
806 
807         final TelephonyConnection connection =
808                 createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(),
809                         request.getTelecomCallId(), request.getAddress(), request.getVideoState());
810         if (connection == null) {
811             return Connection.createFailedConnection(
812                     DisconnectCauseUtil.toTelecomDisconnectCause(
813                             android.telephony.DisconnectCause.OUTGOING_FAILURE,
814                             "Invalid phone type",
815                             phone.getPhoneId()));
816         }
817         connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
818         connection.setInitializing();
819         connection.setVideoState(request.getVideoState());
820         connection.setRttTextStream(request.getRttTextStream());
821         connection.setTtyEnabled(isTtyModeEnabled);
822         return connection;
823     }
824 
825     @Override
onCreateIncomingConnection( PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)826     public Connection onCreateIncomingConnection(
827             PhoneAccountHandle connectionManagerPhoneAccount,
828             ConnectionRequest request) {
829         Log.i(this, "onCreateIncomingConnection, request: " + request);
830         // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM),
831         // make sure the PhoneAccount lookup retrieves the default Emergency Phone.
832         PhoneAccountHandle accountHandle = request.getAccountHandle();
833         boolean isEmergency = false;
834         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
835                 accountHandle.getId())) {
836             Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " +
837                     "Treat as an Emergency Call.");
838             isEmergency = true;
839         }
840         Phone phone = getPhoneForAccount(accountHandle, isEmergency,
841                 /* Note: when not an emergency, handle can be null for unknown callers */
842                 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart());
843         if (phone == null) {
844             return Connection.createFailedConnection(
845                     DisconnectCauseUtil.toTelecomDisconnectCause(
846                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
847                             "Phone is null"));
848         }
849 
850         Call call = phone.getRingingCall();
851         if (!call.getState().isRinging()) {
852             Log.i(this, "onCreateIncomingConnection, no ringing call");
853             return Connection.createFailedConnection(
854                     DisconnectCauseUtil.toTelecomDisconnectCause(
855                             android.telephony.DisconnectCause.INCOMING_MISSED,
856                             "Found no ringing call",
857                             phone.getPhoneId()));
858         }
859 
860         com.android.internal.telephony.Connection originalConnection =
861                 call.getState() == Call.State.WAITING ?
862                     call.getLatestConnection() : call.getEarliestConnection();
863         if (isOriginalConnectionKnown(originalConnection)) {
864             Log.i(this, "onCreateIncomingConnection, original connection already registered");
865             return Connection.createCanceledConnection();
866         }
867 
868         // We should rely on the originalConnection to get the video state.  The request coming
869         // from Telecom does not know the video state of the incoming call.
870         int videoState = originalConnection != null ? originalConnection.getVideoState() :
871                 VideoProfile.STATE_AUDIO_ONLY;
872 
873         TelephonyConnection connection =
874                 createConnectionFor(phone, originalConnection, false /* isOutgoing */,
875                         request.getAccountHandle(), request.getTelecomCallId(),
876                         request.getAddress(), videoState);
877         handleIncomingRtt(request, originalConnection);
878         if (connection == null) {
879             return Connection.createCanceledConnection();
880         } else {
881             return connection;
882         }
883     }
884 
handleIncomingRtt(ConnectionRequest request, com.android.internal.telephony.Connection originalConnection)885     private void handleIncomingRtt(ConnectionRequest request,
886             com.android.internal.telephony.Connection originalConnection) {
887         if (originalConnection == null
888                 || originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) {
889             if (request.isRequestingRtt()) {
890                 Log.w(this, "Requesting RTT on non-IMS call, ignoring");
891             }
892             return;
893         }
894 
895         ImsPhoneConnection imsOriginalConnection = (ImsPhoneConnection) originalConnection;
896         if (!request.isRequestingRtt()) {
897             if (imsOriginalConnection.isRttEnabledForCall()) {
898                 Log.w(this, "Incoming call requested RTT but we did not get a RttTextStream");
899             }
900             return;
901         }
902 
903         Log.i(this, "Setting RTT stream on ImsPhoneConnection in case we need it later");
904         imsOriginalConnection.setCurrentRttTextStream(request.getRttTextStream());
905 
906         if (!imsOriginalConnection.isRttEnabledForCall()) {
907             if (request.isRequestingRtt()) {
908                 Log.w(this, "Incoming call processed as RTT but did not come in as one. Ignoring");
909             }
910             return;
911         }
912 
913         Log.i(this, "Setting the call to be answered with RTT on.");
914         imsOriginalConnection.getImsCall().setAnswerWithRtt();
915     }
916 
917     /**
918      * Called by the {@link ConnectionService} when a newly created {@link Connection} has been
919      * added to the {@link ConnectionService} and sent to Telecom.  Here it is safe to send
920      * connection events.
921      *
922      * @param connection the {@link Connection}.
923      */
924     @Override
onCreateConnectionComplete(Connection connection)925     public void onCreateConnectionComplete(Connection connection) {
926         if (connection instanceof TelephonyConnection) {
927             TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
928             maybeSendInternationalCallEvent(telephonyConnection);
929         }
930     }
931 
932     @Override
triggerConferenceRecalculate()933     public void triggerConferenceRecalculate() {
934         if (mTelephonyConferenceController.shouldRecalculate()) {
935             mTelephonyConferenceController.recalculate();
936         }
937     }
938 
939     @Override
onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request)940     public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
941             ConnectionRequest request) {
942         Log.i(this, "onCreateUnknownConnection, request: " + request);
943         // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's
944         // Emergency PhoneAccount
945         PhoneAccountHandle accountHandle = request.getAccountHandle();
946         boolean isEmergency = false;
947         if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
948                 accountHandle.getId())) {
949             Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " +
950                     "Treat as an Emergency Call.");
951             isEmergency = true;
952         }
953         Phone phone = getPhoneForAccount(accountHandle, isEmergency,
954                 /* Note: when not an emergency, handle can be null for unknown callers */
955                 request.getAddress() == null ? null : request.getAddress().getSchemeSpecificPart());
956         if (phone == null) {
957             return Connection.createFailedConnection(
958                     DisconnectCauseUtil.toTelecomDisconnectCause(
959                             android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
960                             "Phone is null"));
961         }
962         Bundle extras = request.getExtras();
963 
964         final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>();
965 
966         // Handle the case where an unknown connection has an IMS external call ID specified; we can
967         // skip the rest of the guesswork and just grad that unknown call now.
968         if (phone.getImsPhone() != null && extras != null &&
969                 extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) {
970 
971             ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
972             ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker();
973             int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID,
974                     -1);
975 
976             if (externalCallTracker != null) {
977                 com.android.internal.telephony.Connection connection =
978                         externalCallTracker.getConnectionById(externalCallId);
979 
980                 if (connection != null) {
981                     allConnections.add(connection);
982                 }
983             }
984         }
985 
986         if (allConnections.isEmpty()) {
987             final Call ringingCall = phone.getRingingCall();
988             if (ringingCall.hasConnections()) {
989                 allConnections.addAll(ringingCall.getConnections());
990             }
991             final Call foregroundCall = phone.getForegroundCall();
992             if ((foregroundCall.getState() != Call.State.DISCONNECTED)
993                     && (foregroundCall.hasConnections())) {
994                 allConnections.addAll(foregroundCall.getConnections());
995             }
996             if (phone.getImsPhone() != null) {
997                 final Call imsFgCall = phone.getImsPhone().getForegroundCall();
998                 if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall
999                         .hasConnections()) {
1000                     allConnections.addAll(imsFgCall.getConnections());
1001                 }
1002             }
1003             final Call backgroundCall = phone.getBackgroundCall();
1004             if (backgroundCall.hasConnections()) {
1005                 allConnections.addAll(phone.getBackgroundCall().getConnections());
1006             }
1007         }
1008 
1009         com.android.internal.telephony.Connection unknownConnection = null;
1010         for (com.android.internal.telephony.Connection telephonyConnection : allConnections) {
1011             if (!isOriginalConnectionKnown(telephonyConnection)) {
1012                 unknownConnection = telephonyConnection;
1013                 Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection);
1014                 break;
1015             }
1016         }
1017 
1018         if (unknownConnection == null) {
1019             Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection.");
1020             return Connection.createCanceledConnection();
1021         }
1022 
1023         // We should rely on the originalConnection to get the video state.  The request coming
1024         // from Telecom does not know the video state of the unknown call.
1025         int videoState = unknownConnection != null ? unknownConnection.getVideoState() :
1026                 VideoProfile.STATE_AUDIO_ONLY;
1027 
1028         TelephonyConnection connection =
1029                 createConnectionFor(phone, unknownConnection,
1030                         !unknownConnection.isIncoming() /* isOutgoing */,
1031                         request.getAccountHandle(), request.getTelecomCallId(),
1032                         request.getAddress(), videoState);
1033 
1034         if (connection == null) {
1035             return Connection.createCanceledConnection();
1036         } else {
1037             connection.updateState();
1038             return connection;
1039         }
1040     }
1041 
1042     /**
1043      * Conferences two connections.
1044      *
1045      * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has
1046      * a limitation in that it can only specify conferenceables which are instances of
1047      * {@link android.telecom.RemoteConnection}.  In the case of an {@link ImsConference}, the
1048      * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge
1049      * a {@link Conference} and a {@link Connection}.  As a result when, merging a
1050      * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference}
1051      * require merging a {@link ConferenceParticipantConnection} which is a child of the
1052      * {@link Conference} with a {@link TelephonyConnection}.  The
1053      * {@link ConferenceParticipantConnection} class does not have the capability to initiate a
1054      * conference merge, so we need to call
1055      * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or
1056      * {@code connection2}, one of which is an instance of {@link TelephonyConnection}.
1057      *
1058      * @param connection1 A connection to merge into a conference call.
1059      * @param connection2 A connection to merge into a conference call.
1060      */
1061     @Override
onConference(Connection connection1, Connection connection2)1062     public void onConference(Connection connection1, Connection connection2) {
1063         if (connection1 instanceof TelephonyConnection) {
1064             ((TelephonyConnection) connection1).performConference(connection2);
1065         } else if (connection2 instanceof TelephonyConnection) {
1066             ((TelephonyConnection) connection2).performConference(connection1);
1067         } else {
1068             Log.w(this, "onConference - cannot merge connections " +
1069                     "Connection1: %s, Connection2: %2", connection1, connection2);
1070         }
1071     }
1072 
1073     @Override
onConnectionAdded(Connection connection)1074     public void onConnectionAdded(Connection connection) {
1075         if (connection instanceof Holdable && !isExternalConnection(connection)) {
1076             connection.addConnectionListener(mConnectionListener);
1077             mHoldTracker.addHoldable(
1078                     connection.getPhoneAccountHandle(), (Holdable) connection);
1079         }
1080     }
1081 
1082     @Override
onConnectionRemoved(Connection connection)1083     public void onConnectionRemoved(Connection connection) {
1084         if (connection instanceof Holdable && !isExternalConnection(connection)) {
1085             mHoldTracker.removeHoldable(connection.getPhoneAccountHandle(), (Holdable) connection);
1086         }
1087     }
1088 
1089     @Override
onConferenceAdded(Conference conference)1090     public void onConferenceAdded(Conference conference) {
1091         if (conference instanceof Holdable) {
1092             mHoldTracker.addHoldable(conference.getPhoneAccountHandle(), (Holdable) conference);
1093         }
1094     }
1095 
1096     @Override
onConferenceRemoved(Conference conference)1097     public void onConferenceRemoved(Conference conference) {
1098         if (conference instanceof Holdable) {
1099             mHoldTracker.removeHoldable(conference.getPhoneAccountHandle(), (Holdable) conference);
1100         }
1101     }
1102 
isExternalConnection(Connection connection)1103     private boolean isExternalConnection(Connection connection) {
1104         return (connection.getConnectionProperties() & Connection.PROPERTY_IS_EXTERNAL_CALL)
1105                 == Connection.PROPERTY_IS_EXTERNAL_CALL;
1106     }
1107 
blockCallForwardingNumberWhileRoaming(Phone phone, String number)1108     private boolean blockCallForwardingNumberWhileRoaming(Phone phone, String number) {
1109         if (phone == null || TextUtils.isEmpty(number) || !phone.getServiceState().getRoaming()) {
1110             return false;
1111         }
1112         String[] blockPrefixes = null;
1113         CarrierConfigManager cfgManager = (CarrierConfigManager)
1114                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1115         if (cfgManager != null) {
1116             blockPrefixes = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray(
1117                     CarrierConfigManager.KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY);
1118         }
1119 
1120         if (blockPrefixes != null) {
1121             for (String prefix : blockPrefixes) {
1122                 if (number.startsWith(prefix)) {
1123                     return true;
1124                 }
1125             }
1126         }
1127         return false;
1128     }
1129 
isRadioOn()1130     private boolean isRadioOn() {
1131         boolean result = false;
1132         for (Phone phone : mPhoneFactoryProxy.getPhones()) {
1133             result |= phone.isRadioOn();
1134         }
1135         return result;
1136     }
1137 
makeCachedConnectionPhonePair( TelephonyConnection c)1138     private Pair<WeakReference<TelephonyConnection>, Queue<Phone>> makeCachedConnectionPhonePair(
1139             TelephonyConnection c) {
1140         Queue<Phone> phones = new LinkedList<>(Arrays.asList(mPhoneFactoryProxy.getPhones()));
1141         return new Pair<>(new WeakReference<>(c), phones);
1142     }
1143 
1144     // Update the mEmergencyRetryCache by removing the Phone used to call the last failed emergency
1145     // number and then moving it to the back of the queue if it is not a permanent failure cause
1146     // from the modem.
updateCachedConnectionPhonePair(TelephonyConnection c, boolean isPermanentFailure)1147     private void updateCachedConnectionPhonePair(TelephonyConnection c,
1148             boolean isPermanentFailure) {
1149         // No cache exists, create a new one.
1150         if (mEmergencyRetryCache == null) {
1151             Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache");
1152             mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
1153         // Cache is stale, create a new one with the new TelephonyConnection.
1154         } else if (mEmergencyRetryCache.first.get() != c) {
1155             Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating.");
1156             mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
1157         }
1158 
1159         Queue<Phone> cachedPhones = mEmergencyRetryCache.second;
1160         // Need to refer default phone considering ImsPhone because
1161         // cachedPhones is a list that contains default phones.
1162         Phone phoneUsed = c.getPhone().getDefaultPhone();
1163         if (phoneUsed == null) {
1164             return;
1165         }
1166         // Remove phone used from the list, but for temporary fail cause, it will be added
1167         // back to list further in this method. However in case of permanent failure, the
1168         // phone shouldn't be reused, hence it will not be added back again.
1169         cachedPhones.remove(phoneUsed);
1170         Log.i(this, "updateCachedConnectionPhonePair, isPermanentFailure:" + isPermanentFailure);
1171         if (!isPermanentFailure) {
1172             // In case of temporary failure, add the phone back, this will result adding it
1173             // to tail of list mEmergencyRetryCache.second, giving other phone more
1174             // priority and that is what we want.
1175             cachedPhones.offer(phoneUsed);
1176         }
1177     }
1178 
1179     /**
1180      * Updates a cache containing all of the slots that are available for redial at any point.
1181      *
1182      * - If a Connection returns with the disconnect cause EMERGENCY_TEMP_FAILURE, keep that phone
1183      * in the cache, but move it to the lowest priority in the list. Then, place the emergency call
1184      * on the next phone in the list.
1185      * - If a Connection returns with the disconnect cause EMERGENCY_PERM_FAILURE, remove that phone
1186      * from the cache and pull another phone from the cache to place the emergency call.
1187      *
1188      * This will continue until there are no more slots to dial on.
1189      */
1190     @VisibleForTesting
retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure)1191     public void retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure) {
1192         int phoneId = (c.getPhone() == null) ? -1 : c.getPhone().getPhoneId();
1193         updateCachedConnectionPhonePair(c, isPermanentFailure);
1194         // Pull next phone to use from the cache or null if it is empty
1195         Phone newPhoneToUse = (mEmergencyRetryCache.second != null)
1196                 ? mEmergencyRetryCache.second.peek() : null;
1197         if (newPhoneToUse != null) {
1198             int videoState = c.getVideoState();
1199             Bundle connExtras = c.getExtras();
1200             Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse);
1201             c.clearOriginalConnection();
1202             if (phoneId != newPhoneToUse.getPhoneId()) updatePhoneAccount(c, newPhoneToUse);
1203             placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
1204         } else {
1205             // We have run out of Phones to use. Disconnect the call and destroy the connection.
1206             Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting.");
1207             c.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
1208             c.clearOriginalConnection();
1209             c.destroy();
1210         }
1211     }
1212 
updatePhoneAccount(TelephonyConnection connection, Phone phone)1213     private void updatePhoneAccount(TelephonyConnection connection, Phone phone) {
1214         PhoneAccountHandle pHandle = PhoneUtils.makePstnPhoneAccountHandle(phone);
1215         // For ECall handling on MSIM, until the request reaches here (i.e PhoneApp), we don't know
1216         // on which phone account ECall can be placed. After deciding, we should notify Telecom of
1217         // the change so that the proper PhoneAccount can be displayed.
1218         Log.i(this, "updatePhoneAccount setPhoneAccountHandle, account = " + pHandle);
1219         connection.setPhoneAccountHandle(pHandle);
1220     }
1221 
placeOutgoingConnection( TelephonyConnection connection, Phone phone, ConnectionRequest request)1222     private void placeOutgoingConnection(
1223             TelephonyConnection connection, Phone phone, ConnectionRequest request) {
1224         placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras());
1225     }
1226 
placeOutgoingConnection( TelephonyConnection connection, Phone phone, int videoState, Bundle extras)1227     private void placeOutgoingConnection(
1228             TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
1229         String number = connection.getAddress().getSchemeSpecificPart();
1230 
1231         com.android.internal.telephony.Connection originalConnection = null;
1232         try {
1233             if (phone != null) {
1234                 originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
1235                         .setVideoState(videoState)
1236                         .setIntentExtras(extras)
1237                         .setRttTextStream(connection.getRttTextStream())
1238                         .build());
1239             }
1240         } catch (CallStateException e) {
1241             Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
1242             int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
1243             switch (e.getError()) {
1244                 case CallStateException.ERROR_OUT_OF_SERVICE:
1245                     cause = android.telephony.DisconnectCause.OUT_OF_SERVICE;
1246                     break;
1247                 case CallStateException.ERROR_POWER_OFF:
1248                     cause = android.telephony.DisconnectCause.POWER_OFF;
1249                     break;
1250                 case CallStateException.ERROR_ALREADY_DIALING:
1251                     cause = android.telephony.DisconnectCause.ALREADY_DIALING;
1252                     break;
1253                 case CallStateException.ERROR_CALL_RINGING:
1254                     cause = android.telephony.DisconnectCause.CANT_CALL_WHILE_RINGING;
1255                     break;
1256                 case CallStateException.ERROR_CALLING_DISABLED:
1257                     cause = android.telephony.DisconnectCause.CALLING_DISABLED;
1258                     break;
1259                 case CallStateException.ERROR_TOO_MANY_CALLS:
1260                     cause = android.telephony.DisconnectCause.TOO_MANY_ONGOING_CALLS;
1261                     break;
1262                 case CallStateException.ERROR_OTASP_PROVISIONING_IN_PROCESS:
1263                     cause = android.telephony.DisconnectCause.OTASP_PROVISIONING_IN_PROCESS;
1264                     break;
1265             }
1266             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
1267                     cause, e.getMessage(), phone.getPhoneId()));
1268             connection.clearOriginalConnection();
1269             connection.destroy();
1270             return;
1271         }
1272 
1273         if (originalConnection == null) {
1274             int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
1275             // On GSM phones, null connection means that we dialed an MMI code
1276             if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
1277                 Log.d(this, "dialed MMI code");
1278                 int subId = phone.getSubId();
1279                 Log.d(this, "subId: "+subId);
1280                 telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
1281                 final Intent intent = new Intent(this, MMIDialogActivity.class);
1282                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1283                         Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1284                 if (SubscriptionManager.isValidSubscriptionId(subId)) {
1285                     intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
1286                 }
1287                 startActivity(intent);
1288             }
1289             Log.d(this, "placeOutgoingConnection, phone.dial returned null");
1290             connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
1291                     telephonyDisconnectCause, "Connection is null", phone.getPhoneId()));
1292             connection.clearOriginalConnection();
1293             connection.destroy();
1294         } else {
1295             connection.setOriginalConnection(originalConnection);
1296         }
1297     }
1298 
createConnectionFor( Phone phone, com.android.internal.telephony.Connection originalConnection, boolean isOutgoing, PhoneAccountHandle phoneAccountHandle, String telecomCallId, Uri address, int videoState)1299     private TelephonyConnection createConnectionFor(
1300             Phone phone,
1301             com.android.internal.telephony.Connection originalConnection,
1302             boolean isOutgoing,
1303             PhoneAccountHandle phoneAccountHandle,
1304             String telecomCallId,
1305             Uri address,
1306             int videoState) {
1307         TelephonyConnection returnConnection = null;
1308         int phoneType = phone.getPhoneType();
1309         if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
1310             returnConnection = new GsmConnection(originalConnection, telecomCallId, isOutgoing);
1311         } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
1312             boolean allowsMute = allowsMute(phone);
1313             returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer,
1314                     allowsMute, isOutgoing, telecomCallId);
1315         }
1316         if (returnConnection != null) {
1317             // Listen to Telephony specific callbacks from the connection
1318             returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener);
1319             returnConnection.setVideoPauseSupported(
1320                     TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
1321                             phoneAccountHandle));
1322             returnConnection.setManageImsConferenceCallSupported(
1323                     TelecomAccountRegistry.getInstance(this).isManageImsConferenceCallSupported(
1324                             phoneAccountHandle));
1325             returnConnection.setShowPreciseFailedCause(
1326                     TelecomAccountRegistry.getInstance(this).isShowPreciseFailedCause(
1327                             phoneAccountHandle));
1328         }
1329         return returnConnection;
1330     }
1331 
isOriginalConnectionKnown( com.android.internal.telephony.Connection originalConnection)1332     private boolean isOriginalConnectionKnown(
1333             com.android.internal.telephony.Connection originalConnection) {
1334         for (Connection connection : getAllConnections()) {
1335             if (connection instanceof TelephonyConnection) {
1336                 TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
1337                 if (telephonyConnection.getOriginalConnection() == originalConnection) {
1338                     return true;
1339                 }
1340             }
1341         }
1342         return false;
1343     }
1344 
1345     /**
1346      * Determines which {@link Phone} will be used to place the call.
1347      * @param accountHandle The {@link PhoneAccountHandle} which was sent from Telecom to place the
1348      *      call on.
1349      * @param isEmergency {@code true} if this is an emergency call, {@code false} otherwise.
1350      * @param emergencyNumberAddress When {@code isEmergency} is {@code true}, will be the phone
1351      *      of the emergency call.  Otherwise, this can be {@code null}  .
1352      * @return
1353      */
getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency, @Nullable String emergencyNumberAddress)1354     private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency,
1355                                      @Nullable String emergencyNumberAddress) {
1356         Phone chosenPhone = null;
1357         int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
1358         if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1359             int phoneId = mSubscriptionManagerProxy.getPhoneId(subId);
1360             chosenPhone = mPhoneFactoryProxy.getPhone(phoneId);
1361         }
1362         // If this is an emergency call and the phone we originally planned to make this call
1363         // with is not in service or was invalid, try to find one that is in service, using the
1364         // default as a last chance backup.
1365         if (isEmergency && (chosenPhone == null || !isAvailableForEmergencyCalls(chosenPhone))) {
1366             Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
1367                     + "or invalid for emergency call.", accountHandle);
1368             chosenPhone = getPhoneForEmergencyCall(emergencyNumberAddress);
1369             Log.d(this, "getPhoneForAccount: using subId: " +
1370                     (chosenPhone == null ? "null" : chosenPhone.getSubId()));
1371         }
1372         return chosenPhone;
1373     }
1374 
delayDialForDdsSwitch(Phone phone)1375     private CompletableFuture<Boolean> delayDialForDdsSwitch(Phone phone) {
1376         if (phone == null) {
1377             return CompletableFuture.completedFuture(Boolean.TRUE);
1378         }
1379         return possiblyOverrideDefaultDataForEmergencyCall(phone)
1380                 .completeOnTimeout(false, DEFAULT_DATA_SWITCH_TIMEOUT_MS,
1381                         TimeUnit.MILLISECONDS);
1382     }
1383 
1384     /**
1385      * If needed, block until Default Data subscription is switched for outgoing emergency call.
1386      *
1387      * In some cases, we need to try to switch the Default Data subscription before placing the
1388      * emergency call on DSDS devices. This includes the following situation:
1389      * - The modem does not support processing GNSS SUPL requests on the non-default data
1390      * subscription. For some carriers that do not provide a control plane fallback mechanism, the
1391      * SUPL request will be dropped and we will not be able to get the user's location for the
1392      * emergency call. In this case, we need to swap default data temporarily.
1393      * @param phone Evaluates whether or not the default data should be moved to the phone
1394      *              specified. Should not be null.
1395      */
possiblyOverrideDefaultDataForEmergencyCall( @onNull Phone phone)1396     private CompletableFuture<Boolean> possiblyOverrideDefaultDataForEmergencyCall(
1397             @NonNull Phone phone) {
1398         TelephonyManager telephony = TelephonyManager.from(phone.getContext());
1399         int phoneCount = telephony.getPhoneCount();
1400         // Do not override DDS if this is a single SIM device.
1401         if (phoneCount <= PhoneConstants.MAX_PHONE_COUNT_SINGLE_SIM) {
1402             return CompletableFuture.completedFuture(Boolean.TRUE);
1403         }
1404 
1405         CarrierConfigManager cfgManager = (CarrierConfigManager)
1406                 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1407         if (cfgManager == null) {
1408             // For some reason CarrierConfigManager is unavailable. Do not block emergency call.
1409             Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: couldn't get"
1410                     + "CarrierConfigManager");
1411             return CompletableFuture.completedFuture(Boolean.TRUE);
1412         }
1413         // Only override default data if we are IN_SERVICE and on a home network. We don't want to
1414         // perform a DDS switch of we are on a roaming network, where SUPL may not be available.
1415         boolean isPhoneAvailableForEmergency = isAvailableForEmergencyCalls(phone);
1416         boolean isRoaming = phone.getServiceState().getVoiceRoaming();
1417         if (!isPhoneAvailableForEmergency || isRoaming) {
1418             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, avail = "
1419                     + isPhoneAvailableForEmergency + ", roaming = " + isRoaming);
1420             return CompletableFuture.completedFuture(Boolean.TRUE);
1421         }
1422 
1423         // Do not switch Default data if this device supports emergency SUPL on non-DDS.
1424         final boolean gnssSuplRequiresDefaultData = phone.getContext().getResources().getBoolean(
1425                 R.bool.config_gnss_supl_requires_default_data_for_emergency);
1426         if (!gnssSuplRequiresDefaultData) {
1427             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, does not "
1428                     + "require DDS switch.");
1429             return CompletableFuture.completedFuture(Boolean.TRUE);
1430         }
1431 
1432         final boolean supportsCpFallback = cfgManager.getConfigForSubId(phone.getSubId())
1433                 .getInt(CarrierConfigManager.Gps.KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT,
1434                         CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_CP_ONLY)
1435                 != CarrierConfigManager.Gps.SUPL_EMERGENCY_MODE_TYPE_DP_ONLY;
1436         if (supportsCpFallback) {
1437             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: not switching DDS, carrier "
1438                     + "supports CP fallback.");
1439             // Do not try to swap default data if we support CS fallback, do not want to introduce
1440             // a lag in emergency call setup time if possible.
1441             return CompletableFuture.completedFuture(Boolean.TRUE);
1442         }
1443 
1444         // Get extension time, may be 0 for some carriers that support ECBM as well. Use
1445         // CarrierConfig default if format fails.
1446         int extensionTime = 0;
1447         try {
1448             extensionTime = Integer.valueOf(cfgManager.getConfigForSubId(phone.getSubId())
1449                     .getString(CarrierConfigManager.Gps.KEY_ES_EXTENSION_SEC_STRING, "0"));
1450         } catch (NumberFormatException e) {
1451             // Just use default.
1452         }
1453         CompletableFuture<Boolean> modemResultFuture = new CompletableFuture<>();
1454         try {
1455             Log.d(this, "possiblyOverrideDefaultDataForEmergencyCall: overriding DDS for "
1456                     + extensionTime + "seconds");
1457             PhoneSwitcher.getInstance().overrideDefaultDataForEmergency(phone.getPhoneId(),
1458                     extensionTime, modemResultFuture);
1459             // Catch all exceptions, we want to continue with emergency call if possible.
1460         } catch (Exception e) {
1461             Log.w(this, "possiblyOverrideDefaultDataForEmergencyCall: exception = "
1462                     + e.getMessage());
1463         }
1464         return modemResultFuture;
1465     }
1466 
1467     /**
1468      * Get the Phone to use for an emergency call of the given emergency number address:
1469      *  a) If there are multiple Phones with the Subscriptions that support the emergency number
1470      *     address, and one of them is the default voice Phone, consider the default voice phone
1471      *     if 1.4 HAL is supported, or if it is available for emergency call.
1472      *  b) If there are multiple Phones with the Subscriptions that support the emergency number
1473      *     address, and none of them is the default voice Phone, use one of these Phones if 1.4 HAL
1474      *     is supported, or if it is available for emergency call.
1475      *  c) If there is no Phone that supports the emergency call for the address, use the defined
1476      *     Priority list to select the Phone via {@link #getFirstPhoneForEmergencyCall}.
1477      */
getPhoneForEmergencyCall(String emergencyNumberAddress)1478     public Phone getPhoneForEmergencyCall(String emergencyNumberAddress) {
1479         // Find the list of available Phones for the given emergency number address
1480         List<Phone> potentialEmergencyPhones = new ArrayList<>();
1481         int defaultVoicePhoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId();
1482         for (Phone phone : mPhoneFactoryProxy.getPhones()) {
1483             if (phone.getEmergencyNumberTracker() != null) {
1484                 if (phone.getEmergencyNumberTracker().isEmergencyNumber(
1485                         emergencyNumberAddress, true)) {
1486                     if (phone.getHalVersion().greaterOrEqual(RIL.RADIO_HAL_VERSION_1_4)
1487                             || isAvailableForEmergencyCalls(phone)) {
1488                         // a)
1489                         if (phone.getPhoneId() == defaultVoicePhoneId) {
1490                             Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports"
1491                                     + " emergency number: " + phone.getPhoneId());
1492                             return phone;
1493                         }
1494                         potentialEmergencyPhones.add(phone);
1495                     }
1496                 }
1497             }
1498         }
1499         // b)
1500         if (potentialEmergencyPhones.size() > 0) {
1501             Log.i(this, "getPhoneForEmergencyCall, Phone Id that supports emergency number:"
1502                     + potentialEmergencyPhones.get(0).getPhoneId());
1503             return getFirstPhoneForEmergencyCall(potentialEmergencyPhones);
1504         }
1505         // c)
1506         return getFirstPhoneForEmergencyCall();
1507     }
1508 
1509     @VisibleForTesting
getFirstPhoneForEmergencyCall()1510     public Phone getFirstPhoneForEmergencyCall() {
1511         return getFirstPhoneForEmergencyCall(null);
1512     }
1513 
1514     /**
1515      * Retrieves the most sensible Phone to use for an emergency call using the following Priority
1516      *  list (for multi-SIM devices):
1517      *  1) The User's SIM preference for Voice calling
1518      *  2) The First Phone that is currently IN_SERVICE or is available for emergency calling
1519      *  3) Prioritize phones that have the dialed emergency number as part of their emergency
1520      *     number list
1521      *  4) If there is a PUK locked SIM, compare the SIMs that are not PUK locked. If all the SIMs
1522      *     are locked, skip to condition 5).
1523      *  5) The Phone with more Capabilities.
1524      *  6) The First Phone that has a SIM card in it (Starting from Slot 0...N)
1525      *  7) The Default Phone (Currently set as Slot 0)
1526      */
1527     @VisibleForTesting
getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber)1528     public Phone getFirstPhoneForEmergencyCall(List<Phone> phonesWithEmergencyNumber) {
1529         // 1)
1530         int phoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId();
1531         if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
1532             Phone defaultPhone = mPhoneFactoryProxy.getPhone(phoneId);
1533             if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) {
1534                 if (phonesWithEmergencyNumber == null
1535                         || phonesWithEmergencyNumber.contains(defaultPhone)) {
1536                     return defaultPhone;
1537                 }
1538             }
1539         }
1540 
1541         Phone firstPhoneWithSim = null;
1542         int phoneCount = mTelephonyManagerProxy.getPhoneCount();
1543         List<SlotStatus> phoneSlotStatus = new ArrayList<>(phoneCount);
1544         for (int i = 0; i < phoneCount; i++) {
1545             Phone phone = mPhoneFactoryProxy.getPhone(i);
1546             if (phone == null) {
1547                 continue;
1548             }
1549             // 2)
1550             if (isAvailableForEmergencyCalls(phone)) {
1551                 if (phonesWithEmergencyNumber == null
1552                         || phonesWithEmergencyNumber.contains(phone)) {
1553                     // the slot has the radio on & state is in service.
1554                     Log.i(this,
1555                             "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i);
1556                     return phone;
1557                 }
1558             }
1559             // 5)
1560             // Store the RAF Capabilities for sorting later.
1561             int radioAccessFamily = phone.getRadioAccessFamily();
1562             SlotStatus status = new SlotStatus(i, radioAccessFamily);
1563             phoneSlotStatus.add(status);
1564             Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" +
1565                     Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i);
1566             // 4)
1567             // Report Slot's PIN/PUK lock status for sorting later.
1568             int simState = mSubscriptionManagerProxy.getSimStateForSlotIdx(i);
1569             if (simState == TelephonyManager.SIM_STATE_PIN_REQUIRED ||
1570                     simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) {
1571                 status.isLocked = true;
1572             }
1573             // 3) Store if the Phone has the corresponding emergency number
1574             if (phonesWithEmergencyNumber != null) {
1575                 for (Phone phoneWithEmergencyNumber : phonesWithEmergencyNumber) {
1576                     if (phoneWithEmergencyNumber != null
1577                             && phoneWithEmergencyNumber.getPhoneId() == i) {
1578                         status.hasDialedEmergencyNumber = true;
1579                     }
1580                 }
1581             }
1582             // 6)
1583             if (firstPhoneWithSim == null && mTelephonyManagerProxy.hasIccCard(i)) {
1584                 // The slot has a SIM card inserted, but is not in service, so keep track of this
1585                 // Phone. Do not return because we want to make sure that none of the other Phones
1586                 // are in service (because that is always faster).
1587                 firstPhoneWithSim = phone;
1588                 Log.i(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" +
1589                         firstPhoneWithSim.getPhoneId());
1590             }
1591         }
1592         // 7)
1593         if (firstPhoneWithSim == null && phoneSlotStatus.isEmpty()) {
1594             if (phonesWithEmergencyNumber == null || phonesWithEmergencyNumber.isEmpty()) {
1595                 // No Phones available, get the default
1596                 Log.i(this, "getFirstPhoneForEmergencyCall, return default phone");
1597                 return  mPhoneFactoryProxy.getDefaultPhone();
1598             }
1599             return phonesWithEmergencyNumber.get(0);
1600         } else {
1601             // 5)
1602             final int defaultPhoneId = mPhoneFactoryProxy.getDefaultPhone().getPhoneId();
1603             final Phone firstOccupiedSlot = firstPhoneWithSim;
1604             if (!phoneSlotStatus.isEmpty()) {
1605                 // Only sort if there are enough elements to do so.
1606                 if (phoneSlotStatus.size() > 1) {
1607                     Collections.sort(phoneSlotStatus, (o1, o2) -> {
1608                         if (!o1.hasDialedEmergencyNumber && o2.hasDialedEmergencyNumber) {
1609                             return -1;
1610                         }
1611                         if (o1.hasDialedEmergencyNumber && !o2.hasDialedEmergencyNumber) {
1612                             return 1;
1613                         }
1614                         // First start by seeing if either of the phone slots are locked. If they
1615                         // are, then sort by non-locked SIM first. If they are both locked, sort
1616                         // by capability instead.
1617                         if (o1.isLocked && !o2.isLocked) {
1618                             return -1;
1619                         }
1620                         if (o2.isLocked && !o1.isLocked) {
1621                             return 1;
1622                         }
1623                         // sort by number of RadioAccessFamily Capabilities.
1624                         int compare = Integer.bitCount(o1.capabilities) -
1625                                 Integer.bitCount(o2.capabilities);
1626                         if (compare == 0) {
1627                             // Sort by highest RAF Capability if the number is the same.
1628                             compare = RadioAccessFamily.getHighestRafCapability(o1.capabilities) -
1629                                     RadioAccessFamily.getHighestRafCapability(o2.capabilities);
1630                             if (compare == 0) {
1631                                 if (firstOccupiedSlot != null) {
1632                                     // If the RAF capability is the same, choose based on whether or
1633                                     // not any of the slots are occupied with a SIM card (if both
1634                                     // are, always choose the first).
1635                                     if (o1.slotId == firstOccupiedSlot.getPhoneId()) {
1636                                         return 1;
1637                                     } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) {
1638                                         return -1;
1639                                     }
1640                                 } else {
1641                                     // No slots have SIMs detected in them, so weight the default
1642                                     // Phone Id greater than the others.
1643                                     if (o1.slotId == defaultPhoneId) {
1644                                         return 1;
1645                                     } else if (o2.slotId == defaultPhoneId) {
1646                                         return -1;
1647                                     }
1648                                 }
1649                             }
1650                         }
1651                         return compare;
1652                     });
1653                 }
1654                 int mostCapablePhoneId = phoneSlotStatus.get(phoneSlotStatus.size() - 1).slotId;
1655                 Log.i(this, "getFirstPhoneForEmergencyCall, Using Phone Id: " + mostCapablePhoneId +
1656                         "with highest capability");
1657                 return mPhoneFactoryProxy.getPhone(mostCapablePhoneId);
1658             } else {
1659                 // 6)
1660                 return firstPhoneWithSim;
1661             }
1662         }
1663     }
1664 
1665     /**
1666      * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only.
1667      */
isAvailableForEmergencyCalls(Phone phone)1668     private boolean isAvailableForEmergencyCalls(Phone phone) {
1669         return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() ||
1670                 phone.getServiceState().isEmergencyOnly();
1671     }
1672 
1673     /**
1674      * Determines if the connection should allow mute.
1675      *
1676      * @param phone The current phone.
1677      * @return {@code True} if the connection should allow mute.
1678      */
allowsMute(Phone phone)1679     private boolean allowsMute(Phone phone) {
1680         // For CDMA phones, check if we are in Emergency Callback Mode (ECM).  Mute is disallowed
1681         // in ECM mode.
1682         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
1683             if (phone.isInEcm()) {
1684                 return false;
1685             }
1686         }
1687 
1688         return true;
1689     }
1690 
1691     @Override
removeConnection(Connection connection)1692     public void removeConnection(Connection connection) {
1693         super.removeConnection(connection);
1694         if (connection instanceof TelephonyConnection) {
1695             TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
1696             telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
1697         }
1698     }
1699 
1700     /**
1701      * When a {@link TelephonyConnection} has its underlying original connection configured,
1702      * we need to add it to the correct conference controller.
1703      *
1704      * @param connection The connection to be added to the controller
1705      */
addConnectionToConferenceController(TelephonyConnection connection)1706     public void addConnectionToConferenceController(TelephonyConnection connection) {
1707         // TODO: Need to revisit what happens when the original connection for the
1708         // TelephonyConnection changes.  If going from CDMA --> GSM (for example), the
1709         // instance of TelephonyConnection will still be a CdmaConnection, not a GsmConnection.
1710         // The CDMA conference controller makes the assumption that it will only have CDMA
1711         // connections in it, while the other conference controllers aren't as restrictive.  Really,
1712         // when we go between CDMA and GSM we should replace the TelephonyConnection.
1713         if (connection.isImsConnection()) {
1714             Log.d(this, "Adding IMS connection to conference controller: " + connection);
1715             mImsConferenceController.add(connection);
1716             mTelephonyConferenceController.remove(connection);
1717             if (connection instanceof CdmaConnection) {
1718                 mCdmaConferenceController.remove((CdmaConnection) connection);
1719             }
1720         } else {
1721             int phoneType = connection.getCall().getPhone().getPhoneType();
1722             if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
1723                 Log.d(this, "Adding GSM connection to conference controller: " + connection);
1724                 mTelephonyConferenceController.add(connection);
1725                 if (connection instanceof CdmaConnection) {
1726                     mCdmaConferenceController.remove((CdmaConnection) connection);
1727                 }
1728             } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA &&
1729                     connection instanceof CdmaConnection) {
1730                 Log.d(this, "Adding CDMA connection to conference controller: " + connection);
1731                 mCdmaConferenceController.add((CdmaConnection) connection);
1732                 mTelephonyConferenceController.remove(connection);
1733             }
1734             Log.d(this, "Removing connection from IMS conference controller: " + connection);
1735             mImsConferenceController.remove(connection);
1736         }
1737     }
1738 
1739     /**
1740      * Create a new CDMA connection. CDMA connections have additional limitations when creating
1741      * additional calls which are handled in this method.  Specifically, CDMA has a "FLASH" command
1742      * that can be used for three purposes: merging a call, swapping unmerged calls, and adding
1743      * a new outgoing call. The function of the flash command depends on the context of the current
1744      * set of calls. This method will prevent an outgoing call from being made if it is not within
1745      * the right circumstances to support adding a call.
1746      */
checkAdditionalOutgoingCallLimits(Phone phone)1747     private Connection checkAdditionalOutgoingCallLimits(Phone phone) {
1748         if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
1749             // Check to see if any CDMA conference calls exist, and if they do, check them for
1750             // limitations.
1751             for (Conference conference : getAllConferences()) {
1752                 if (conference instanceof CdmaConference) {
1753                     CdmaConference cdmaConf = (CdmaConference) conference;
1754 
1755                     // If the CDMA conference has not been merged, add-call will not work, so fail
1756                     // this request to add a call.
1757                     if (cdmaConf.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
1758                         return Connection.createFailedConnection(new DisconnectCause(
1759                                     DisconnectCause.RESTRICTED,
1760                                     null,
1761                                     getResources().getString(R.string.callFailed_cdma_call_limit),
1762                                     "merge-capable call exists, prevent flash command."));
1763                     }
1764                 }
1765             }
1766         }
1767 
1768         return null; // null means nothing went wrong, and call should continue.
1769     }
1770 
isTtyModeEnabled(Context context)1771     private boolean isTtyModeEnabled(Context context) {
1772         return (android.provider.Settings.Secure.getInt(
1773                 context.getContentResolver(),
1774                 android.provider.Settings.Secure.PREFERRED_TTY_MODE,
1775                 TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF);
1776     }
1777 
1778     /**
1779      * For outgoing dialed calls, potentially send a ConnectionEvent if the user is on WFC and is
1780      * dialing an international number.
1781      * @param telephonyConnection The connection.
1782      */
maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection)1783     private void maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection) {
1784         if (telephonyConnection == null || telephonyConnection.getPhone() == null ||
1785                 telephonyConnection.getPhone().getDefaultPhone() == null) {
1786             return;
1787         }
1788         Phone phone = telephonyConnection.getPhone().getDefaultPhone();
1789         if (phone instanceof GsmCdmaPhone) {
1790             GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone;
1791             if (telephonyConnection.isOutgoingCall() &&
1792                     gsmCdmaPhone.isNotificationOfWfcCallRequired(
1793                             telephonyConnection.getOriginalConnection().getOrigDialString())) {
1794                 // Send connection event to InCall UI to inform the user of the fact they
1795                 // are potentially placing an international call on WFC.
1796                 Log.i(this, "placeOutgoingConnection - sending international call on WFC " +
1797                         "confirmation event");
1798                 telephonyConnection.sendConnectionEvent(
1799                         TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null);
1800             }
1801         }
1802     }
1803 
handleTtyModeChange(boolean isTtyEnabled)1804     private void handleTtyModeChange(boolean isTtyEnabled) {
1805         Log.i(this, "handleTtyModeChange; isTtyEnabled=%b", isTtyEnabled);
1806         mIsTtyEnabled = isTtyEnabled;
1807         for (Connection connection : getAllConnections()) {
1808             if (connection instanceof TelephonyConnection) {
1809                 TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
1810                 telephonyConnection.setTtyEnabled(isTtyEnabled);
1811             }
1812         }
1813     }
1814 }
1815