TelephonyConnectionService.java revision 93a734df1d69df54ae58af0944c79b33aef7782d
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.telecomm.Connection;
23import android.telecomm.PhoneCapabilities;
24import android.telecomm.ConnectionRequest;
25import android.telecomm.ConnectionService;
26import android.telecomm.PhoneAccountHandle;
27import android.telecomm.Response;
28import android.telephony.DisconnectCause;
29import android.telephony.PhoneNumberUtils;
30import android.telephony.ServiceState;
31import android.telephony.TelephonyManager;
32import android.text.TextUtils;
33
34import com.android.internal.telephony.Call;
35import com.android.internal.telephony.CallStateException;
36import com.android.internal.telephony.Phone;
37import com.android.internal.telephony.PhoneConstants;
38import com.android.internal.telephony.PhoneFactory;
39import com.android.internal.telephony.SubscriptionController;
40import com.android.phone.MMIDialogActivity;
41
42import java.util.Objects;
43
44/**
45 * Service for making GSM and CDMA connections.
46 */
47public class TelephonyConnectionService extends ConnectionService {
48    static String SCHEME_TEL = "tel";
49
50    private final GsmConferenceController mGsmConferenceController =
51            new GsmConferenceController(this);
52    private ComponentName mExpectedComponentName = null;
53    private EmergencyCallHelper mEmergencyCallHelper;
54
55    @Override
56    public void onCreate() {
57        super.onCreate();
58        mExpectedComponentName = new ComponentName(this, this.getClass());
59    }
60
61    @Override
62    public Connection onCreateOutgoingConnection(
63            PhoneAccountHandle connectionManagerPhoneAccount,
64            final ConnectionRequest request) {
65        Log.v(this, "onCreateOutgoingConnection, request: " + request);
66
67        Uri handle = request.getHandle();
68        if (handle == null) {
69            Log.d(this, "onCreateOutgoingConnection, handle is null");
70            return Connection.createFailedConnection(DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
71                    "Handle is null");
72        }
73
74        if (!SCHEME_TEL.equals(handle.getScheme())) {
75            Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel",
76                    handle.getScheme());
77            return Connection.createFailedConnection(DisconnectCause.INVALID_NUMBER,
78                    "Handle scheme is not type tel");
79        }
80
81        final String number = handle.getSchemeSpecificPart();
82        if (TextUtils.isEmpty(number)) {
83            Log.d(this, "onCreateOutgoingConnection, unable to parse number");
84            return Connection.createFailedConnection(DisconnectCause.INVALID_NUMBER,
85                    "Unable to parse number");
86        }
87
88        boolean isEmergencyNumber = PhoneNumberUtils.isPotentialEmergencyNumber(number);
89
90        // Get the right phone object from the account data passed in.
91        final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
92        if (phone == null) {
93            Log.d(this, "onCreateOutgoingConnection, phone is null");
94            return Connection.createFailedConnection(DisconnectCause.OUTGOING_FAILURE,
95                    "Phone is null");
96        }
97
98        int state = phone.getServiceState().getState();
99        boolean useEmergencyCallHelper = false;
100
101        if (isEmergencyNumber) {
102            if (state == ServiceState.STATE_POWER_OFF) {
103                useEmergencyCallHelper = true;
104            }
105        } else {
106            switch (state) {
107                case ServiceState.STATE_IN_SERVICE:
108                case ServiceState.STATE_EMERGENCY_ONLY:
109                    break;
110                case ServiceState.STATE_OUT_OF_SERVICE:
111                    return Connection.createFailedConnection(DisconnectCause.OUT_OF_SERVICE,
112                            "ServiceState.STATE_OUT_OF_SERVICE");
113                case ServiceState.STATE_POWER_OFF:
114                    return Connection.createFailedConnection(DisconnectCause.POWER_OFF,
115                            "ServiceState.STATE_POWER_OFF");
116                default:
117                    Log.d(this, "onCreateOutgoingConnection, unkown service state: %d", state);
118                    return Connection.createFailedConnection(DisconnectCause.OUTGOING_FAILURE,
119                            "Unknown service state " + state);
120            }
121        }
122
123        final TelephonyConnection connection = createConnectionFor(phone.getPhoneType(), null);
124        if (connection == null) {
125            return Connection.createFailedConnection(
126                    DisconnectCause.OUTGOING_FAILURE, "Invalid phone type");
127        }
128        connection.setHandle(handle, PhoneConstants.PRESENTATION_ALLOWED);
129        connection.setInitializing();
130        connection.setVideoState(request.getVideoState());
131
132        if (useEmergencyCallHelper) {
133            if (mEmergencyCallHelper == null) {
134                mEmergencyCallHelper = new EmergencyCallHelper(this);
135            }
136            mEmergencyCallHelper.startTurnOnRadioSequence(phone,
137                    new EmergencyCallHelper.Callback() {
138                        @Override
139                        public void onComplete(boolean isRadioReady) {
140                            if (isRadioReady) {
141                                connection.setInitialized();
142                                placeOutgoingConnection(connection, phone, request);
143                            } else {
144                                Log.d(this, "onCreateOutgoingConnection, failed to turn on radio");
145                                connection.setDisconnected(DisconnectCause.POWER_OFF,
146                                        "Failed to turn on radio.");
147                            }
148                        }
149                    });
150
151        } else {
152            placeOutgoingConnection(connection, phone, request);
153        }
154
155        return connection;
156    }
157
158    @Override
159    public Connection onCreateIncomingConnection(
160            PhoneAccountHandle connectionManagerPhoneAccount,
161            ConnectionRequest request) {
162        Log.v(this, "onCreateIncomingConnection, request: " + request);
163
164        Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
165        if (phone == null) {
166            return Connection.createFailedConnection(DisconnectCause.ERROR_UNSPECIFIED, null);
167        }
168
169        Call call = phone.getRingingCall();
170        if (!call.getState().isRinging()) {
171            Log.v(this, "onCreateIncomingConnection, no ringing call");
172            return Connection.createFailedConnection(DisconnectCause.INCOMING_MISSED,
173                    "Found no ringing call");
174        }
175
176        com.android.internal.telephony.Connection originalConnection = call.getEarliestConnection();
177        if (isOriginalConnectionKnown(originalConnection)) {
178            Log.v(this, "onCreateIncomingConnection, original connection already registered");
179            return Connection.createCanceledConnection();
180        }
181
182        Connection connection = createConnectionFor(phone.getPhoneType(), originalConnection);
183        if (connection == null) {
184            connection = Connection.createCanceledConnection();
185            return Connection.createCanceledConnection();
186        } else {
187            return connection;
188        }
189    }
190
191    @Override
192    public void onConference(Connection connection1, Connection connection2) {
193        if (connection1 instanceof TelephonyConnection &&
194                connection2 instanceof TelephonyConnection) {
195            ((TelephonyConnection) connection1).performConference(
196                (TelephonyConnection) connection2);
197        }
198
199    }
200
201    @Override
202    public void onConnectionAdded(Connection connection) {
203        if (connection instanceof GsmConnection) {
204            mGsmConferenceController.add((GsmConnection) connection);
205        }
206    }
207
208    @Override
209    public void onConnectionRemoved(Connection connection) {
210        if (connection instanceof GsmConnection) {
211            mGsmConferenceController.remove((GsmConnection) connection);
212        }
213    }
214
215    private void placeOutgoingConnection(
216            TelephonyConnection connection, Phone phone, ConnectionRequest request) {
217        String number = connection.getHandle().getSchemeSpecificPart();
218
219        com.android.internal.telephony.Connection originalConnection;
220        try {
221            originalConnection = phone.dial(number, request.getVideoState());
222        } catch (CallStateException e) {
223            Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
224            connection.setDisconnected(DisconnectCause.OUTGOING_FAILURE, e.getMessage());
225            return;
226        }
227
228        if (originalConnection == null) {
229            int disconnectCause = DisconnectCause.OUTGOING_FAILURE;
230
231            // On GSM phones, null connection means that we dialed an MMI code
232            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
233                Log.d(this, "dialed MMI code");
234                disconnectCause = DisconnectCause.DIALED_MMI;
235                final Intent intent = new Intent(this, MMIDialogActivity.class);
236                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
237                        Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
238                startActivity(intent);
239            }
240            Log.d(this, "placeOutgoingConnection, phone.dial returned null");
241            connection.setDisconnected(disconnectCause, "Connection is null");
242        } else {
243            connection.setOriginalConnection(originalConnection);
244        }
245    }
246
247    private TelephonyConnection createConnectionFor(
248            int phoneType, com.android.internal.telephony.Connection originalConnection) {
249        if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
250            return new GsmConnection(originalConnection);
251        } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
252            return new CdmaConnection(originalConnection);
253        } else {
254            return null;
255        }
256    }
257
258    private boolean isOriginalConnectionKnown(
259            com.android.internal.telephony.Connection originalConnection) {
260        for (Connection connection : getAllConnections()) {
261            TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
262            if (connection instanceof TelephonyConnection) {
263                if (telephonyConnection.getOriginalConnection() == originalConnection) {
264                    return true;
265                }
266            }
267        }
268        return false;
269    }
270
271    private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
272        if (isEmergency) {
273            return PhoneFactory.getDefaultPhone();
274        }
275
276        if (Objects.equals(mExpectedComponentName, accountHandle.getComponentName())) {
277            if (accountHandle.getId() != null) {
278                try {
279                    int phoneId = SubscriptionController.getInstance().getPhoneId(
280                            Long.parseLong(accountHandle.getId()));
281                    return PhoneFactory.getPhone(phoneId);
282                } catch (NumberFormatException e) {
283                    Log.w(this, "Could not get subId from account: " + accountHandle.getId());
284                }
285            }
286        }
287        return null;
288    }
289}
290