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