TelephonyConnectionService.java revision b0baef15d2559af2506ba76c79b072f4d683c81c
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.net.Uri;
20import android.telephony.DisconnectCause;
21import android.telephony.ServiceState;
22import android.telecomm.Subscription;
23import android.text.TextUtils;
24
25import com.android.internal.telephony.CallStateException;
26import com.android.internal.telephony.Connection.PostDialListener;
27import com.android.internal.telephony.Phone;
28
29import android.telecomm.Connection;
30import android.telecomm.ConnectionRequest;
31import android.telecomm.ConnectionService;
32import android.telecomm.Response;
33
34import java.util.HashSet;
35import java.util.Set;
36
37/**
38 * The parent class for Android's built-in connection services.
39 */
40public abstract class TelephonyConnectionService extends ConnectionService {
41    private static final Set<com.android.internal.telephony.Connection> sKnownConnections
42            = new HashSet<>();
43
44    /**
45     * Initiates the underlying Telephony call, then creates a {@link TelephonyConnection}
46     * by calling
47     * {@link #createTelephonyConnection(
48     *         ConnectionRequest,Phone,com.android.internal.telephony.Connection)}
49     * at the appropriate time. Should be called by the subclass.
50     */
51    protected void startCallWithPhone(
52            Phone phone,
53            ConnectionRequest request,
54            OutgoingCallResponse<Connection> response) {
55        Log.d(this, "startCallWithPhone: %s.", request);
56
57        if (phone == null) {
58            respondWithError(
59                    request,
60                    response,
61                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
62                    "Phone is null");
63            return;
64        }
65
66        if (request.getHandle() == null) {
67            respondWithError(
68                    request,
69                    response,
70                    DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
71                    "Handle is null");
72            return;
73        }
74
75        String number = request.getHandle().getSchemeSpecificPart();
76        if (TextUtils.isEmpty(number)) {
77            respondWithError(
78                    request,
79                    response,
80                    DisconnectCause.INVALID_NUMBER,
81                    "Unable to parse number");
82            return;
83        }
84
85        if (!checkServiceStateForOutgoingCall(phone, request, response)) {
86            return;
87        }
88
89        com.android.internal.telephony.Connection connection;
90        try {
91            connection = phone.dial(number);
92        } catch (CallStateException e) {
93            Log.e(this, e, "Call to Phone.dial failed with exception");
94            respondWithError(
95                    request,
96                    response,
97                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
98                    e.getMessage());
99            return;
100        }
101
102        if (connection == null) {
103            respondWithError(
104                    request,
105                    response,
106                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
107                    "Call to phone.dial failed");
108            return;
109        }
110
111        try {
112            final TelephonyConnection telephonyConnection =
113                    createTelephonyConnection(request, phone, connection);
114            respondWithResult(request, response, telephonyConnection);
115
116            final com.android.internal.telephony.Connection connectionFinal = connection;
117            PostDialListener postDialListener = new PostDialListener() {
118                @Override
119                public void onPostDialWait() {
120                    TelephonyConnectionService.this.onPostDialWait(telephonyConnection,
121                            connectionFinal.getRemainingPostDialString());
122                }
123            };
124            connection.addPostDialListener(postDialListener);
125        } catch (Exception e) {
126            Log.e(this, e, "Call to createConnection failed with exception");
127            respondWithError(
128                    request,
129                    response,
130                    DisconnectCause.ERROR_UNSPECIFIED,  // Generic internal error
131                    e.getMessage());
132        }
133    }
134
135    private boolean checkServiceStateForOutgoingCall(
136            Phone phone,
137            ConnectionRequest request,
138            OutgoingCallResponse<Connection> response) {
139        int state = phone.getServiceState().getState();
140        switch (state) {
141            case ServiceState.STATE_IN_SERVICE:
142                return true;
143            case ServiceState.STATE_OUT_OF_SERVICE:
144                respondWithError(
145                        request,
146                        response,
147                        DisconnectCause.OUT_OF_SERVICE,
148                        null);
149                break;
150            case ServiceState.STATE_EMERGENCY_ONLY:
151                respondWithError(
152                        request,
153                        response,
154                        DisconnectCause.EMERGENCY_ONLY,
155                        null);
156                break;
157            case ServiceState.STATE_POWER_OFF:
158                respondWithError(
159                        request,
160                        response,
161                        DisconnectCause.POWER_OFF,
162                        null);
163                break;
164            default:
165                // Internal error, but we pass it upwards and do not crash.
166                Log.d(this, "Unrecognized service state %d", state);
167                respondWithError(
168                        request,
169                        response,
170                        DisconnectCause.ERROR_UNSPECIFIED,
171                        "Unrecognized service state " + state);
172                break;
173        }
174        return false;
175    }
176
177    protected <REQUEST, RESULT> void respondWithError(
178            REQUEST request,
179            Response<REQUEST, RESULT> response,
180            int errorCode,
181            String errorMsg) {
182        Log.d(this, "respondWithError %s: %d %s", request, errorCode, errorMsg);
183        response.onError(request, errorCode, errorMsg);
184    }
185
186    protected void respondWithResult(
187            ConnectionRequest request,
188            Response<ConnectionRequest, Connection> response,
189            Connection result) {
190        Log.d(this, "respondWithResult %s -> %s", request, result);
191        response.onResult(request, result);
192    }
193
194    protected void respondWithResult(
195            ConnectionRequest request,
196            OutgoingCallResponse<Connection> response,
197            Connection result) {
198        Log.d(this, "respondWithResult %s -> %s", request, result);
199        response.onSuccess(request, result);
200    }
201
202    protected void respondWithError(
203            ConnectionRequest request,
204            OutgoingCallResponse<Connection> response,
205            int errorCode,
206            String errorMsg) {
207        Log.d(this, "respondWithError %s: %d %s", request, errorCode, errorMsg);
208        response.onFailure(request, errorCode, errorMsg);
209    }
210
211    protected final TelephonyConnection createTelephonyConnection(
212            ConnectionRequest request,
213            Phone phone,
214            final com.android.internal.telephony.Connection connection) {
215        final TelephonyConnection telephonyConnection =
216                onCreateTelephonyConnection(request, phone, connection);
217        sKnownConnections.add(connection);
218        telephonyConnection.addConnectionListener(new Connection.ListenerBase() {
219            @Override
220            public void onDestroyed(Connection c) {
221                telephonyConnection.removeConnectionListener(this);
222                sKnownConnections.remove(connection);
223            }
224        });
225
226        return telephonyConnection;
227    }
228
229    protected static boolean isConnectionKnown(
230            com.android.internal.telephony.Connection connection) {
231        return sKnownConnections.contains(connection);
232    }
233
234    /**
235     * Determine whether this {@link TelephonyConnectionService} can place a call
236     * to the supplied handle (phone number).
237     *
238     * @param handle The proposed handle.
239     * @return {@code true} if the handle can be called.
240     */
241    protected abstract boolean canCall(Uri handle);
242
243    /**
244     * Create a Telephony-specific {@link Connection} object.
245     *
246     * @param request A request for creating a {@link Connection}.
247     * @param phone A {@code Phone} object to use.
248     * @param connection An underlying Telephony {@link com.android.internal.telephony.Connection}
249     *         to use.
250     * @return A new {@link TelephonyConnection}.
251     */
252    protected abstract TelephonyConnection onCreateTelephonyConnection(
253            ConnectionRequest request,
254            Phone phone,
255            com.android.internal.telephony.Connection connection);
256}
257