TelephonyConnectionService.java revision 3bbcaa2856a87a903ce2443a342b3d4ff95a9db5
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
17package com.android.services.telephony;
18
19import android.content.ActivityNotFoundException;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.net.Uri;
24import android.os.Bundle;
25import android.provider.Settings;
26import android.telecom.Conference;
27import android.telecom.Connection;
28import android.telecom.ConnectionRequest;
29import android.telecom.ConnectionService;
30import android.telecom.DisconnectCause;
31import android.telecom.PhoneAccount;
32import android.telecom.PhoneAccountHandle;
33import android.telecom.TelecomManager;
34import android.telecom.VideoProfile;
35import android.telephony.CarrierConfigManager;
36import android.telephony.PhoneNumberUtils;
37import android.telephony.RadioAccessFamily;
38import android.telephony.ServiceState;
39import android.telephony.SubscriptionManager;
40import android.telephony.TelephonyManager;
41import android.text.TextUtils;
42import android.util.Pair;
43
44import com.android.internal.annotations.VisibleForTesting;
45import com.android.internal.telephony.Call;
46import com.android.internal.telephony.CallStateException;
47import com.android.internal.telephony.GsmCdmaPhone;
48import com.android.internal.telephony.IccCard;
49import com.android.internal.telephony.IccCardConstants;
50import com.android.internal.telephony.Phone;
51import com.android.internal.telephony.PhoneConstants;
52import com.android.internal.telephony.PhoneFactory;
53import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
54import com.android.internal.telephony.imsphone.ImsPhone;
55import com.android.phone.MMIDialogActivity;
56import com.android.phone.PhoneUtils;
57import com.android.phone.R;
58
59import java.lang.ref.WeakReference;
60import java.util.ArrayList;
61import java.util.Arrays;
62import java.util.Collection;
63import java.util.Collections;
64import java.util.LinkedList;
65import java.util.List;
66import java.util.Queue;
67import java.util.regex.Pattern;
68
69/**
70 * Service for making GSM and CDMA connections.
71 */
72public class TelephonyConnectionService extends ConnectionService {
73
74    // If configured, reject attempts to dial numbers matching this pattern.
75    private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
76            Pattern.compile("\\*228[0-9]{0,2}");
77
78    private final TelephonyConnectionServiceProxy mTelephonyConnectionServiceProxy =
79            new TelephonyConnectionServiceProxy() {
80        @Override
81        public Collection<Connection> getAllConnections() {
82            return TelephonyConnectionService.this.getAllConnections();
83        }
84        @Override
85        public void addConference(TelephonyConference mTelephonyConference) {
86            TelephonyConnectionService.this.addConference(mTelephonyConference);
87        }
88        @Override
89        public void addConference(ImsConference mImsConference) {
90            TelephonyConnectionService.this.addConference(mImsConference);
91        }
92        @Override
93        public void removeConnection(Connection connection) {
94            TelephonyConnectionService.this.removeConnection(connection);
95        }
96        @Override
97        public void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
98                                          Connection connection) {
99            TelephonyConnectionService.this
100                    .addExistingConnection(phoneAccountHandle, connection);
101        }
102        @Override
103        public void addExistingConnection(PhoneAccountHandle phoneAccountHandle,
104                Connection connection, Conference conference) {
105            TelephonyConnectionService.this
106                    .addExistingConnection(phoneAccountHandle, connection, conference);
107        }
108        @Override
109        public void addConnectionToConferenceController(TelephonyConnection connection) {
110            TelephonyConnectionService.this.addConnectionToConferenceController(connection);
111        }
112    };
113
114    private final TelephonyConferenceController mTelephonyConferenceController =
115            new TelephonyConferenceController(mTelephonyConnectionServiceProxy);
116    private final CdmaConferenceController mCdmaConferenceController =
117            new CdmaConferenceController(this);
118    private final ImsConferenceController mImsConferenceController =
119            new ImsConferenceController(TelecomAccountRegistry.getInstance(this),
120                    mTelephonyConnectionServiceProxy);
121
122    private ComponentName mExpectedComponentName = null;
123    private RadioOnHelper mRadioOnHelper;
124    private EmergencyTonePlayer mEmergencyTonePlayer;
125
126    // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has
127    // already tried to connect with. There should be only one TelephonyConnection trying to place a
128    // call at one time. We also only access this cache from a TelephonyConnection that wishes to
129    // redial, so we use a WeakReference that will become stale once the TelephonyConnection is
130    // destroyed.
131    @VisibleForTesting
132    public Pair<WeakReference<TelephonyConnection>, Queue<Phone>> mEmergencyRetryCache;
133
134    /**
135     * Keeps track of the status of a SIM slot.
136     */
137    private static class SlotStatus {
138        public int slotId;
139        // RAT capabilities
140        public int capabilities;
141        // By default, we will assume that the slots are not locked.
142        public boolean isLocked = false;
143
144        public SlotStatus(int slotId, int capabilities) {
145            this.slotId = slotId;
146            this.capabilities = capabilities;
147        }
148    }
149
150    // SubscriptionManager Proxy interface for testing
151    public interface SubscriptionManagerProxy {
152        int getDefaultVoicePhoneId();
153        int getSimStateForSlotIdx(int slotId);
154        int getPhoneId(int subId);
155    }
156
157    private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() {
158        @Override
159        public int getDefaultVoicePhoneId() {
160            return SubscriptionManager.getDefaultVoicePhoneId();
161        }
162
163        @Override
164        public int getSimStateForSlotIdx(int slotId) {
165            return SubscriptionManager.getSimStateForSlotIndex(slotId);
166        }
167
168        @Override
169        public int getPhoneId(int subId) {
170            return SubscriptionManager.getPhoneId(subId);
171        }
172    };
173
174    // TelephonyManager Proxy interface for testing
175    public interface TelephonyManagerProxy {
176        int getPhoneCount();
177        boolean hasIccCard(int slotId);
178    }
179
180    private TelephonyManagerProxy mTelephonyManagerProxy = new TelephonyManagerProxy() {
181        private final TelephonyManager sTelephonyManager = TelephonyManager.getDefault();
182
183        @Override
184        public int getPhoneCount() {
185            return sTelephonyManager.getPhoneCount();
186        }
187
188        @Override
189        public boolean hasIccCard(int slotId) {
190            return sTelephonyManager.hasIccCard(slotId);
191        }
192    };
193
194    //PhoneFactory proxy interface for testing
195    public interface PhoneFactoryProxy {
196        Phone getPhone(int index);
197        Phone getDefaultPhone();
198        Phone[] getPhones();
199    }
200
201    private PhoneFactoryProxy mPhoneFactoryProxy = new PhoneFactoryProxy() {
202        @Override
203        public Phone getPhone(int index) {
204            return PhoneFactory.getPhone(index);
205        }
206
207        @Override
208        public Phone getDefaultPhone() {
209            return PhoneFactory.getDefaultPhone();
210        }
211
212        @Override
213        public Phone[] getPhones() {
214            return PhoneFactory.getPhones();
215        }
216    };
217
218    @VisibleForTesting
219    public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) {
220        mSubscriptionManagerProxy = proxy;
221    }
222
223    @VisibleForTesting
224    public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) {
225        mTelephonyManagerProxy = proxy;
226    }
227
228    @VisibleForTesting
229    public void setPhoneFactoryProxy(PhoneFactoryProxy proxy) {
230        mPhoneFactoryProxy = proxy;
231    }
232
233    /**
234     * A listener to actionable events specific to the TelephonyConnection.
235     */
236    private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
237            new TelephonyConnection.TelephonyConnectionListener() {
238        @Override
239        public void onOriginalConnectionConfigured(TelephonyConnection c) {
240            addConnectionToConferenceController(c);
241        }
242
243        @Override
244        public void onOriginalConnectionRetry(TelephonyConnection c, boolean isPermanentFailure) {
245            retryOutgoingOriginalConnection(c, isPermanentFailure);
246        }
247    };
248
249    @Override
250    public void onCreate() {
251        super.onCreate();
252        Log.initLogging(this);
253        mExpectedComponentName = new ComponentName(this, this.getClass());
254        mEmergencyTonePlayer = new EmergencyTonePlayer(this);
255        TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
256    }
257
258    @Override
259    public Connection onCreateOutgoingConnection(
260            PhoneAccountHandle connectionManagerPhoneAccount,
261            final ConnectionRequest request) {
262        Log.i(this, "onCreateOutgoingConnection, request: " + request);
263
264        Uri handle = request.getAddress();
265        if (handle == null) {
266            Log.d(this, "onCreateOutgoingConnection, handle is null");
267            return Connection.createFailedConnection(
268                    DisconnectCauseUtil.toTelecomDisconnectCause(
269                            android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
270                            "No phone number supplied"));
271        }
272
273        String scheme = handle.getScheme();
274        String number;
275        if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
276            // TODO: We don't check for SecurityException here (requires
277            // CALL_PRIVILEGED permission).
278            final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
279            if (phone == null) {
280                Log.d(this, "onCreateOutgoingConnection, phone is null");
281                return Connection.createFailedConnection(
282                        DisconnectCauseUtil.toTelecomDisconnectCause(
283                                android.telephony.DisconnectCause.OUT_OF_SERVICE,
284                                "Phone is null"));
285            }
286            number = phone.getVoiceMailNumber();
287            if (TextUtils.isEmpty(number)) {
288                Log.d(this, "onCreateOutgoingConnection, no voicemail number set.");
289                return Connection.createFailedConnection(
290                        DisconnectCauseUtil.toTelecomDisconnectCause(
291                                android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING,
292                                "Voicemail scheme provided but no voicemail number set."));
293            }
294
295            // Convert voicemail: to tel:
296            handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
297        } else {
298            if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
299                Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme);
300                return Connection.createFailedConnection(
301                        DisconnectCauseUtil.toTelecomDisconnectCause(
302                                android.telephony.DisconnectCause.INVALID_NUMBER,
303                                "Handle scheme is not type tel"));
304            }
305
306            number = handle.getSchemeSpecificPart();
307            if (TextUtils.isEmpty(number)) {
308                Log.d(this, "onCreateOutgoingConnection, unable to parse number");
309                return Connection.createFailedConnection(
310                        DisconnectCauseUtil.toTelecomDisconnectCause(
311                                android.telephony.DisconnectCause.INVALID_NUMBER,
312                                "Unable to parse number"));
313            }
314
315            final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
316            if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) {
317                // Obtain the configuration for the outgoing phone's SIM. If the outgoing number
318                // matches the *228 regex pattern, fail the call. This number is used for OTASP, and
319                // when dialed could lock LTE SIMs to 3G if not prohibited..
320                boolean disableActivation = false;
321                CarrierConfigManager cfgManager = (CarrierConfigManager)
322                        phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
323                if (cfgManager != null) {
324                    disableActivation = cfgManager.getConfigForSubId(phone.getSubId())
325                            .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL);
326                }
327
328                if (disableActivation) {
329                    return Connection.createFailedConnection(
330                            DisconnectCauseUtil.toTelecomDisconnectCause(
331                                    android.telephony.DisconnectCause
332                                            .CDMA_ALREADY_ACTIVATED,
333                                    "Tried to dial *228"));
334                }
335            }
336        }
337
338        // Convert into emergency number if necessary
339        // This is required in some regions (e.g. Taiwan).
340        if (!PhoneNumberUtils.isLocalEmergencyNumber(this, number)) {
341            final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
342            // We only do the conversion if the phone is not in service. The un-converted
343            // emergency numbers will go to the correct destination when the phone is in-service,
344            // so they will only need the special emergency call setup when the phone is out of
345            // service.
346            if (phone == null || phone.getServiceState().getState()
347                    != ServiceState.STATE_IN_SERVICE) {
348                String convertedNumber = PhoneNumberUtils.convertToEmergencyNumber(this, number);
349                if (!TextUtils.equals(convertedNumber, number)) {
350                    Log.i(this, "onCreateOutgoingConnection, converted to emergency number");
351                    number = convertedNumber;
352                    handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
353                }
354            }
355        }
356        final String numberToDial = number;
357
358        final boolean isEmergencyNumber =
359                PhoneNumberUtils.isLocalEmergencyNumber(this, numberToDial);
360
361
362        final boolean isAirplaneModeOn = Settings.Global.getInt(getContentResolver(),
363                Settings.Global.AIRPLANE_MODE_ON, 0) > 0;
364
365        boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn))
366                || isRadioPowerDownOnBluetooth();
367
368        if (needToTurnOnRadio) {
369            final Uri resultHandle = handle;
370            // By default, Connection based on the default Phone, since we need to return to Telecom
371            // now.
372            final int originalPhoneType = PhoneFactory.getDefaultPhone().getPhoneType();
373            final Connection resultConnection = getTelephonyConnection(request, numberToDial,
374                    isEmergencyNumber, resultHandle, PhoneFactory.getDefaultPhone());
375            if (mRadioOnHelper == null) {
376                mRadioOnHelper = new RadioOnHelper(this);
377            }
378            mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
379                @Override
380                public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
381                    handleOnComplete(isRadioReady, isEmergencyNumber, resultConnection, request,
382                            numberToDial, resultHandle, originalPhoneType);
383                }
384
385                @Override
386                public boolean isOkToCall(Phone phone, int serviceState) {
387                    if (isEmergencyNumber) {
388                        // We currently only look to make sure that the radio is on before dialing.
389                        // We should be able to make emergency calls at any time after the radio has
390                        // been powered on and isn't in the UNAVAILABLE state, even if it is
391                        // reporting the OUT_OF_SERVICE state.
392                        return (phone.getState() == PhoneConstants.State.OFFHOOK)
393                            || phone.getServiceStateTracker().isRadioOn();
394                    } else {
395                        // It is not an emergency number, so wait until we are in service and ready
396                        // to make calls. This can happen when we power down the radio on bluetooth
397                        // to save power on watches.
398                        return (phone.getState() == PhoneConstants.State.OFFHOOK)
399                            || serviceState == ServiceState.STATE_IN_SERVICE;
400                    }
401                }
402            });
403            // Return the still unconnected GsmConnection and wait for the Radios to boot before
404            // connecting it to the underlying Phone.
405            return resultConnection;
406        } else {
407            if (!canAddCall() && !isEmergencyNumber) {
408                Log.d(this, "onCreateOutgoingConnection, cannot add call .");
409                return Connection.createFailedConnection(
410                        new DisconnectCause(DisconnectCause.ERROR,
411                                getApplicationContext().getText(
412                                        R.string.incall_error_cannot_add_call),
413                                getApplicationContext().getText(
414                                        R.string.incall_error_cannot_add_call),
415                                "Add call restricted due to ongoing video call"));
416            }
417
418            // Get the right phone object from the account data passed in.
419            final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
420            Connection resultConnection = getTelephonyConnection(request, numberToDial,
421                    isEmergencyNumber, handle, phone);
422            // If there was a failure, the resulting connection will not be a TelephonyConnection,
423            // so don't place the call!
424            if(resultConnection instanceof TelephonyConnection) {
425                placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
426            }
427            return resultConnection;
428        }
429    }
430
431    /**
432     * Whether the cellular radio is power off because the device is on Bluetooth.
433     */
434    private boolean isRadioPowerDownOnBluetooth() {
435        final Context context = getApplicationContext();
436        final boolean allowed = context.getResources().getBoolean(
437                R.bool.config_allowRadioPowerDownOnBluetooth);
438        final int cellOn = Settings.Global.getInt(context.getContentResolver(),
439                Settings.Global.CELL_ON,
440                PhoneConstants.CELL_OFF_FLAG);
441        return (allowed && cellOn == PhoneConstants.CELL_ON_FLAG && !isRadioOn());
442    }
443
444    /**
445     * Handle the onComplete callback of RadioOnStateListener.
446     */
447    private void handleOnComplete(boolean isRadioReady, boolean isEmergencyNumber,
448            Connection originalConnection, ConnectionRequest request, String numberToDial,
449            Uri handle, int originalPhoneType) {
450        // Make sure the Call has not already been canceled by the user.
451        if (originalConnection.getState() == Connection.STATE_DISCONNECTED) {
452            Log.i(this, "Call disconnected before the outgoing call was placed. Skipping call "
453                    + "placement.");
454            return;
455        }
456        if (isRadioReady) {
457            // Get the right phone object since the radio has been turned on
458            // successfully.
459            final Phone phone = getPhoneForAccount(request.getAccountHandle(),
460                    isEmergencyNumber);
461            // If the PhoneType of the Phone being used is different than the Default Phone, then we
462            // need create a new Connection using that PhoneType and replace it in Telecom.
463            if (phone.getPhoneType() != originalPhoneType) {
464                Connection repConnection = getTelephonyConnection(request, numberToDial,
465                        isEmergencyNumber, handle, phone);
466                // If there was a failure, the resulting connection will not be a
467                // TelephonyConnection, so don't place the call, just return!
468                if (repConnection instanceof TelephonyConnection) {
469                    placeOutgoingConnection((TelephonyConnection) repConnection, phone, request);
470                }
471                // Notify Telecom of the new Connection type.
472                // TODO: Switch out the underlying connection instead of creating a new
473                // one and causing UI Jank.
474                addExistingConnection(PhoneUtils.makePstnPhoneAccountHandle(phone), repConnection);
475                // Remove the old connection from Telecom after.
476                originalConnection.setDisconnected(
477                        DisconnectCauseUtil.toTelecomDisconnectCause(
478                                android.telephony.DisconnectCause.OUTGOING_CANCELED,
479                                "Reconnecting outgoing Emergency Call."));
480                originalConnection.destroy();
481            } else {
482                placeOutgoingConnection((TelephonyConnection) originalConnection, phone, request);
483            }
484        } else {
485            Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
486            originalConnection.setDisconnected(
487                    DisconnectCauseUtil.toTelecomDisconnectCause(
488                            android.telephony.DisconnectCause.POWER_OFF,
489                            "Failed to turn on radio."));
490            originalConnection.destroy();
491        }
492    }
493
494    /**
495     * @return {@code true} if any other call is disabling the ability to add calls, {@code false}
496     *      otherwise.
497     */
498    private boolean canAddCall() {
499        Collection<Connection> connections = getAllConnections();
500        for (Connection connection : connections) {
501            if (connection.getExtras() != null &&
502                    connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) {
503                return false;
504            }
505        }
506        return true;
507    }
508
509    private Connection getTelephonyConnection(final ConnectionRequest request, final String number,
510            boolean isEmergencyNumber, final Uri handle, Phone phone) {
511
512        if (phone == null) {
513            final Context context = getApplicationContext();
514            if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) {
515                // Check SIM card state before the outgoing call.
516                // Start the SIM unlock activity if PIN_REQUIRED.
517                final Phone defaultPhone = mPhoneFactoryProxy.getDefaultPhone();
518                final IccCard icc = defaultPhone.getIccCard();
519                IccCardConstants.State simState = IccCardConstants.State.UNKNOWN;
520                if (icc != null) {
521                    simState = icc.getState();
522                }
523                if (simState == IccCardConstants.State.PIN_REQUIRED) {
524                    final String simUnlockUiPackage = context.getResources().getString(
525                            R.string.config_simUnlockUiPackage);
526                    final String simUnlockUiClass = context.getResources().getString(
527                            R.string.config_simUnlockUiClass);
528                    if (simUnlockUiPackage != null && simUnlockUiClass != null) {
529                        Intent simUnlockIntent = new Intent().setComponent(new ComponentName(
530                                simUnlockUiPackage, simUnlockUiClass));
531                        simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
532                        try {
533                            context.startActivity(simUnlockIntent);
534                        } catch (ActivityNotFoundException exception) {
535                            Log.e(this, exception, "Unable to find SIM unlock UI activity.");
536                        }
537                    }
538                    return Connection.createFailedConnection(
539                            DisconnectCauseUtil.toTelecomDisconnectCause(
540                                    android.telephony.DisconnectCause.OUT_OF_SERVICE,
541                                    "SIM_STATE_PIN_REQUIRED"));
542                }
543            }
544
545            Log.d(this, "onCreateOutgoingConnection, phone is null");
546            return Connection.createFailedConnection(
547                    DisconnectCauseUtil.toTelecomDisconnectCause(
548                            android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null"));
549        }
550
551        // Check both voice & data RAT to enable normal CS call,
552        // when voice RAT is OOS but Data RAT is present.
553        int state = phone.getServiceState().getState();
554        if (state == ServiceState.STATE_OUT_OF_SERVICE) {
555            int dataNetType = phone.getServiceState().getDataNetworkType();
556            if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
557                    dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
558                state = phone.getServiceState().getDataRegState();
559            }
560        }
561
562        // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if
563        // carrier configuration specifies that we cannot make non-emergency calls in ECM mode.
564        if (!isEmergencyNumber && phone.isInEcm()) {
565            boolean allowNonEmergencyCalls = true;
566            CarrierConfigManager cfgManager = (CarrierConfigManager)
567                    phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
568            if (cfgManager != null) {
569                allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId())
570                        .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL);
571            }
572
573            if (!allowNonEmergencyCalls) {
574                return Connection.createFailedConnection(
575                        DisconnectCauseUtil.toTelecomDisconnectCause(
576                                android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY,
577                                "Cannot make non-emergency call in ECM mode."
578                        ));
579            }
580        }
581
582        if (!isEmergencyNumber) {
583            switch (state) {
584                case ServiceState.STATE_IN_SERVICE:
585                case ServiceState.STATE_EMERGENCY_ONLY:
586                    break;
587                case ServiceState.STATE_OUT_OF_SERVICE:
588                    if (phone.isUtEnabled() && number.endsWith("#")) {
589                        Log.d(this, "onCreateOutgoingConnection dial for UT");
590                        break;
591                    } else {
592                        return Connection.createFailedConnection(
593                                DisconnectCauseUtil.toTelecomDisconnectCause(
594                                        android.telephony.DisconnectCause.OUT_OF_SERVICE,
595                                        "ServiceState.STATE_OUT_OF_SERVICE"));
596                    }
597                case ServiceState.STATE_POWER_OFF:
598                    // Don't disconnect if radio is power off because the device is on Bluetooth.
599                    if (isRadioPowerDownOnBluetooth()) {
600                        break;
601                    }
602                    return Connection.createFailedConnection(
603                            DisconnectCauseUtil.toTelecomDisconnectCause(
604                                    android.telephony.DisconnectCause.POWER_OFF,
605                                    "ServiceState.STATE_POWER_OFF"));
606                default:
607                    Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state);
608                    return Connection.createFailedConnection(
609                            DisconnectCauseUtil.toTelecomDisconnectCause(
610                                    android.telephony.DisconnectCause.OUTGOING_FAILURE,
611                                    "Unknown service state " + state));
612            }
613        }
614
615        final Context context = getApplicationContext();
616        if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled(context) &&
617                !isEmergencyNumber) {
618            return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
619                    android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED));
620        }
621
622        // Check for additional limits on CDMA phones.
623        final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone);
624        if (failedConnection != null) {
625            return failedConnection;
626        }
627
628        // Check roaming status to see if we should block custom call forwarding codes
629        if (blockCallForwardingNumberWhileRoaming(phone, number)) {
630            return Connection.createFailedConnection(
631                    DisconnectCauseUtil.toTelecomDisconnectCause(
632                            android.telephony.DisconnectCause.DIALED_CALL_FORWARDING_WHILE_ROAMING,
633                            "Call forwarding while roaming"));
634        }
635
636
637        final TelephonyConnection connection =
638                createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(),
639                        request.getTelecomCallId(), request.getAddress(), request.getVideoState());
640        if (connection == null) {
641            return Connection.createFailedConnection(
642                    DisconnectCauseUtil.toTelecomDisconnectCause(
643                            android.telephony.DisconnectCause.OUTGOING_FAILURE,
644                            "Invalid phone type"));
645        }
646        connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
647        connection.setInitializing();
648        connection.setVideoState(request.getVideoState());
649
650        return connection;
651    }
652
653    @Override
654    public Connection onCreateIncomingConnection(
655            PhoneAccountHandle connectionManagerPhoneAccount,
656            ConnectionRequest request) {
657        Log.i(this, "onCreateIncomingConnection, request: " + request);
658        // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM),
659        // make sure the PhoneAccount lookup retrieves the default Emergency Phone.
660        PhoneAccountHandle accountHandle = request.getAccountHandle();
661        boolean isEmergency = false;
662        if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
663                accountHandle.getId())) {
664            Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " +
665                    "Treat as an Emergency Call.");
666            isEmergency = true;
667        }
668        Phone phone = getPhoneForAccount(accountHandle, isEmergency);
669        if (phone == null) {
670            return Connection.createFailedConnection(
671                    DisconnectCauseUtil.toTelecomDisconnectCause(
672                            android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
673                            "Phone is null"));
674        }
675
676        Call call = phone.getRingingCall();
677        if (!call.getState().isRinging()) {
678            Log.i(this, "onCreateIncomingConnection, no ringing call");
679            return Connection.createFailedConnection(
680                    DisconnectCauseUtil.toTelecomDisconnectCause(
681                            android.telephony.DisconnectCause.INCOMING_MISSED,
682                            "Found no ringing call"));
683        }
684
685        com.android.internal.telephony.Connection originalConnection =
686                call.getState() == Call.State.WAITING ?
687                    call.getLatestConnection() : call.getEarliestConnection();
688        if (isOriginalConnectionKnown(originalConnection)) {
689            Log.i(this, "onCreateIncomingConnection, original connection already registered");
690            return Connection.createCanceledConnection();
691        }
692
693        // We should rely on the originalConnection to get the video state.  The request coming
694        // from Telecom does not know the video state of the incoming call.
695        int videoState = originalConnection != null ? originalConnection.getVideoState() :
696                VideoProfile.STATE_AUDIO_ONLY;
697
698        Connection connection =
699                createConnectionFor(phone, originalConnection, false /* isOutgoing */,
700                        request.getAccountHandle(), request.getTelecomCallId(),
701                        request.getAddress(), videoState);
702        if (connection == null) {
703            return Connection.createCanceledConnection();
704        } else {
705            return connection;
706        }
707    }
708
709    /**
710     * Called by the {@link ConnectionService} when a newly created {@link Connection} has been
711     * added to the {@link ConnectionService} and sent to Telecom.  Here it is safe to send
712     * connection events.
713     *
714     * @param connection the {@link Connection}.
715     */
716    @Override
717    public void onCreateConnectionComplete(Connection connection) {
718        if (connection instanceof TelephonyConnection) {
719            TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
720            maybeSendInternationalCallEvent(telephonyConnection);
721        }
722    }
723
724    @Override
725    public void triggerConferenceRecalculate() {
726        if (mTelephonyConferenceController.shouldRecalculate()) {
727            mTelephonyConferenceController.recalculate();
728        }
729    }
730
731    @Override
732    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
733            ConnectionRequest request) {
734        Log.i(this, "onCreateUnknownConnection, request: " + request);
735        // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's
736        // Emergency PhoneAccount
737        PhoneAccountHandle accountHandle = request.getAccountHandle();
738        boolean isEmergency = false;
739        if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
740                accountHandle.getId())) {
741            Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " +
742                    "Treat as an Emergency Call.");
743            isEmergency = true;
744        }
745        Phone phone = getPhoneForAccount(accountHandle, isEmergency);
746        if (phone == null) {
747            return Connection.createFailedConnection(
748                    DisconnectCauseUtil.toTelecomDisconnectCause(
749                            android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
750                            "Phone is null"));
751        }
752        Bundle extras = request.getExtras();
753
754        final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>();
755
756        // Handle the case where an unknown connection has an IMS external call ID specified; we can
757        // skip the rest of the guesswork and just grad that unknown call now.
758        if (phone.getImsPhone() != null && extras != null &&
759                extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) {
760
761            ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
762            ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker();
763            int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID,
764                    -1);
765
766            if (externalCallTracker != null) {
767                com.android.internal.telephony.Connection connection =
768                        externalCallTracker.getConnectionById(externalCallId);
769
770                if (connection != null) {
771                    allConnections.add(connection);
772                }
773            }
774        }
775
776        if (allConnections.isEmpty()) {
777            final Call ringingCall = phone.getRingingCall();
778            if (ringingCall.hasConnections()) {
779                allConnections.addAll(ringingCall.getConnections());
780            }
781            final Call foregroundCall = phone.getForegroundCall();
782            if ((foregroundCall.getState() != Call.State.DISCONNECTED)
783                    && (foregroundCall.hasConnections())) {
784                allConnections.addAll(foregroundCall.getConnections());
785            }
786            if (phone.getImsPhone() != null) {
787                final Call imsFgCall = phone.getImsPhone().getForegroundCall();
788                if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall
789                        .hasConnections()) {
790                    allConnections.addAll(imsFgCall.getConnections());
791                }
792            }
793            final Call backgroundCall = phone.getBackgroundCall();
794            if (backgroundCall.hasConnections()) {
795                allConnections.addAll(phone.getBackgroundCall().getConnections());
796            }
797        }
798
799        com.android.internal.telephony.Connection unknownConnection = null;
800        for (com.android.internal.telephony.Connection telephonyConnection : allConnections) {
801            if (!isOriginalConnectionKnown(telephonyConnection)) {
802                unknownConnection = telephonyConnection;
803                Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection);
804                break;
805            }
806        }
807
808        if (unknownConnection == null) {
809            Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection.");
810            return Connection.createCanceledConnection();
811        }
812
813        // We should rely on the originalConnection to get the video state.  The request coming
814        // from Telecom does not know the video state of the unknown call.
815        int videoState = unknownConnection != null ? unknownConnection.getVideoState() :
816                VideoProfile.STATE_AUDIO_ONLY;
817
818        TelephonyConnection connection =
819                createConnectionFor(phone, unknownConnection,
820                        !unknownConnection.isIncoming() /* isOutgoing */,
821                        request.getAccountHandle(), request.getTelecomCallId(),
822                        request.getAddress(), videoState);
823
824        if (connection == null) {
825            return Connection.createCanceledConnection();
826        } else {
827            connection.updateState();
828            return connection;
829        }
830    }
831
832    /**
833     * Conferences two connections.
834     *
835     * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has
836     * a limitation in that it can only specify conferenceables which are instances of
837     * {@link android.telecom.RemoteConnection}.  In the case of an {@link ImsConference}, the
838     * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge
839     * a {@link Conference} and a {@link Connection}.  As a result when, merging a
840     * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference}
841     * require merging a {@link ConferenceParticipantConnection} which is a child of the
842     * {@link Conference} with a {@link TelephonyConnection}.  The
843     * {@link ConferenceParticipantConnection} class does not have the capability to initiate a
844     * conference merge, so we need to call
845     * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or
846     * {@code connection2}, one of which is an instance of {@link TelephonyConnection}.
847     *
848     * @param connection1 A connection to merge into a conference call.
849     * @param connection2 A connection to merge into a conference call.
850     */
851    @Override
852    public void onConference(Connection connection1, Connection connection2) {
853        if (connection1 instanceof TelephonyConnection) {
854            ((TelephonyConnection) connection1).performConference(connection2);
855        } else if (connection2 instanceof TelephonyConnection) {
856            ((TelephonyConnection) connection2).performConference(connection1);
857        } else {
858            Log.w(this, "onConference - cannot merge connections " +
859                    "Connection1: %s, Connection2: %2", connection1, connection2);
860        }
861    }
862
863    private boolean blockCallForwardingNumberWhileRoaming(Phone phone, String number) {
864        if (phone == null || TextUtils.isEmpty(number) || !phone.getServiceState().getRoaming()) {
865            return false;
866        }
867        String[] blockPrefixes = null;
868        CarrierConfigManager cfgManager = (CarrierConfigManager)
869                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
870        if (cfgManager != null) {
871            blockPrefixes = cfgManager.getConfigForSubId(phone.getSubId()).getStringArray(
872                    CarrierConfigManager.KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY);
873        }
874
875        if (blockPrefixes != null) {
876            for (String prefix : blockPrefixes) {
877                if (number.startsWith(prefix)) {
878                    return true;
879                }
880            }
881        }
882        return false;
883    }
884
885    private boolean isRadioOn() {
886        boolean result = false;
887        for (Phone phone : mPhoneFactoryProxy.getPhones()) {
888            result |= phone.isRadioOn();
889        }
890        return result;
891    }
892
893    private Pair<WeakReference<TelephonyConnection>, Queue<Phone>> makeCachedConnectionPhonePair(
894            TelephonyConnection c) {
895        Queue<Phone> phones = new LinkedList<>(Arrays.asList(mPhoneFactoryProxy.getPhones()));
896        return new Pair<>(new WeakReference<>(c), phones);
897    }
898
899    // Update the mEmergencyRetryCache by removing the Phone used to call the last failed emergency
900    // number and then moving it to the back of the queue if it is not a permanent failure cause
901    // from the modem.
902    private void updateCachedConnectionPhonePair(TelephonyConnection c,
903            boolean isPermanentFailure) {
904        // No cache exists, create a new one.
905        if (mEmergencyRetryCache == null) {
906            Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache");
907            mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
908        // Cache is stale, create a new one with the new TelephonyConnection.
909        } else if (mEmergencyRetryCache.first.get() != c) {
910            Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating.");
911            mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
912        }
913
914        Queue<Phone> cachedPhones = mEmergencyRetryCache.second;
915        Phone phoneUsed = c.getPhone();
916        if (phoneUsed == null) {
917            return;
918        }
919        // Remove phone used from the list, but for temporary fail cause, it will be added
920        // back to list further in this method. However in case of permanent failure, the
921        // phone shouldn't be reused, hence it will not be added back again.
922        cachedPhones.remove(phoneUsed);
923        Log.i(this, "updateCachedConnectionPhonePair, isPermanentFailure:" + isPermanentFailure);
924        if (!isPermanentFailure) {
925            // In case of temporary failure, add the phone back, this will result adding it
926            // to tail of list mEmergencyRetryCache.second, giving other phone more
927            // priority and that is what we want.
928            cachedPhones.offer(phoneUsed);
929        }
930    }
931
932    /**
933     * Updates a cache containing all of the slots that are available for redial at any point.
934     *
935     * - If a Connection returns with the disconnect cause EMERGENCY_TEMP_FAILURE, keep that phone
936     * in the cache, but move it to the lowest priority in the list. Then, place the emergency call
937     * on the next phone in the list.
938     * - If a Connection returns with the disconnect cause EMERGENCY_PERM_FAILURE, remove that phone
939     * from the cache and pull another phone from the cache to place the emergency call.
940     *
941     * This will continue until there are no more slots to dial on.
942     */
943    @VisibleForTesting
944    public void retryOutgoingOriginalConnection(TelephonyConnection c, boolean isPermanentFailure) {
945        int phoneId = (c.getPhone() == null) ? -1 : c.getPhone().getPhoneId();
946        updateCachedConnectionPhonePair(c, isPermanentFailure);
947        // Pull next phone to use from the cache or null if it is empty
948        Phone newPhoneToUse = (mEmergencyRetryCache.second != null)
949                ? mEmergencyRetryCache.second.peek() : null;
950        if (newPhoneToUse != null) {
951            int videoState = c.getVideoState();
952            Bundle connExtras = c.getExtras();
953            Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse);
954            c.clearOriginalConnection();
955            if (phoneId != newPhoneToUse.getPhoneId()) updatePhoneAccount(c, newPhoneToUse);
956            placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
957        } else {
958            // We have run out of Phones to use. Disconnect the call and destroy the connection.
959            Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting.");
960            c.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
961            c.clearOriginalConnection();
962            c.destroy();
963        }
964    }
965
966    private void updatePhoneAccount(TelephonyConnection connection, Phone phone) {
967        PhoneAccountHandle pHandle = PhoneUtils.makePstnPhoneAccountHandle(phone);
968        // For ECall handling on MSIM, until the request reaches here (i.e PhoneApp), we don't know
969        // on which phone account ECall can be placed. After deciding, we should notify Telecom of
970        // the change so that the proper PhoneAccount can be displayed.
971        Log.i(this, "updatePhoneAccount setPhoneAccountHandle, account = " + pHandle);
972        connection.notifyPhoneAccountChanged(pHandle);
973    }
974
975    private void placeOutgoingConnection(
976            TelephonyConnection connection, Phone phone, ConnectionRequest request) {
977        placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras());
978    }
979
980    private void placeOutgoingConnection(
981            TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
982        String number = connection.getAddress().getSchemeSpecificPart();
983
984        com.android.internal.telephony.Connection originalConnection = null;
985        try {
986            if (phone != null) {
987                originalConnection = phone.dial(number, null, videoState, extras);
988            }
989        } catch (CallStateException e) {
990            Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
991            int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
992            if (e.getError() == CallStateException.ERROR_OUT_OF_SERVICE) {
993                cause = android.telephony.DisconnectCause.OUT_OF_SERVICE;
994            } else if (e.getError() == CallStateException.ERROR_POWER_OFF) {
995                cause = android.telephony.DisconnectCause.POWER_OFF;
996            }
997            connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
998                    cause, e.getMessage()));
999            return;
1000        }
1001
1002        if (originalConnection == null) {
1003            int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
1004            // On GSM phones, null connection means that we dialed an MMI code
1005            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
1006                Log.d(this, "dialed MMI code");
1007                int subId = phone.getSubId();
1008                Log.d(this, "subId: "+subId);
1009                telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
1010                final Intent intent = new Intent(this, MMIDialogActivity.class);
1011                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
1012                        Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1013                if (SubscriptionManager.isValidSubscriptionId(subId)) {
1014                    intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
1015                }
1016                startActivity(intent);
1017            }
1018            Log.d(this, "placeOutgoingConnection, phone.dial returned null");
1019            connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
1020                    telephonyDisconnectCause, "Connection is null"));
1021        } else {
1022            connection.setOriginalConnection(originalConnection);
1023        }
1024    }
1025
1026    private TelephonyConnection createConnectionFor(
1027            Phone phone,
1028            com.android.internal.telephony.Connection originalConnection,
1029            boolean isOutgoing,
1030            PhoneAccountHandle phoneAccountHandle,
1031            String telecomCallId,
1032            Uri address,
1033            int videoState) {
1034        TelephonyConnection returnConnection = null;
1035        int phoneType = phone.getPhoneType();
1036        if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
1037            returnConnection = new GsmConnection(originalConnection, telecomCallId, isOutgoing);
1038        } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
1039            boolean allowsMute = allowsMute(phone);
1040            returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer,
1041                    allowsMute, isOutgoing, telecomCallId);
1042        }
1043        if (returnConnection != null) {
1044            // Listen to Telephony specific callbacks from the connection
1045            returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener);
1046            returnConnection.setVideoPauseSupported(
1047                    TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
1048                            phoneAccountHandle));
1049            returnConnection.setManageImsConferenceCallSupported(
1050                    TelecomAccountRegistry.getInstance(this).isManageImsConferenceCallSupported(
1051                            phoneAccountHandle));
1052        }
1053        return returnConnection;
1054    }
1055
1056    private boolean isOriginalConnectionKnown(
1057            com.android.internal.telephony.Connection originalConnection) {
1058        for (Connection connection : getAllConnections()) {
1059            if (connection instanceof TelephonyConnection) {
1060                TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
1061                if (telephonyConnection.getOriginalConnection() == originalConnection) {
1062                    return true;
1063                }
1064            }
1065        }
1066        return false;
1067    }
1068
1069    private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
1070        Phone chosenPhone = null;
1071        int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
1072        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
1073            int phoneId = mSubscriptionManagerProxy.getPhoneId(subId);
1074            chosenPhone = mPhoneFactoryProxy.getPhone(phoneId);
1075        }
1076        // If this is an emergency call and the phone we originally planned to make this call
1077        // with is not in service or was invalid, try to find one that is in service, using the
1078        // default as a last chance backup.
1079        if (isEmergency && (chosenPhone == null || ServiceState.STATE_IN_SERVICE != chosenPhone
1080                .getServiceState().getState())) {
1081            Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
1082                    + "or invalid for emergency call.", accountHandle);
1083            chosenPhone = getFirstPhoneForEmergencyCall();
1084            Log.d(this, "getPhoneForAccount: using subId: " +
1085                    (chosenPhone == null ? "null" : chosenPhone.getSubId()));
1086        }
1087        return chosenPhone;
1088    }
1089
1090    /**
1091     * Retrieves the most sensible Phone to use for an emergency call using the following Priority
1092     *  list (for multi-SIM devices):
1093     *  1) The User's SIM preference for Voice calling
1094     *  2) The First Phone that is currently IN_SERVICE or is available for emergency calling
1095     *  3) If there is a PUK locked SIM, compare the SIMs that are not PUK locked. If all the SIMs
1096     *     are locked, skip to condition 4).
1097     *  4) The Phone with more Capabilities.
1098     *  5) The First Phone that has a SIM card in it (Starting from Slot 0...N)
1099     *  6) The Default Phone (Currently set as Slot 0)
1100     */
1101    @VisibleForTesting
1102    public Phone getFirstPhoneForEmergencyCall() {
1103        // 1)
1104        int phoneId = mSubscriptionManagerProxy.getDefaultVoicePhoneId();
1105        if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
1106            Phone defaultPhone = mPhoneFactoryProxy.getPhone(phoneId);
1107            if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) {
1108                return defaultPhone;
1109            }
1110        }
1111
1112        Phone firstPhoneWithSim = null;
1113        int phoneCount = mTelephonyManagerProxy.getPhoneCount();
1114        List<SlotStatus> phoneSlotStatus = new ArrayList<>(phoneCount);
1115        for (int i = 0; i < phoneCount; i++) {
1116            Phone phone = mPhoneFactoryProxy.getPhone(i);
1117            if (phone == null) {
1118                continue;
1119            }
1120            // 2)
1121            if (isAvailableForEmergencyCalls(phone)) {
1122                // the slot has the radio on & state is in service.
1123                Log.i(this, "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i);
1124                return phone;
1125            }
1126            // 4)
1127            // Store the RAF Capabilities for sorting later.
1128            int radioAccessFamily = phone.getRadioAccessFamily();
1129            SlotStatus status = new SlotStatus(i, radioAccessFamily);
1130            phoneSlotStatus.add(status);
1131            Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" +
1132                    Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i);
1133            // 3)
1134            // Report Slot's PIN/PUK lock status for sorting later.
1135            int simState = mSubscriptionManagerProxy.getSimStateForSlotIdx(i);
1136            if (simState == TelephonyManager.SIM_STATE_PIN_REQUIRED ||
1137                    simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) {
1138                status.isLocked = true;
1139            }
1140            // 5)
1141            if (firstPhoneWithSim == null && mTelephonyManagerProxy.hasIccCard(i)) {
1142                // The slot has a SIM card inserted, but is not in service, so keep track of this
1143                // Phone. Do not return because we want to make sure that none of the other Phones
1144                // are in service (because that is always faster).
1145                firstPhoneWithSim = phone;
1146                Log.i(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" +
1147                        firstPhoneWithSim.getPhoneId());
1148            }
1149        }
1150        // 6)
1151        if (firstPhoneWithSim == null && phoneSlotStatus.isEmpty()) {
1152            // No Phones available, get the default.
1153            Log.i(this, "getFirstPhoneForEmergencyCall, return default phone");
1154            return mPhoneFactoryProxy.getDefaultPhone();
1155        } else {
1156            // 4)
1157            final int defaultPhoneId = mPhoneFactoryProxy.getDefaultPhone().getPhoneId();
1158            final Phone firstOccupiedSlot = firstPhoneWithSim;
1159            if (!phoneSlotStatus.isEmpty()) {
1160                // Only sort if there are enough elements to do so.
1161                if (phoneSlotStatus.size() > 1) {
1162                    Collections.sort(phoneSlotStatus, (o1, o2) -> {
1163                        // First start by seeing if either of the phone slots are locked. If they
1164                        // are, then sort by non-locked SIM first. If they are both locked, sort
1165                        // by capability instead.
1166                        if (o1.isLocked && !o2.isLocked) {
1167                            return -1;
1168                        }
1169                        if (o2.isLocked && !o1.isLocked) {
1170                            return 1;
1171                        }
1172                        // sort by number of RadioAccessFamily Capabilities.
1173                        int compare = Integer.bitCount(o1.capabilities) -
1174                                Integer.bitCount(o2.capabilities);
1175                        if (compare == 0) {
1176                            // Sort by highest RAF Capability if the number is the same.
1177                            compare = RadioAccessFamily.getHighestRafCapability(o1.capabilities) -
1178                                    RadioAccessFamily.getHighestRafCapability(o2.capabilities);
1179                            if (compare == 0) {
1180                                if (firstOccupiedSlot != null) {
1181                                    // If the RAF capability is the same, choose based on whether or
1182                                    // not any of the slots are occupied with a SIM card (if both
1183                                    // are, always choose the first).
1184                                    if (o1.slotId == firstOccupiedSlot.getPhoneId()) {
1185                                        return 1;
1186                                    } else if (o2.slotId == firstOccupiedSlot.getPhoneId()) {
1187                                        return -1;
1188                                    }
1189                                } else {
1190                                    // No slots have SIMs detected in them, so weight the default
1191                                    // Phone Id greater than the others.
1192                                    if (o1.slotId == defaultPhoneId) {
1193                                        return 1;
1194                                    } else if (o2.slotId == defaultPhoneId) {
1195                                        return -1;
1196                                    }
1197                                }
1198                            }
1199                        }
1200                        return compare;
1201                    });
1202                }
1203                int mostCapablePhoneId = phoneSlotStatus.get(phoneSlotStatus.size() - 1).slotId;
1204                Log.i(this, "getFirstPhoneForEmergencyCall, Using Phone Id: " + mostCapablePhoneId +
1205                        "with highest capability");
1206                return mPhoneFactoryProxy.getPhone(mostCapablePhoneId);
1207            } else {
1208                // 5)
1209                return firstPhoneWithSim;
1210            }
1211        }
1212    }
1213
1214    /**
1215     * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only.
1216     */
1217    private boolean isAvailableForEmergencyCalls(Phone phone) {
1218        return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() ||
1219                phone.getServiceState().isEmergencyOnly();
1220    }
1221
1222    /**
1223     * Determines if the connection should allow mute.
1224     *
1225     * @param phone The current phone.
1226     * @return {@code True} if the connection should allow mute.
1227     */
1228    private boolean allowsMute(Phone phone) {
1229        // For CDMA phones, check if we are in Emergency Callback Mode (ECM).  Mute is disallowed
1230        // in ECM mode.
1231        if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
1232            if (phone.isInEcm()) {
1233                return false;
1234            }
1235        }
1236
1237        return true;
1238    }
1239
1240    @Override
1241    public void removeConnection(Connection connection) {
1242        super.removeConnection(connection);
1243        if (connection instanceof TelephonyConnection) {
1244            TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
1245            telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
1246        }
1247    }
1248
1249    /**
1250     * When a {@link TelephonyConnection} has its underlying original connection configured,
1251     * we need to add it to the correct conference controller.
1252     *
1253     * @param connection The connection to be added to the controller
1254     */
1255    public void addConnectionToConferenceController(TelephonyConnection connection) {
1256        // TODO: Need to revisit what happens when the original connection for the
1257        // TelephonyConnection changes.  If going from CDMA --> GSM (for example), the
1258        // instance of TelephonyConnection will still be a CdmaConnection, not a GsmConnection.
1259        // The CDMA conference controller makes the assumption that it will only have CDMA
1260        // connections in it, while the other conference controllers aren't as restrictive.  Really,
1261        // when we go between CDMA and GSM we should replace the TelephonyConnection.
1262        if (connection.isImsConnection()) {
1263            Log.d(this, "Adding IMS connection to conference controller: " + connection);
1264            mImsConferenceController.add(connection);
1265            mTelephonyConferenceController.remove(connection);
1266            if (connection instanceof CdmaConnection) {
1267                mCdmaConferenceController.remove((CdmaConnection) connection);
1268            }
1269        } else {
1270            int phoneType = connection.getCall().getPhone().getPhoneType();
1271            if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
1272                Log.d(this, "Adding GSM connection to conference controller: " + connection);
1273                mTelephonyConferenceController.add(connection);
1274                if (connection instanceof CdmaConnection) {
1275                    mCdmaConferenceController.remove((CdmaConnection) connection);
1276                }
1277            } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA &&
1278                    connection instanceof CdmaConnection) {
1279                Log.d(this, "Adding CDMA connection to conference controller: " + connection);
1280                mCdmaConferenceController.add((CdmaConnection) connection);
1281                mTelephonyConferenceController.remove(connection);
1282            }
1283            Log.d(this, "Removing connection from IMS conference controller: " + connection);
1284            mImsConferenceController.remove(connection);
1285        }
1286    }
1287
1288    /**
1289     * Create a new CDMA connection. CDMA connections have additional limitations when creating
1290     * additional calls which are handled in this method.  Specifically, CDMA has a "FLASH" command
1291     * that can be used for three purposes: merging a call, swapping unmerged calls, and adding
1292     * a new outgoing call. The function of the flash command depends on the context of the current
1293     * set of calls. This method will prevent an outgoing call from being made if it is not within
1294     * the right circumstances to support adding a call.
1295     */
1296    private Connection checkAdditionalOutgoingCallLimits(Phone phone) {
1297        if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
1298            // Check to see if any CDMA conference calls exist, and if they do, check them for
1299            // limitations.
1300            for (Conference conference : getAllConferences()) {
1301                if (conference instanceof CdmaConference) {
1302                    CdmaConference cdmaConf = (CdmaConference) conference;
1303
1304                    // If the CDMA conference has not been merged, add-call will not work, so fail
1305                    // this request to add a call.
1306                    if (cdmaConf.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
1307                        return Connection.createFailedConnection(new DisconnectCause(
1308                                    DisconnectCause.RESTRICTED,
1309                                    null,
1310                                    getResources().getString(R.string.callFailed_cdma_call_limit),
1311                                    "merge-capable call exists, prevent flash command."));
1312                    }
1313                }
1314            }
1315        }
1316
1317        return null; // null means nothing went wrong, and call should continue.
1318    }
1319
1320    private boolean isTtyModeEnabled(Context context) {
1321        return (android.provider.Settings.Secure.getInt(
1322                context.getContentResolver(),
1323                android.provider.Settings.Secure.PREFERRED_TTY_MODE,
1324                TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF);
1325    }
1326
1327    /**
1328     * For outgoing dialed calls, potentially send a ConnectionEvent if the user is on WFC and is
1329     * dialing an international number.
1330     * @param telephonyConnection The connection.
1331     */
1332    private void maybeSendInternationalCallEvent(TelephonyConnection telephonyConnection) {
1333        if (telephonyConnection == null || telephonyConnection.getPhone() == null ||
1334                telephonyConnection.getPhone().getDefaultPhone() == null) {
1335            return;
1336        }
1337        Phone phone = telephonyConnection.getPhone().getDefaultPhone();
1338        if (phone instanceof GsmCdmaPhone) {
1339            GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone;
1340            if (telephonyConnection.isOutgoingCall() &&
1341                    gsmCdmaPhone.isNotificationOfWfcCallRequired(
1342                            telephonyConnection.getOriginalConnection().getOrigDialString())) {
1343                // Send connection event to InCall UI to inform the user of the fact they
1344                // are potentially placing an international call on WFC.
1345                Log.i(this, "placeOutgoingConnection - sending international call on WFC " +
1346                        "confirmation event");
1347                telephonyConnection.sendConnectionEvent(
1348                        TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null);
1349            }
1350        }
1351    }
1352}
1353