TelephonyConnectionService.java revision d6cd279c84523443107ede08871636a0128f1b3a
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.telecom.Conference;
26import android.telecom.Connection;
27import android.telecom.ConnectionRequest;
28import android.telecom.ConnectionService;
29import android.telecom.DisconnectCause;
30import android.telecom.PhoneAccount;
31import android.telecom.PhoneAccountHandle;
32import android.telecom.TelecomManager;
33import android.telecom.VideoProfile;
34import android.telephony.CarrierConfigManager;
35import android.telephony.PhoneNumberUtils;
36import android.telephony.RadioAccessFamily;
37import android.telephony.ServiceState;
38import android.telephony.SubscriptionManager;
39import android.telephony.TelephonyManager;
40import android.text.TextUtils;
41import android.util.Pair;
42
43import com.android.internal.telephony.Call;
44import com.android.internal.telephony.CallStateException;
45import com.android.internal.telephony.GsmCdmaPhone;
46import com.android.internal.telephony.IccCard;
47import com.android.internal.telephony.IccCardConstants;
48import com.android.internal.telephony.Phone;
49import com.android.internal.telephony.PhoneConstants;
50import com.android.internal.telephony.PhoneFactory;
51import com.android.internal.telephony.imsphone.ImsExternalCallTracker;
52import com.android.internal.telephony.imsphone.ImsPhone;
53import com.android.phone.MMIDialogActivity;
54import com.android.phone.PhoneUtils;
55import com.android.phone.R;
56
57import java.lang.ref.WeakReference;
58import java.util.ArrayList;
59import java.util.Arrays;
60import java.util.Collection;
61import java.util.Collections;
62import java.util.List;
63import java.util.regex.Pattern;
64
65/**
66 * Service for making GSM and CDMA connections.
67 */
68public class TelephonyConnectionService extends ConnectionService {
69
70    // If configured, reject attempts to dial numbers matching this pattern.
71    private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
72            Pattern.compile("\\*228[0-9]{0,2}");
73
74    private final TelephonyConferenceController mTelephonyConferenceController =
75            new TelephonyConferenceController(this);
76    private final CdmaConferenceController mCdmaConferenceController =
77            new CdmaConferenceController(this);
78    private final ImsConferenceController mImsConferenceController =
79            new ImsConferenceController(this);
80
81    private ComponentName mExpectedComponentName = null;
82    private EmergencyCallHelper mEmergencyCallHelper;
83    private EmergencyTonePlayer mEmergencyTonePlayer;
84
85    // Contains one TelephonyConnection that has placed a call and a memory of which Phones it has
86    // already tried to connect with. There should be only one TelephonyConnection trying to place a
87    // call at one time. We also only access this cache from a TelephonyConnection that wishes to
88    // redial, so we use a WeakReference that will become stale once the TelephonyConnection is
89    // destroyed.
90    private Pair<WeakReference<TelephonyConnection>, List<Phone>> mEmergencyRetryCache;
91
92    /**
93     * A listener to actionable events specific to the TelephonyConnection.
94     */
95    private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
96            new TelephonyConnection.TelephonyConnectionListener() {
97        @Override
98        public void onOriginalConnectionConfigured(TelephonyConnection c) {
99            addConnectionToConferenceController(c);
100        }
101
102        @Override
103        public void onOriginalConnectionRetry(TelephonyConnection c) {
104            retryOutgoingOriginalConnection(c);
105        }
106    };
107
108    @Override
109    public void onCreate() {
110        super.onCreate();
111        Log.initLogging(this);
112        mExpectedComponentName = new ComponentName(this, this.getClass());
113        mEmergencyTonePlayer = new EmergencyTonePlayer(this);
114        TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
115    }
116
117    @Override
118    public Connection onCreateOutgoingConnection(
119            PhoneAccountHandle connectionManagerPhoneAccount,
120            final ConnectionRequest request) {
121        Log.i(this, "onCreateOutgoingConnection, request: " + request);
122
123        Uri handle = request.getAddress();
124        if (handle == null) {
125            Log.d(this, "onCreateOutgoingConnection, handle is null");
126            return Connection.createFailedConnection(
127                    DisconnectCauseUtil.toTelecomDisconnectCause(
128                            android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
129                            "No phone number supplied"));
130        }
131
132        String scheme = handle.getScheme();
133        String number;
134        if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
135            // TODO: We don't check for SecurityException here (requires
136            // CALL_PRIVILEGED permission).
137            final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
138            if (phone == null) {
139                Log.d(this, "onCreateOutgoingConnection, phone is null");
140                return Connection.createFailedConnection(
141                        DisconnectCauseUtil.toTelecomDisconnectCause(
142                                android.telephony.DisconnectCause.OUT_OF_SERVICE,
143                                "Phone is null"));
144            }
145            number = phone.getVoiceMailNumber();
146            if (TextUtils.isEmpty(number)) {
147                Log.d(this, "onCreateOutgoingConnection, no voicemail number set.");
148                return Connection.createFailedConnection(
149                        DisconnectCauseUtil.toTelecomDisconnectCause(
150                                android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING,
151                                "Voicemail scheme provided but no voicemail number set."));
152            }
153
154            // Convert voicemail: to tel:
155            handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
156        } else {
157            if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
158                Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme);
159                return Connection.createFailedConnection(
160                        DisconnectCauseUtil.toTelecomDisconnectCause(
161                                android.telephony.DisconnectCause.INVALID_NUMBER,
162                                "Handle scheme is not type tel"));
163            }
164
165            number = handle.getSchemeSpecificPart();
166            if (TextUtils.isEmpty(number)) {
167                Log.d(this, "onCreateOutgoingConnection, unable to parse number");
168                return Connection.createFailedConnection(
169                        DisconnectCauseUtil.toTelecomDisconnectCause(
170                                android.telephony.DisconnectCause.INVALID_NUMBER,
171                                "Unable to parse number"));
172            }
173
174            final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
175            if (phone != null && CDMA_ACTIVATION_CODE_REGEX_PATTERN.matcher(number).matches()) {
176                // Obtain the configuration for the outgoing phone's SIM. If the outgoing number
177                // matches the *228 regex pattern, fail the call. This number is used for OTASP, and
178                // when dialed could lock LTE SIMs to 3G if not prohibited..
179                boolean disableActivation = false;
180                CarrierConfigManager cfgManager = (CarrierConfigManager)
181                        phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
182                if (cfgManager != null) {
183                    disableActivation = cfgManager.getConfigForSubId(phone.getSubId())
184                            .getBoolean(CarrierConfigManager.KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL);
185                }
186
187                if (disableActivation) {
188                    return Connection.createFailedConnection(
189                            DisconnectCauseUtil.toTelecomDisconnectCause(
190                                    android.telephony.DisconnectCause
191                                            .CDMA_ALREADY_ACTIVATED,
192                                    "Tried to dial *228"));
193                }
194            }
195        }
196
197        // Convert into emergency number if necessary
198        // This is required in some regions (e.g. Taiwan).
199        if (!PhoneNumberUtils.isLocalEmergencyNumber(this, number) &&
200                PhoneNumberUtils.isConvertToEmergencyNumberEnabled()) {
201            final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
202            // We only do the conversion if the phone is not in service. The un-converted
203            // emergency numbers will go to the correct destination when the phone is in-service,
204            // so they will only need the special emergency call setup when the phone is out of
205            // service.
206            if (phone == null || phone.getServiceState().getState()
207                    != ServiceState.STATE_IN_SERVICE) {
208                String convertedNumber = PhoneNumberUtils.convertToEmergencyNumber(number);
209                if (!TextUtils.equals(convertedNumber, number)) {
210                    Log.i(this, "onCreateOutgoingConnection, converted to emergency number");
211                    number = convertedNumber;
212                    handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
213                }
214            }
215        }
216        final String numberToDial = number;
217
218        final boolean isEmergencyNumber =
219                PhoneNumberUtils.isLocalEmergencyNumber(this, numberToDial);
220
221        if (isEmergencyNumber && !isRadioOn()) {
222            final Uri emergencyHandle = handle;
223            // By default, Connection based on the default Phone, since we need to return to Telecom
224            // now.
225            final int defaultPhoneType = PhoneFactory.getDefaultPhone().getPhoneType();
226            final Connection emergencyConnection = getTelephonyConnection(request, numberToDial,
227                    isEmergencyNumber, emergencyHandle, PhoneFactory.getDefaultPhone());
228            if (mEmergencyCallHelper == null) {
229                mEmergencyCallHelper = new EmergencyCallHelper(this);
230            }
231            mEmergencyCallHelper.enableEmergencyCalling(new EmergencyCallStateListener.Callback() {
232                @Override
233                public void onComplete(EmergencyCallStateListener listener, boolean isRadioReady) {
234                    // Make sure the Call has not already been canceled by the user.
235                    if (emergencyConnection.getState() == Connection.STATE_DISCONNECTED) {
236                        Log.i(this, "Emergency call disconnected before the outgoing call was " +
237                                "placed. Skipping emergency call placement.");
238                        return;
239                    }
240                    if (isRadioReady) {
241                        // Get the right phone object since the radio has been turned on
242                        // successfully.
243                        final Phone phone = getPhoneForAccount(request.getAccountHandle(),
244                                isEmergencyNumber);
245                        // If the PhoneType of the Phone being used is different than the Default
246                        // Phone, then we need create a new Connection using that PhoneType and
247                        // replace it in Telecom.
248                        if (phone.getPhoneType() != defaultPhoneType) {
249                            Connection repConnection = getTelephonyConnection(request, numberToDial,
250                                    isEmergencyNumber, emergencyHandle, phone);
251                            // If there was a failure, the resulting connection will not be a
252                            // TelephonyConnection, so don't place the call, just return!
253                            if (repConnection instanceof TelephonyConnection) {
254                                placeOutgoingConnection((TelephonyConnection) repConnection, phone,
255                                        request);
256                            }
257                            // Notify Telecom of the new Connection type.
258                            // TODO: Switch out the underlying connection instead of creating a new
259                            // one and causing UI Jank.
260                            addExistingConnection(PhoneUtils.makePstnPhoneAccountHandle(phone),
261                                    repConnection);
262                            // Remove the old connection from Telecom after.
263                            emergencyConnection.setDisconnected(
264                                    DisconnectCauseUtil.toTelecomDisconnectCause(
265                                            android.telephony.DisconnectCause.OUTGOING_CANCELED,
266                                            "Reconnecting outgoing Emergency Call."));
267                            emergencyConnection.destroy();
268                        } else {
269                            placeOutgoingConnection((TelephonyConnection) emergencyConnection,
270                                    phone, request);
271                        }
272                    } else {
273                        Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
274                        emergencyConnection.setDisconnected(
275                                DisconnectCauseUtil.toTelecomDisconnectCause(
276                                        android.telephony.DisconnectCause.POWER_OFF,
277                                        "Failed to turn on radio."));
278                        emergencyConnection.destroy();
279                    }
280                }
281            });
282            // Return the still unconnected GsmConnection and wait for the Radios to boot before
283            // connecting it to the underlying Phone.
284            return emergencyConnection;
285        } else {
286            if (!canAddCall() && !isEmergencyNumber) {
287                Log.d(this, "onCreateOutgoingConnection, cannot add call .");
288                return Connection.createFailedConnection(
289                        new DisconnectCause(DisconnectCause.ERROR,
290                                getApplicationContext().getText(
291                                        R.string.incall_error_cannot_add_call),
292                                getApplicationContext().getText(
293                                        R.string.incall_error_cannot_add_call),
294                                "Add call restricted due to ongoing video call"));
295            }
296
297            // Get the right phone object from the account data passed in.
298            final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
299            Connection resultConnection = getTelephonyConnection(request, numberToDial,
300                    isEmergencyNumber, handle, phone);
301            // If there was a failure, the resulting connection will not be a TelephonyConnection,
302            // so don't place the call!
303            if(resultConnection instanceof TelephonyConnection) {
304                placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
305            }
306            return resultConnection;
307        }
308    }
309
310    /**
311     * @return {@code true} if any other call is disabling the ability to add calls, {@code false}
312     *      otherwise.
313     */
314    private boolean canAddCall() {
315        Collection<Connection> connections = getAllConnections();
316        for (Connection connection : connections) {
317            if (connection.getExtras() != null &&
318                    connection.getExtras().getBoolean(Connection.EXTRA_DISABLE_ADD_CALL, false)) {
319                return false;
320            }
321        }
322        return true;
323    }
324
325    private Connection getTelephonyConnection(final ConnectionRequest request, final String number,
326            boolean isEmergencyNumber, final Uri handle, Phone phone) {
327
328        if (phone == null) {
329            final Context context = getApplicationContext();
330            if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) {
331                // Check SIM card state before the outgoing call.
332                // Start the SIM unlock activity if PIN_REQUIRED.
333                final Phone defaultPhone = PhoneFactory.getDefaultPhone();
334                final IccCard icc = defaultPhone.getIccCard();
335                IccCardConstants.State simState = IccCardConstants.State.UNKNOWN;
336                if (icc != null) {
337                    simState = icc.getState();
338                }
339                if (simState == IccCardConstants.State.PIN_REQUIRED) {
340                    final String simUnlockUiPackage = context.getResources().getString(
341                            R.string.config_simUnlockUiPackage);
342                    final String simUnlockUiClass = context.getResources().getString(
343                            R.string.config_simUnlockUiClass);
344                    if (simUnlockUiPackage != null && simUnlockUiClass != null) {
345                        Intent simUnlockIntent = new Intent().setComponent(new ComponentName(
346                                simUnlockUiPackage, simUnlockUiClass));
347                        simUnlockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
348                        try {
349                            context.startActivity(simUnlockIntent);
350                        } catch (ActivityNotFoundException exception) {
351                            Log.e(this, exception, "Unable to find SIM unlock UI activity.");
352                        }
353                    }
354                    return Connection.createFailedConnection(
355                            DisconnectCauseUtil.toTelecomDisconnectCause(
356                                    android.telephony.DisconnectCause.OUT_OF_SERVICE,
357                                    "SIM_STATE_PIN_REQUIRED"));
358                }
359            }
360
361            Log.d(this, "onCreateOutgoingConnection, phone is null");
362            return Connection.createFailedConnection(
363                    DisconnectCauseUtil.toTelecomDisconnectCause(
364                            android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null"));
365        }
366
367        // Check both voice & data RAT to enable normal CS call,
368        // when voice RAT is OOS but Data RAT is present.
369        int state = phone.getServiceState().getState();
370        if (state == ServiceState.STATE_OUT_OF_SERVICE) {
371            int dataNetType = phone.getServiceState().getDataNetworkType();
372            if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
373                    dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
374                state = phone.getServiceState().getDataRegState();
375            }
376        }
377
378        // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if
379        // carrier configuration specifies that we cannot make non-emergency calls in ECM mode.
380        if (!isEmergencyNumber && phone.isInEcm()) {
381            boolean allowNonEmergencyCalls = true;
382            CarrierConfigManager cfgManager = (CarrierConfigManager)
383                    phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
384            if (cfgManager != null) {
385                allowNonEmergencyCalls = cfgManager.getConfigForSubId(phone.getSubId())
386                        .getBoolean(CarrierConfigManager.KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL);
387            }
388
389            if (!allowNonEmergencyCalls) {
390                return Connection.createFailedConnection(
391                        DisconnectCauseUtil.toTelecomDisconnectCause(
392                                android.telephony.DisconnectCause.CDMA_NOT_EMERGENCY,
393                                "Cannot make non-emergency call in ECM mode."
394                        ));
395            }
396        }
397
398        if (!isEmergencyNumber) {
399            switch (state) {
400                case ServiceState.STATE_IN_SERVICE:
401                case ServiceState.STATE_EMERGENCY_ONLY:
402                    break;
403                case ServiceState.STATE_OUT_OF_SERVICE:
404                    if (phone.isUtEnabled() && number.endsWith("#")) {
405                        Log.d(this, "onCreateOutgoingConnection dial for UT");
406                        break;
407                    } else {
408                        return Connection.createFailedConnection(
409                                DisconnectCauseUtil.toTelecomDisconnectCause(
410                                        android.telephony.DisconnectCause.OUT_OF_SERVICE,
411                                        "ServiceState.STATE_OUT_OF_SERVICE"));
412                    }
413                case ServiceState.STATE_POWER_OFF:
414                    return Connection.createFailedConnection(
415                            DisconnectCauseUtil.toTelecomDisconnectCause(
416                                    android.telephony.DisconnectCause.POWER_OFF,
417                                    "ServiceState.STATE_POWER_OFF"));
418                default:
419                    Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state);
420                    return Connection.createFailedConnection(
421                            DisconnectCauseUtil.toTelecomDisconnectCause(
422                                    android.telephony.DisconnectCause.OUTGOING_FAILURE,
423                                    "Unknown service state " + state));
424            }
425        }
426
427        final Context context = getApplicationContext();
428        if (VideoProfile.isVideo(request.getVideoState()) && isTtyModeEnabled(context) &&
429                !isEmergencyNumber) {
430            return Connection.createFailedConnection(DisconnectCauseUtil.toTelecomDisconnectCause(
431                    android.telephony.DisconnectCause.VIDEO_CALL_NOT_ALLOWED_WHILE_TTY_ENABLED));
432        }
433
434        // Check for additional limits on CDMA phones.
435        final Connection failedConnection = checkAdditionalOutgoingCallLimits(phone);
436        if (failedConnection != null) {
437            return failedConnection;
438        }
439
440        final TelephonyConnection connection =
441                createConnectionFor(phone, null, true /* isOutgoing */, request.getAccountHandle(),
442                        request.getTelecomCallId(), request.getAddress(), request.getVideoState());
443        if (connection == null) {
444            return Connection.createFailedConnection(
445                    DisconnectCauseUtil.toTelecomDisconnectCause(
446                            android.telephony.DisconnectCause.OUTGOING_FAILURE,
447                            "Invalid phone type"));
448        }
449        connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
450        connection.setInitializing();
451        connection.setVideoState(request.getVideoState());
452
453        return connection;
454    }
455
456    @Override
457    public Connection onCreateIncomingConnection(
458            PhoneAccountHandle connectionManagerPhoneAccount,
459            ConnectionRequest request) {
460        Log.i(this, "onCreateIncomingConnection, request: " + request);
461        // If there is an incoming emergency CDMA Call (while the phone is in ECBM w/ No SIM),
462        // make sure the PhoneAccount lookup retrieves the default Emergency Phone.
463        PhoneAccountHandle accountHandle = request.getAccountHandle();
464        boolean isEmergency = false;
465        if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
466                accountHandle.getId())) {
467            Log.i(this, "Emergency PhoneAccountHandle is being used for incoming call... " +
468                    "Treat as an Emergency Call.");
469            isEmergency = true;
470        }
471        Phone phone = getPhoneForAccount(accountHandle, isEmergency);
472        if (phone == null) {
473            return Connection.createFailedConnection(
474                    DisconnectCauseUtil.toTelecomDisconnectCause(
475                            android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
476                            "Phone is null"));
477        }
478
479        Call call = phone.getRingingCall();
480        if (!call.getState().isRinging()) {
481            Log.i(this, "onCreateIncomingConnection, no ringing call");
482            return Connection.createFailedConnection(
483                    DisconnectCauseUtil.toTelecomDisconnectCause(
484                            android.telephony.DisconnectCause.INCOMING_MISSED,
485                            "Found no ringing call"));
486        }
487
488        com.android.internal.telephony.Connection originalConnection =
489                call.getState() == Call.State.WAITING ?
490                    call.getLatestConnection() : call.getEarliestConnection();
491        if (isOriginalConnectionKnown(originalConnection)) {
492            Log.i(this, "onCreateIncomingConnection, original connection already registered");
493            return Connection.createCanceledConnection();
494        }
495
496        // We should rely on the originalConnection to get the video state.  The request coming
497        // from Telecom does not know the video state of the incoming call.
498        int videoState = originalConnection != null ? originalConnection.getVideoState() :
499                VideoProfile.STATE_AUDIO_ONLY;
500
501        Connection connection =
502                createConnectionFor(phone, originalConnection, false /* isOutgoing */,
503                        request.getAccountHandle(), request.getTelecomCallId(),
504                        request.getAddress(), videoState);
505        if (connection == null) {
506            return Connection.createCanceledConnection();
507        } else {
508            return connection;
509        }
510    }
511
512    @Override
513    public void triggerConferenceRecalculate() {
514        if (mTelephonyConferenceController.shouldRecalculate()) {
515            mTelephonyConferenceController.recalculate();
516        }
517    }
518
519    @Override
520    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
521            ConnectionRequest request) {
522        Log.i(this, "onCreateUnknownConnection, request: " + request);
523        // Use the registered emergency Phone if the PhoneAccountHandle is set to Telephony's
524        // Emergency PhoneAccount
525        PhoneAccountHandle accountHandle = request.getAccountHandle();
526        boolean isEmergency = false;
527        if (accountHandle != null && PhoneUtils.EMERGENCY_ACCOUNT_HANDLE_ID.equals(
528                accountHandle.getId())) {
529            Log.i(this, "Emergency PhoneAccountHandle is being used for unknown call... " +
530                    "Treat as an Emergency Call.");
531            isEmergency = true;
532        }
533        Phone phone = getPhoneForAccount(accountHandle, isEmergency);
534        if (phone == null) {
535            return Connection.createFailedConnection(
536                    DisconnectCauseUtil.toTelecomDisconnectCause(
537                            android.telephony.DisconnectCause.ERROR_UNSPECIFIED,
538                            "Phone is null"));
539        }
540        Bundle extras = request.getExtras();
541
542        final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>();
543
544        // Handle the case where an unknown connection has an IMS external call ID specified; we can
545        // skip the rest of the guesswork and just grad that unknown call now.
546        if (phone.getImsPhone() != null && extras != null &&
547                extras.containsKey(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID)) {
548
549            ImsPhone imsPhone = (ImsPhone) phone.getImsPhone();
550            ImsExternalCallTracker externalCallTracker = imsPhone.getExternalCallTracker();
551            int externalCallId = extras.getInt(ImsExternalCallTracker.EXTRA_IMS_EXTERNAL_CALL_ID,
552                    -1);
553
554            if (externalCallTracker != null) {
555                com.android.internal.telephony.Connection connection =
556                        externalCallTracker.getConnectionById(externalCallId);
557
558                if (connection != null) {
559                    allConnections.add(connection);
560                }
561            }
562        }
563
564        if (allConnections.isEmpty()) {
565            final Call ringingCall = phone.getRingingCall();
566            if (ringingCall.hasConnections()) {
567                allConnections.addAll(ringingCall.getConnections());
568            }
569            final Call foregroundCall = phone.getForegroundCall();
570            if ((foregroundCall.getState() != Call.State.DISCONNECTED)
571                    && (foregroundCall.hasConnections())) {
572                allConnections.addAll(foregroundCall.getConnections());
573            }
574            if (phone.getImsPhone() != null) {
575                final Call imsFgCall = phone.getImsPhone().getForegroundCall();
576                if ((imsFgCall.getState() != Call.State.DISCONNECTED) && imsFgCall
577                        .hasConnections()) {
578                    allConnections.addAll(imsFgCall.getConnections());
579                }
580            }
581            final Call backgroundCall = phone.getBackgroundCall();
582            if (backgroundCall.hasConnections()) {
583                allConnections.addAll(phone.getBackgroundCall().getConnections());
584            }
585        }
586
587        com.android.internal.telephony.Connection unknownConnection = null;
588        for (com.android.internal.telephony.Connection telephonyConnection : allConnections) {
589            if (!isOriginalConnectionKnown(telephonyConnection)) {
590                unknownConnection = telephonyConnection;
591                Log.d(this, "onCreateUnknownConnection: conn = " + unknownConnection);
592                break;
593            }
594        }
595
596        if (unknownConnection == null) {
597            Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection.");
598            return Connection.createCanceledConnection();
599        }
600
601        // We should rely on the originalConnection to get the video state.  The request coming
602        // from Telecom does not know the video state of the unknown call.
603        int videoState = unknownConnection != null ? unknownConnection.getVideoState() :
604                VideoProfile.STATE_AUDIO_ONLY;
605
606        TelephonyConnection connection =
607                createConnectionFor(phone, unknownConnection,
608                        !unknownConnection.isIncoming() /* isOutgoing */,
609                        request.getAccountHandle(), request.getTelecomCallId(),
610                        request.getAddress(), videoState);
611
612        if (connection == null) {
613            return Connection.createCanceledConnection();
614        } else {
615            connection.updateState();
616            return connection;
617        }
618    }
619
620    /**
621     * Conferences two connections.
622     *
623     * Note: The {@link android.telecom.RemoteConnection#setConferenceableConnections(List)} API has
624     * a limitation in that it can only specify conferenceables which are instances of
625     * {@link android.telecom.RemoteConnection}.  In the case of an {@link ImsConference}, the
626     * regular {@link Connection#setConferenceables(List)} API properly handles being able to merge
627     * a {@link Conference} and a {@link Connection}.  As a result when, merging a
628     * {@link android.telecom.RemoteConnection} into a {@link android.telecom.RemoteConference}
629     * require merging a {@link ConferenceParticipantConnection} which is a child of the
630     * {@link Conference} with a {@link TelephonyConnection}.  The
631     * {@link ConferenceParticipantConnection} class does not have the capability to initiate a
632     * conference merge, so we need to call
633     * {@link TelephonyConnection#performConference(Connection)} on either {@code connection1} or
634     * {@code connection2}, one of which is an instance of {@link TelephonyConnection}.
635     *
636     * @param connection1 A connection to merge into a conference call.
637     * @param connection2 A connection to merge into a conference call.
638     */
639    @Override
640    public void onConference(Connection connection1, Connection connection2) {
641        if (connection1 instanceof TelephonyConnection) {
642            ((TelephonyConnection) connection1).performConference(connection2);
643        } else if (connection2 instanceof TelephonyConnection) {
644            ((TelephonyConnection) connection2).performConference(connection1);
645        } else {
646            Log.w(this, "onConference - cannot merge connections " +
647                    "Connection1: %s, Connection2: %2", connection1, connection2);
648        }
649    }
650
651    private boolean isRadioOn() {
652        boolean result = false;
653        for (Phone phone : PhoneFactory.getPhones()) {
654            result |= phone.isRadioOn();
655        }
656        return result;
657    }
658
659    private Pair<WeakReference<TelephonyConnection>, List<Phone>> makeCachedConnectionPhonePair(
660            TelephonyConnection c) {
661        List<Phone> phones = new ArrayList<>(Arrays.asList(PhoneFactory.getPhones()));
662        return new Pair<>(new WeakReference<>(c), phones);
663    }
664
665    // Check the mEmergencyRetryCache to see if it contains the TelephonyConnection. If it doesn't,
666    // then it is stale. Create a new one!
667    private void updateCachedConnectionPhonePair(TelephonyConnection c) {
668        if (mEmergencyRetryCache == null) {
669            Log.i(this, "updateCachedConnectionPhonePair, cache is null. Generating new cache");
670            mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
671        } else {
672            // Check to see if old cache is stale. If it is, replace it
673            WeakReference<TelephonyConnection> cachedConnection = mEmergencyRetryCache.first;
674            if (cachedConnection.get() != c) {
675                Log.i(this, "updateCachedConnectionPhonePair, cache is stale. Regenerating.");
676                mEmergencyRetryCache = makeCachedConnectionPhonePair(c);
677            }
678        }
679    }
680
681    /**
682     * Returns the first Phone that has not been used yet to place the call. Any Phones that have
683     * been used to place a call will have already been removed from mEmergencyRetryCache.second.
684     * The phone that it excluded will be removed from mEmergencyRetryCache.second in this method.
685     * @param phoneToExclude The Phone object that will be removed from our cache of available
686     * phones.
687     * @return the first Phone that is available to be used to retry the call.
688     */
689    private Phone getPhoneForRedial(Phone phoneToExclude) {
690        List<Phone> cachedPhones = mEmergencyRetryCache.second;
691        if (cachedPhones.contains(phoneToExclude)) {
692            Log.i(this, "getPhoneForRedial, removing Phone[" + phoneToExclude.getPhoneId() +
693                    "] from the available Phone cache.");
694            cachedPhones.remove(phoneToExclude);
695        }
696        return cachedPhones.isEmpty() ? null : cachedPhones.get(0);
697    }
698
699    private void retryOutgoingOriginalConnection(TelephonyConnection c) {
700        updateCachedConnectionPhonePair(c);
701        Phone newPhoneToUse = getPhoneForRedial(c.getPhone());
702        if (newPhoneToUse != null) {
703            int videoState = c.getVideoState();
704            Bundle connExtras = c.getExtras();
705            Log.i(this, "retryOutgoingOriginalConnection, redialing on Phone Id: " + newPhoneToUse);
706            c.clearOriginalConnection();
707            placeOutgoingConnection(c, newPhoneToUse, videoState, connExtras);
708        } else {
709            // We have run out of Phones to use. Disconnect the call and destroy the connection.
710            Log.i(this, "retryOutgoingOriginalConnection, no more Phones to use. Disconnecting.");
711            c.setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
712            c.clearOriginalConnection();
713            c.destroy();
714        }
715    }
716
717    private void placeOutgoingConnection(
718            TelephonyConnection connection, Phone phone, ConnectionRequest request) {
719        placeOutgoingConnection(connection, phone, request.getVideoState(), request.getExtras());
720    }
721
722    private void placeOutgoingConnection(
723            TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
724        String number = connection.getAddress().getSchemeSpecificPart();
725
726        com.android.internal.telephony.Connection originalConnection = null;
727        try {
728            if (phone != null) {
729                originalConnection = phone.dial(number, null, videoState, extras);
730
731                if (phone instanceof GsmCdmaPhone) {
732                    GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone;
733                    if (gsmCdmaPhone.isNotificationOfWfcCallRequired(number)) {
734                        // Send connection event to InCall UI to inform the user of the fact they
735                        // are potentially placing an international call on WFC.
736                        Log.i(this, "placeOutgoingConnection - sending international call on WFC " +
737                                "confirmation event");
738                        connection.sendConnectionEvent(
739                                TelephonyManager.EVENT_NOTIFY_INTERNATIONAL_CALL_ON_WFC, null);
740                    }
741                }
742            }
743        } catch (CallStateException e) {
744            Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
745            int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
746            if (e.getError() == CallStateException.ERROR_DISCONNECTED) {
747                cause = android.telephony.DisconnectCause.OUT_OF_SERVICE;
748            }
749            connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
750                    cause, e.getMessage()));
751            return;
752        }
753
754        if (originalConnection == null) {
755            int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
756            // On GSM phones, null connection means that we dialed an MMI code
757            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
758                Log.d(this, "dialed MMI code");
759                telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
760                final Intent intent = new Intent(this, MMIDialogActivity.class);
761                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
762                        Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
763                startActivity(intent);
764            }
765            Log.d(this, "placeOutgoingConnection, phone.dial returned null");
766            connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
767                    telephonyDisconnectCause, "Connection is null"));
768        } else {
769            connection.setOriginalConnection(originalConnection);
770        }
771    }
772
773    private TelephonyConnection createConnectionFor(
774            Phone phone,
775            com.android.internal.telephony.Connection originalConnection,
776            boolean isOutgoing,
777            PhoneAccountHandle phoneAccountHandle,
778            String telecomCallId,
779            Uri address,
780            int videoState) {
781        TelephonyConnection returnConnection = null;
782        int phoneType = phone.getPhoneType();
783        if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
784            returnConnection = new GsmConnection(originalConnection, telecomCallId);
785        } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
786            boolean allowsMute = allowsMute(phone);
787            returnConnection = new CdmaConnection(originalConnection, mEmergencyTonePlayer,
788                    allowsMute, isOutgoing, telecomCallId);
789        }
790        if (returnConnection != null) {
791            // Listen to Telephony specific callbacks from the connection
792            returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener);
793            returnConnection.setVideoPauseSupported(
794                    TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
795                            phoneAccountHandle));
796        }
797        return returnConnection;
798    }
799
800    private boolean isOriginalConnectionKnown(
801            com.android.internal.telephony.Connection originalConnection) {
802        for (Connection connection : getAllConnections()) {
803            if (connection instanceof TelephonyConnection) {
804                TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
805                if (telephonyConnection.getOriginalConnection() == originalConnection) {
806                    return true;
807                }
808            }
809        }
810        return false;
811    }
812
813    private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
814        Phone chosenPhone = null;
815        int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
816        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
817            int phoneId = SubscriptionManager.getPhoneId(subId);
818            chosenPhone = PhoneFactory.getPhone(phoneId);
819        }
820        // If this is an emergency call and the phone we originally planned to make this call
821        // with is not in service or was invalid, try to find one that is in service, using the
822        // default as a last chance backup.
823        if (isEmergency && (chosenPhone == null || ServiceState.STATE_IN_SERVICE != chosenPhone
824                .getServiceState().getState())) {
825            Log.d(this, "getPhoneForAccount: phone for phone acct handle %s is out of service "
826                    + "or invalid for emergency call.", accountHandle);
827            chosenPhone = getFirstPhoneForEmergencyCall();
828            Log.d(this, "getPhoneForAccount: using subId: " +
829                    (chosenPhone == null ? "null" : chosenPhone.getSubId()));
830        }
831        return chosenPhone;
832    }
833
834    /**
835     * Retrieves the most sensible Phone to use for an emergency call using the following Priority
836     *  list (for multi-SIM devices):
837     *  1) The User's SIM preference for Voice calling
838     *  2) The First Phone that is currently IN_SERVICE or is available for emergency calling
839     *  3) The Phone with more Capabilities.
840     *  4) The First Phone that has a SIM card in it (Starting from Slot 0...N)
841     *  5) The Default Phone (Currently set as Slot 0)
842     */
843    private Phone getFirstPhoneForEmergencyCall() {
844        // 1)
845        int phoneId = SubscriptionManager.getDefaultVoicePhoneId();
846        if (phoneId != SubscriptionManager.INVALID_PHONE_INDEX) {
847            Phone defaultPhone = PhoneFactory.getPhone(phoneId);
848            if (defaultPhone != null && isAvailableForEmergencyCalls(defaultPhone)) {
849                return defaultPhone;
850            }
851        }
852
853        Phone firstPhoneWithSim = null;
854        int phoneCount = TelephonyManager.getDefault().getPhoneCount();
855        List<Pair<Integer, Integer>> phoneNetworkType = new ArrayList<>(phoneCount);
856        for (int i = 0; i < phoneCount; i++) {
857            Phone phone = PhoneFactory.getPhone(i);
858            if (phone == null)
859                continue;
860            // 2)
861            if (isAvailableForEmergencyCalls(phone)) {
862                // the slot has the radio on & state is in service.
863                Log.i(this, "getFirstPhoneForEmergencyCall, radio on & in service, Phone Id:" + i);
864                return phone;
865            }
866            // 3)
867            // Store the RAF Capabilities for sorting later only if there are capabilities to sort.
868            int radioAccessFamily = phone.getRadioAccessFamily();
869            if(RadioAccessFamily.getHighestRafCapability(radioAccessFamily) != 0) {
870                phoneNetworkType.add(new Pair<>(i, radioAccessFamily));
871                Log.i(this, "getFirstPhoneForEmergencyCall, RAF:" +
872                        Integer.toHexString(radioAccessFamily) + " saved for Phone Id:" + i);
873            }
874            // 4)
875            if (firstPhoneWithSim == null && TelephonyManager.getDefault().hasIccCard(i)) {
876                // The slot has a SIM card inserted, but is not in service, so keep track of this
877                // Phone. Do not return because we want to make sure that none of the other Phones
878                // are in service (because that is always faster).
879                Log.i(this, "getFirstPhoneForEmergencyCall, SIM card inserted, Phone Id:" + i);
880                firstPhoneWithSim = phone;
881            }
882        }
883        // 5)
884        if (firstPhoneWithSim == null && phoneNetworkType.isEmpty()) {
885            // No SIMs inserted, get the default.
886            Log.i(this, "getFirstPhoneForEmergencyCall, return default phone");
887            return PhoneFactory.getDefaultPhone();
888        } else {
889            // 3)
890            final Phone firstOccupiedSlot = firstPhoneWithSim;
891            if (!phoneNetworkType.isEmpty()) {
892                // Only sort if there are enough elements to do so.
893                if(phoneNetworkType.size() > 1) {
894                    Collections.sort(phoneNetworkType, (o1, o2) -> {
895                        // First start by sorting by number of RadioAccessFamily Capabilities.
896                        int compare = Integer.bitCount(o1.second) - Integer.bitCount(o2.second);
897                        if (compare == 0) {
898                            // Sort by highest RAF Capability if the number is the same.
899                            compare = RadioAccessFamily.getHighestRafCapability(o1.second) -
900                                    RadioAccessFamily.getHighestRafCapability(o2.second);
901                            if (compare == 0 && firstOccupiedSlot != null) {
902                                // If the RAF capability is the same, choose based on whether or not
903                                // any of the slots are occupied with a SIM card (if both are,
904                                // always choose the first).
905                                if (o1.first == firstOccupiedSlot.getPhoneId()) {
906                                    return 1;
907                                } else if (o2.first == firstOccupiedSlot.getPhoneId()) {
908                                    return -1;
909                                }
910                                // Compare is still 0, return equal.
911                            }
912                        }
913                        return compare;
914                    });
915                }
916                int mostCapablePhoneId = phoneNetworkType.get(phoneNetworkType.size()-1).first;
917                Log.i(this, "getFirstPhoneForEmergencyCall, Using Phone Id: " + mostCapablePhoneId +
918                        "with highest capability");
919                return PhoneFactory.getPhone(mostCapablePhoneId);
920            } else {
921                // 4)
922                return firstPhoneWithSim;
923            }
924        }
925    }
926
927    /**
928     * Returns true if the state of the Phone is IN_SERVICE or available for emergency calling only.
929     */
930    private boolean isAvailableForEmergencyCalls(Phone phone) {
931        return ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState() ||
932                phone.getServiceState().isEmergencyOnly();
933    }
934
935    /**
936     * Determines if the connection should allow mute.
937     *
938     * @param phone The current phone.
939     * @return {@code True} if the connection should allow mute.
940     */
941    private boolean allowsMute(Phone phone) {
942        // For CDMA phones, check if we are in Emergency Callback Mode (ECM).  Mute is disallowed
943        // in ECM mode.
944        if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
945            if (phone.isInEcm()) {
946                return false;
947            }
948        }
949
950        return true;
951    }
952
953    @Override
954    public void removeConnection(Connection connection) {
955        super.removeConnection(connection);
956        if (connection instanceof TelephonyConnection) {
957            TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
958            telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
959        }
960    }
961
962    /**
963     * When a {@link TelephonyConnection} has its underlying original connection configured,
964     * we need to add it to the correct conference controller.
965     *
966     * @param connection The connection to be added to the controller
967     */
968    public void addConnectionToConferenceController(TelephonyConnection connection) {
969        // TODO: Need to revisit what happens when the original connection for the
970        // TelephonyConnection changes.  If going from CDMA --> GSM (for example), the
971        // instance of TelephonyConnection will still be a CdmaConnection, not a GsmConnection.
972        // The CDMA conference controller makes the assumption that it will only have CDMA
973        // connections in it, while the other conference controllers aren't as restrictive.  Really,
974        // when we go between CDMA and GSM we should replace the TelephonyConnection.
975        if (connection.isImsConnection()) {
976            Log.d(this, "Adding IMS connection to conference controller: " + connection);
977            mImsConferenceController.add(connection);
978            mTelephonyConferenceController.remove(connection);
979            if (connection instanceof CdmaConnection) {
980                mCdmaConferenceController.remove((CdmaConnection) connection);
981            }
982        } else {
983            int phoneType = connection.getCall().getPhone().getPhoneType();
984            if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
985                Log.d(this, "Adding GSM connection to conference controller: " + connection);
986                mTelephonyConferenceController.add(connection);
987                if (connection instanceof CdmaConnection) {
988                    mCdmaConferenceController.remove((CdmaConnection) connection);
989                }
990            } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA &&
991                    connection instanceof CdmaConnection) {
992                Log.d(this, "Adding CDMA connection to conference controller: " + connection);
993                mCdmaConferenceController.add((CdmaConnection) connection);
994                mTelephonyConferenceController.remove(connection);
995            }
996            Log.d(this, "Removing connection from IMS conference controller: " + connection);
997            mImsConferenceController.remove(connection);
998        }
999    }
1000
1001    /**
1002     * Create a new CDMA connection. CDMA connections have additional limitations when creating
1003     * additional calls which are handled in this method.  Specifically, CDMA has a "FLASH" command
1004     * that can be used for three purposes: merging a call, swapping unmerged calls, and adding
1005     * a new outgoing call. The function of the flash command depends on the context of the current
1006     * set of calls. This method will prevent an outgoing call from being made if it is not within
1007     * the right circumstances to support adding a call.
1008     */
1009    private Connection checkAdditionalOutgoingCallLimits(Phone phone) {
1010        if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
1011            // Check to see if any CDMA conference calls exist, and if they do, check them for
1012            // limitations.
1013            for (Conference conference : getAllConferences()) {
1014                if (conference instanceof CdmaConference) {
1015                    CdmaConference cdmaConf = (CdmaConference) conference;
1016
1017                    // If the CDMA conference has not been merged, add-call will not work, so fail
1018                    // this request to add a call.
1019                    if (cdmaConf.can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
1020                        return Connection.createFailedConnection(new DisconnectCause(
1021                                    DisconnectCause.RESTRICTED,
1022                                    null,
1023                                    getResources().getString(R.string.callFailed_cdma_call_limit),
1024                                    "merge-capable call exists, prevent flash command."));
1025                    }
1026                }
1027            }
1028        }
1029
1030        return null; // null means nothing went wrong, and call should continue.
1031    }
1032
1033    private boolean isTtyModeEnabled(Context context) {
1034        return (android.provider.Settings.Secure.getInt(
1035                context.getContentResolver(),
1036                android.provider.Settings.Secure.PREFERRED_TTY_MODE,
1037                TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF);
1038    }
1039}
1040