TelephonyConnectionService.java revision 213c6162947edb4ba729f1fd3d583b76b110c0ad
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.Context;
21import android.net.Uri;
22import android.os.Debug;
23import android.telephony.DisconnectCause;
24import android.telephony.ServiceState;
25import android.text.TextUtils;
26import android.telecomm.CallCapabilities;
27import android.telecomm.Connection;
28import android.telecomm.ConnectionRequest;
29import android.telecomm.ConnectionService;
30import android.telecomm.PhoneAccount;
31import android.telecomm.Response;
32import android.telephony.PhoneNumberUtils;
33import android.telephony.TelephonyManager;
34
35import com.android.internal.telephony.Call;
36import com.android.internal.telephony.CallStateException;
37import com.android.internal.telephony.Phone;
38import com.android.internal.telephony.PhoneFactory;
39
40/**
41 * Service for making GSM and CDMA connections.
42 */
43public class TelephonyConnectionService extends ConnectionService {
44    private static String SCHEME_TEL = "tel";
45
46    private EmergencyCallHelper mEmergencyCallHelper;
47
48    static PhoneAccount getPhoneAccount(Context context) {
49        return new PhoneAccount(
50                new ComponentName(context, TelephonyConnectionService.class),
51                null /* id */,
52                null,
53                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
54    }
55
56    @Override
57    protected void onCreateOutgoingConnection(
58            final ConnectionRequest request,
59            final CreateConnectionResponse<Connection> response) {
60        Log.v(this, "onCreateOutgoingConnection, request: " + request);
61
62        Uri handle = request.getHandle();
63        if (handle == null) {
64            Log.d(this, "onCreateOutgoingConnection, handle is null");
65            response.onFailure(request, DisconnectCause.NO_PHONE_NUMBER_SUPPLIED, "Handle is null");
66            return;
67        }
68
69        if (!SCHEME_TEL.equals(handle.getScheme())) {
70            Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel",
71                    handle.getScheme());
72            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
73                    "Handle scheme is not type tel");
74            return;
75        }
76
77        final String number = handle.getSchemeSpecificPart();
78        if (TextUtils.isEmpty(number)) {
79            Log.d(this, "onCreateOutgoingConnection, unable to parse number");
80            response.onFailure(request, DisconnectCause.INVALID_NUMBER, "Unable to parse number");
81            return;
82        }
83
84        final Phone phone = PhoneFactory.getDefaultPhone();
85        if (phone == null) {
86            Log.d(this, "onCreateOutgoingConnection, phone is null");
87            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, "Phone is null");
88            return;
89        }
90
91        boolean isEmergencyNumber = PhoneNumberUtils.isPotentialEmergencyNumber(number);
92        if (!isEmergencyNumber) {
93            int state = phone.getServiceState().getState();
94            switch (state) {
95                case ServiceState.STATE_IN_SERVICE:
96                    break;
97                case ServiceState.STATE_OUT_OF_SERVICE:
98                    response.onFailure(request, DisconnectCause.OUT_OF_SERVICE,
99                            "ServiceState.STATE_OUT_OF_SERVICE");
100                    return;
101                case ServiceState.STATE_EMERGENCY_ONLY:
102                    response.onFailure(request, DisconnectCause.EMERGENCY_ONLY,
103                            "ServiceState.STATE_EMERGENCY_ONLY");
104                    return;
105                case ServiceState.STATE_POWER_OFF:
106                    response.onFailure(request, DisconnectCause.POWER_OFF,
107                            "ServiceState.STATE_POWER_OFF");
108                    return;
109                default:
110                    Log.d(this, "onCreateOutgoingConnection, unkown service state: %d", state);
111                    response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED,
112                            "Unkown service state " + state);
113                    return;
114            }
115        }
116
117        if (isEmergencyNumber) {
118            Log.d(this, "onCreateOutgoingConnection, doing startTurnOnRadioSequence for " +
119                    "emergency number");
120            if (mEmergencyCallHelper == null) {
121                mEmergencyCallHelper = new EmergencyCallHelper(this);
122            }
123            mEmergencyCallHelper.startTurnOnRadioSequence(phone,
124                    new EmergencyCallHelper.Callback() {
125                        @Override
126                        public void onComplete(boolean isRadioReady) {
127                            if (isRadioReady) {
128                                startOutgoingCall(request, response, phone, number);
129                            } else {
130                                Log.d(this, "onCreateOutgoingConnection, failed to turn on radio");
131                                response.onFailure(request, DisconnectCause.POWER_OFF,
132                                        "Failed to turn on radio.");
133                            }
134                        }
135            });
136            return;
137        }
138
139        startOutgoingCall(request, response, phone, number);
140    }
141
142    @Override
143    protected void onCreateConferenceConnection(
144            String token,
145            Connection connection,
146            Response<String, Connection> response) {
147        Log.v(this, "onCreateConferenceConnection, connection: " + connection);
148        if (connection instanceof GsmConnection || connection instanceof ConferenceConnection) {
149            if ((connection.getCallCapabilities() & CallCapabilities.MERGE_CALLS) != 0) {
150                response.onResult(token,
151                        GsmConferenceController.createConferenceConnection(connection));
152            }
153        }
154    }
155
156    @Override
157    protected void onCreateIncomingConnection(
158            ConnectionRequest request,
159            CreateConnectionResponse<Connection> response) {
160        Log.v(this, "onCreateIncomingConnection, request: " + request);
161
162        Phone phone = PhoneFactory.getDefaultPhone();
163        Call call = phone.getRingingCall();
164        if (!call.getState().isRinging()) {
165            Log.v(this, "onCreateIncomingConnection, no ringing call");
166            response.onFailure(request, DisconnectCause.INCOMING_MISSED, "Found no ringing call");
167            return;
168        }
169
170        com.android.internal.telephony.Connection originalConnection = call.getEarliestConnection();
171        if (isOriginalConnectionKnown(originalConnection)) {
172            Log.v(this, "onCreateIncomingConnection, original connection already registered");
173            response.onCancel(request);
174            return;
175        }
176
177        Uri handle = getHandleFromAddress(originalConnection.getAddress());
178        ConnectionRequest telephonyRequest = new ConnectionRequest(
179                getPhoneAccount(this),
180                request.getCallId(),
181                handle,
182                originalConnection.getNumberPresentation(),
183                request.getExtras(),
184                request.getVideoState());
185
186        if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
187            response.onSuccess(telephonyRequest, new GsmConnection(originalConnection));
188        } else if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
189            response.onSuccess(telephonyRequest, new CdmaConnection(originalConnection));
190        } else {
191            response.onCancel(request);
192        }
193    }
194
195    @Override
196    protected void onConnectionAdded(Connection connection) {
197        Log.v(this, "onConnectionAdded, connection: " + connection);
198        if (connection instanceof TelephonyConnection) {
199            ((TelephonyConnection) connection).onAddedToCallService(this);
200        }
201    }
202
203    @Override
204    protected void onConnectionRemoved(Connection connection) {
205        Log.v(this, "onConnectionRemoved, connection: " + connection);
206        if (connection instanceof TelephonyConnection) {
207            ((TelephonyConnection) connection).onRemovedFromCallService();
208        }
209    }
210
211    private void startOutgoingCall(
212            ConnectionRequest request,
213            CreateConnectionResponse<Connection> response,
214            Phone phone,
215            String number) {
216        Log.v(this, "startOutgoingCall");
217
218        com.android.internal.telephony.Connection originalConnection;
219        try {
220            originalConnection = phone.dial(number, request.getVideoState());
221        } catch (CallStateException e) {
222            Log.e(this, e, "startOutgoingCall, phone.dial exception: " + e);
223            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, e.getMessage());
224            return;
225        }
226
227        if (originalConnection == null) {
228            Log.d(this, "startOutgoingCall, phone.dial returned null");
229            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, "Connection is null");
230            return;
231        }
232
233        ConnectionRequest telephonyRequest = new ConnectionRequest(
234                getPhoneAccount(this),
235                request.getCallId(),
236                request.getHandle(),
237                request.getHandlePresentation(),
238                request.getExtras(),
239                request.getVideoState());
240
241        if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM) {
242            response.onSuccess(telephonyRequest, new GsmConnection(originalConnection));
243        } else if (phone.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
244            response.onSuccess(telephonyRequest, new CdmaConnection(originalConnection));
245        } else {
246            // TODO(ihab): Tear down 'originalConnection' here, or move recognition of
247            // getPhoneType() earlier in this method before we've already asked phone to dial()
248            response.onFailure(request, DisconnectCause.ERROR_UNSPECIFIED, "Invalid phone type");
249        }
250    }
251
252    private boolean isOriginalConnectionKnown(
253            com.android.internal.telephony.Connection originalConnection) {
254        for (Connection connection : getAllConnections()) {
255            TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
256            if (connection instanceof TelephonyConnection) {
257                if (telephonyConnection.getOriginalConnection() == originalConnection) {
258                    return true;
259                }
260            }
261        }
262        return false;
263    }
264
265    static Uri getHandleFromAddress(String address) {
266        // Address can be null for blocked calls.
267        if (address == null) {
268            address = "";
269        }
270        return Uri.fromParts(SCHEME_TEL, address, null);
271    }
272}
273