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