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