TelephonyConnectionService.java revision 0918d946e9711796acd766bac053b6dedf21f079
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.ComponentName;
20import android.content.Intent;
21import android.net.Uri;
22import android.telecom.Connection;
23import android.telecom.ConnectionRequest;
24import android.telecom.ConnectionService;
25import android.telecom.PhoneAccount;
26import android.telecom.PhoneAccountHandle;
27import android.telephony.PhoneNumberUtils;
28import android.telephony.ServiceState;
29import android.telephony.TelephonyManager;
30import android.text.TextUtils;
31
32import com.android.internal.telephony.Call;
33import com.android.internal.telephony.CallStateException;
34import com.android.internal.telephony.Phone;
35import com.android.internal.telephony.PhoneConstants;
36import com.android.internal.telephony.PhoneFactory;
37import com.android.internal.telephony.PhoneProxy;
38import com.android.internal.telephony.SubscriptionController;
39import com.android.internal.telephony.cdma.CDMAPhone;
40import com.android.phone.MMIDialogActivity;
41
42import java.util.ArrayList;
43import java.util.List;
44import java.util.Objects;
45
46/**
47 * Service for making GSM and CDMA connections.
48 */
49public class TelephonyConnectionService extends ConnectionService {
50    private final TelephonyConferenceController mTelephonyConferenceController =
51            new TelephonyConferenceController(this);
52    private final CdmaConferenceController mCdmaConferenceController =
53            new CdmaConferenceController(this);
54    private final ImsConferenceController mImsConferenceController =
55            new ImsConferenceController(this);
56    private ComponentName mExpectedComponentName = null;
57    private EmergencyCallHelper mEmergencyCallHelper;
58    private EmergencyTonePlayer mEmergencyTonePlayer;
59
60    /**
61     * A listener to actionable events specific to the TelephonyConnection.
62     */
63    private final TelephonyConnection.TelephonyConnectionListener mTelephonyConnectionListener =
64            new TelephonyConnection.TelephonyConnectionListener() {
65        @Override
66        public void onOriginalConnectionConfigured(TelephonyConnection c) {
67            addConnectionToConferenceController(c);
68        }
69    };
70
71    @Override
72    public void onCreate() {
73        super.onCreate();
74        mExpectedComponentName = new ComponentName(this, this.getClass());
75        mEmergencyTonePlayer = new EmergencyTonePlayer(this);
76        TelecomAccountRegistry.getInstance(this).setTelephonyConnectionService(this);
77    }
78
79    @Override
80    public Connection onCreateOutgoingConnection(
81            PhoneAccountHandle connectionManagerPhoneAccount,
82            final ConnectionRequest request) {
83        Log.i(this, "onCreateOutgoingConnection, request: " + request);
84
85        Uri handle = request.getAddress();
86        if (handle == null) {
87            Log.d(this, "onCreateOutgoingConnection, handle is null");
88            return Connection.createFailedConnection(
89                    DisconnectCauseUtil.toTelecomDisconnectCause(
90                            android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
91                            "No phone number supplied"));
92        }
93
94        String scheme = handle.getScheme();
95        final String number;
96        if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
97            // TODO: We don't check for SecurityException here (requires
98            // CALL_PRIVILEGED permission).
99            final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
100            if (phone == null) {
101                Log.d(this, "onCreateOutgoingConnection, phone is null");
102                return Connection.createFailedConnection(
103                        DisconnectCauseUtil.toTelecomDisconnectCause(
104                                android.telephony.DisconnectCause.OUT_OF_SERVICE,
105                                "Phone is null"));
106            }
107            number = phone.getVoiceMailNumber();
108            if (TextUtils.isEmpty(number)) {
109                Log.d(this, "onCreateOutgoingConnection, no voicemail number set.");
110                return Connection.createFailedConnection(
111                        DisconnectCauseUtil.toTelecomDisconnectCause(
112                                android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING,
113                                "Voicemail scheme provided but no voicemail number set."));
114            }
115
116            // Convert voicemail: to tel:
117            handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
118        } else {
119            if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
120                Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme);
121                return Connection.createFailedConnection(
122                        DisconnectCauseUtil.toTelecomDisconnectCause(
123                                android.telephony.DisconnectCause.INVALID_NUMBER,
124                                "Handle scheme is not type tel"));
125            }
126
127            number = handle.getSchemeSpecificPart();
128            if (TextUtils.isEmpty(number)) {
129                Log.d(this, "onCreateOutgoingConnection, unable to parse number");
130                return Connection.createFailedConnection(
131                        DisconnectCauseUtil.toTelecomDisconnectCause(
132                                android.telephony.DisconnectCause.INVALID_NUMBER,
133                                "Unable to parse number"));
134            }
135        }
136
137        boolean isEmergencyNumber = PhoneNumberUtils.isPotentialEmergencyNumber(number);
138
139        // Get the right phone object from the account data passed in.
140        final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
141        if (phone == null) {
142            Log.d(this, "onCreateOutgoingConnection, phone is null");
143            return Connection.createFailedConnection(
144                    DisconnectCauseUtil.toTelecomDisconnectCause(
145                            android.telephony.DisconnectCause.OUT_OF_SERVICE, "Phone is null"));
146        }
147
148        // Check both voice & data RAT to enable normal CS call,
149        // when voice RAT is OOS but Data RAT is present.
150        int state = phone.getServiceState().getState();
151        if (state == ServiceState.STATE_OUT_OF_SERVICE) {
152            state = phone.getServiceState().getDataRegState();
153        }
154        boolean useEmergencyCallHelper = false;
155
156        if (isEmergencyNumber) {
157            if (state == ServiceState.STATE_POWER_OFF) {
158                useEmergencyCallHelper = true;
159            }
160        } else {
161            switch (state) {
162                case ServiceState.STATE_IN_SERVICE:
163                case ServiceState.STATE_EMERGENCY_ONLY:
164                    break;
165                case ServiceState.STATE_OUT_OF_SERVICE:
166                    return Connection.createFailedConnection(
167                            DisconnectCauseUtil.toTelecomDisconnectCause(
168                                    android.telephony.DisconnectCause.OUT_OF_SERVICE,
169                                    "ServiceState.STATE_OUT_OF_SERVICE"));
170                case ServiceState.STATE_POWER_OFF:
171                    return Connection.createFailedConnection(
172                            DisconnectCauseUtil.toTelecomDisconnectCause(
173                                    android.telephony.DisconnectCause.POWER_OFF,
174                                    "ServiceState.STATE_POWER_OFF"));
175                default:
176                    Log.d(this, "onCreateOutgoingConnection, unknown service state: %d", state);
177                    return Connection.createFailedConnection(
178                            DisconnectCauseUtil.toTelecomDisconnectCause(
179                                    android.telephony.DisconnectCause.OUTGOING_FAILURE,
180                                    "Unknown service state " + state));
181            }
182        }
183
184        final TelephonyConnection connection =
185                createConnectionFor(phone, null, true /* isOutgoing */);
186        if (connection == null) {
187            return Connection.createFailedConnection(
188                    DisconnectCauseUtil.toTelecomDisconnectCause(
189                            android.telephony.DisconnectCause.OUTGOING_FAILURE,
190                            "Invalid phone type"));
191        }
192        connection.setAddress(handle, PhoneConstants.PRESENTATION_ALLOWED);
193        connection.setInitializing();
194        connection.setVideoState(request.getVideoState());
195
196        if (useEmergencyCallHelper) {
197            if (mEmergencyCallHelper == null) {
198                mEmergencyCallHelper = new EmergencyCallHelper(this);
199            }
200            mEmergencyCallHelper.startTurnOnRadioSequence(phone,
201                    new EmergencyCallHelper.Callback() {
202                        @Override
203                        public void onComplete(boolean isRadioReady) {
204                            if (connection.getState() == Connection.STATE_DISCONNECTED) {
205                                // If the connection has already been disconnected, do nothing.
206                            } else if (isRadioReady) {
207                                connection.setInitialized();
208                                placeOutgoingConnection(connection, phone, request);
209                            } else {
210                                Log.d(this, "onCreateOutgoingConnection, failed to turn on radio");
211                                connection.setDisconnected(
212                                        DisconnectCauseUtil.toTelecomDisconnectCause(
213                                                android.telephony.DisconnectCause.POWER_OFF,
214                                                "Failed to turn on radio."));
215                                connection.destroy();
216                            }
217                        }
218                    });
219
220        } else {
221            placeOutgoingConnection(connection, phone, request);
222        }
223
224        return connection;
225    }
226
227    @Override
228    public Connection onCreateIncomingConnection(
229            PhoneAccountHandle connectionManagerPhoneAccount,
230            ConnectionRequest request) {
231        Log.i(this, "onCreateIncomingConnection, request: " + request);
232
233        Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
234        if (phone == null) {
235            return Connection.createFailedConnection(
236                    DisconnectCauseUtil.toTelecomDisconnectCause(
237                            android.telephony.DisconnectCause.ERROR_UNSPECIFIED));
238        }
239
240        Call call = phone.getRingingCall();
241        if (!call.getState().isRinging()) {
242            Log.i(this, "onCreateIncomingConnection, no ringing call");
243            return Connection.createFailedConnection(
244                    DisconnectCauseUtil.toTelecomDisconnectCause(
245                            android.telephony.DisconnectCause.INCOMING_MISSED,
246                            "Found no ringing call"));
247        }
248
249        com.android.internal.telephony.Connection originalConnection =
250                call.getState() == Call.State.WAITING ?
251                    call.getLatestConnection() : call.getEarliestConnection();
252        if (isOriginalConnectionKnown(originalConnection)) {
253            Log.i(this, "onCreateIncomingConnection, original connection already registered");
254            return Connection.createCanceledConnection();
255        }
256
257        Connection connection =
258                createConnectionFor(phone, originalConnection, false /* isOutgoing */);
259        if (connection == null) {
260            connection = Connection.createCanceledConnection();
261            return Connection.createCanceledConnection();
262        } else {
263            return connection;
264        }
265    }
266
267    @Override
268    public Connection onCreateUnknownConnection(PhoneAccountHandle connectionManagerPhoneAccount,
269            ConnectionRequest request) {
270        Log.i(this, "onCreateUnknownConnection, request: " + request);
271
272        Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
273        if (phone == null) {
274            return Connection.createFailedConnection(
275                    DisconnectCauseUtil.toTelecomDisconnectCause(
276                            android.telephony.DisconnectCause.ERROR_UNSPECIFIED));
277        }
278
279        final List<com.android.internal.telephony.Connection> allConnections = new ArrayList<>();
280        final Call ringingCall = phone.getRingingCall();
281        if (ringingCall.hasConnections()) {
282            allConnections.addAll(ringingCall.getConnections());
283        }
284        final Call foregroundCall = phone.getForegroundCall();
285        if (foregroundCall.hasConnections()) {
286            allConnections.addAll(foregroundCall.getConnections());
287        }
288        final Call backgroundCall = phone.getBackgroundCall();
289        if (backgroundCall.hasConnections()) {
290            allConnections.addAll(phone.getBackgroundCall().getConnections());
291        }
292
293        com.android.internal.telephony.Connection unknownConnection = null;
294        for (com.android.internal.telephony.Connection telephonyConnection : allConnections) {
295            if (!isOriginalConnectionKnown(telephonyConnection)) {
296                unknownConnection = telephonyConnection;
297                break;
298            }
299        }
300
301        if (unknownConnection == null) {
302            Log.i(this, "onCreateUnknownConnection, did not find previously unknown connection.");
303            return Connection.createCanceledConnection();
304        }
305
306        TelephonyConnection connection =
307                createConnectionFor(phone, unknownConnection,
308                        !unknownConnection.isIncoming() /* isOutgoing */);
309
310        if (connection == null) {
311            return Connection.createCanceledConnection();
312        } else {
313            connection.updateState();
314            return connection;
315        }
316    }
317
318    @Override
319    public void onConference(Connection connection1, Connection connection2) {
320        if (connection1 instanceof TelephonyConnection &&
321                connection2 instanceof TelephonyConnection) {
322            ((TelephonyConnection) connection1).performConference(
323                (TelephonyConnection) connection2);
324        }
325
326    }
327
328    private void placeOutgoingConnection(
329            TelephonyConnection connection, Phone phone, ConnectionRequest request) {
330        String number = connection.getAddress().getSchemeSpecificPart();
331
332        com.android.internal.telephony.Connection originalConnection;
333        try {
334            originalConnection = phone.dial(number, request.getVideoState());
335        } catch (CallStateException e) {
336            Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
337            connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
338                    android.telephony.DisconnectCause.OUTGOING_FAILURE,
339                    e.getMessage()));
340            return;
341        }
342
343        if (originalConnection == null) {
344            int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
345            // On GSM phones, null connection means that we dialed an MMI code
346            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
347                Log.d(this, "dialed MMI code");
348                telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
349                final Intent intent = new Intent(this, MMIDialogActivity.class);
350                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
351                        Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
352                startActivity(intent);
353            }
354            Log.d(this, "placeOutgoingConnection, phone.dial returned null");
355            connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
356                    telephonyDisconnectCause, "Connection is null"));
357        } else {
358            connection.setOriginalConnection(originalConnection);
359        }
360    }
361
362    private TelephonyConnection createConnectionFor(
363            Phone phone,
364            com.android.internal.telephony.Connection originalConnection,
365            boolean isOutgoing) {
366        TelephonyConnection returnConnection = null;
367        int phoneType = phone.getPhoneType();
368        if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
369            returnConnection = new GsmConnection(originalConnection);
370        } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
371            boolean allowMute = allowMute(phone);
372            returnConnection = new CdmaConnection(
373                    originalConnection, mEmergencyTonePlayer, allowMute, isOutgoing);
374        }
375        if (returnConnection != null) {
376            // Listen to Telephony specific callbacks from the connection
377            returnConnection.addTelephonyConnectionListener(mTelephonyConnectionListener);
378        }
379        return returnConnection;
380    }
381
382    private boolean isOriginalConnectionKnown(
383            com.android.internal.telephony.Connection originalConnection) {
384        for (Connection connection : getAllConnections()) {
385            if (connection instanceof TelephonyConnection) {
386                TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
387                if (telephonyConnection.getOriginalConnection() == originalConnection) {
388                    return true;
389                }
390            }
391        }
392        return false;
393    }
394
395    private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
396        if (Objects.equals(mExpectedComponentName, accountHandle.getComponentName())) {
397            if (accountHandle.getId() != null) {
398                try {
399                    int phoneId = SubscriptionController.getInstance().getPhoneId(
400                            Integer.parseInt(accountHandle.getId()));
401                    return PhoneFactory.getPhone(phoneId);
402                } catch (NumberFormatException e) {
403                    Log.w(this, "Could not get subId from account: " + accountHandle.getId());
404                }
405            }
406        }
407
408        if (isEmergency) {
409            // If this is an emergency number and we've been asked to dial it using a PhoneAccount
410            // which does not exist, then default to whatever subscription is available currently.
411            return getFirstPhoneForEmergencyCall();
412        }
413
414        return null;
415    }
416
417    private Phone getFirstPhoneForEmergencyCall() {
418        Phone selectPhone = null;
419        for (int i = 0; i < TelephonyManager.getDefault().getSimCount(); i++) {
420            int[] subIds = SubscriptionController.getInstance().getSubIdUsingSlotId(i);
421            if (subIds.length == 0)
422                continue;
423
424            int phoneId = SubscriptionController.getInstance().getPhoneId(subIds[0]);
425            Phone phone = PhoneFactory.getPhone(phoneId);
426            if (phone == null)
427                continue;
428
429            if (ServiceState.STATE_IN_SERVICE == phone.getServiceState().getState()) {
430                // the slot is radio on & state is in service
431                Log.d(this, "pickBestPhoneForEmergencyCall, radio on & in service, slotId:" + i);
432                return phone;
433            } else if (ServiceState.STATE_POWER_OFF != phone.getServiceState().getState()) {
434                // the slot is radio on & with SIM card inserted.
435                if (TelephonyManager.getDefault().hasIccCard(i)) {
436                    Log.d(this, "pickBestPhoneForEmergencyCall," +
437                            "radio on and SIM card inserted, slotId:" + i);
438                    selectPhone = phone;
439                } else if (selectPhone == null) {
440                    Log.d(this, "pickBestPhoneForEmergencyCall, radio on, slotId:" + i);
441                    selectPhone = phone;
442                }
443            }
444        }
445
446        if (selectPhone == null) {
447            Log.d(this, "pickBestPhoneForEmergencyCall, return default phone");
448            selectPhone = PhoneFactory.getDefaultPhone();
449        }
450
451        return selectPhone;
452    }
453
454    /**
455     * Determines if the connection should allow mute.
456     *
457     * @param phone The current phone.
458     * @return {@code True} if the connection should allow mute.
459     */
460    private boolean allowMute(Phone phone) {
461        // For CDMA phones, check if we are in Emergency Callback Mode (ECM).  Mute is disallowed
462        // in ECM mode.
463        if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
464            PhoneProxy phoneProxy = (PhoneProxy)phone;
465            CDMAPhone cdmaPhone = (CDMAPhone)phoneProxy.getActivePhone();
466            if (cdmaPhone != null) {
467                if (cdmaPhone.isInEcm()) {
468                    return false;
469                }
470            }
471        }
472
473        return true;
474    }
475
476    @Override
477    public void removeConnection(Connection connection) {
478        super.removeConnection(connection);
479        if (connection instanceof TelephonyConnection) {
480            TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
481            telephonyConnection.removeTelephonyConnectionListener(mTelephonyConnectionListener);
482        }
483    }
484
485    /**
486     * When a {@link TelephonyConnection} has its underlying original connection configured,
487     * we need to add it to the correct conference controller.
488     *
489     * @param connection The connection to be added to the controller
490     */
491    public void addConnectionToConferenceController(TelephonyConnection connection) {
492        // TODO: Do we need to handle the case of the original connection changing
493        // and triggering this callback multiple times for the same connection?
494        // If that is the case, we might want to remove this connection from all
495        // conference controllers first before re-adding it.
496        if (connection.isImsConnection()) {
497            Log.d(this, "Adding IMS connection to conference controller: " + connection);
498            mImsConferenceController.add(connection);
499        } else {
500            int phoneType = connection.getCall().getPhone().getPhoneType();
501            if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
502                Log.d(this, "Adding GSM connection to conference controller: " + connection);
503                mTelephonyConferenceController.add(connection);
504            } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA &&
505                    connection instanceof CdmaConnection) {
506                Log.d(this, "Adding CDMA connection to conference controller: " + connection);
507                mCdmaConferenceController.add((CdmaConnection)connection);
508            }
509        }
510    }
511}
512