TelephonyConnectionService.java revision b7795cf75d54752c7d0d1fd59870d5c3d5fe662e
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 (connection.getState() == Connection.STATE_DISCONNECTED) {
141                                // If the connection has already been disconnected, do nothing.
142                            } else if (isRadioReady) {
143                                connection.setInitialized();
144                                placeOutgoingConnection(connection, phone, request);
145                            } else {
146                                Log.d(this, "onCreateOutgoingConnection, failed to turn on radio");
147                                connection.setDisconnected(DisconnectCause.POWER_OFF,
148                                        "Failed to turn on radio.");
149                                connection.destroy();
150                            }
151                        }
152                    });
153
154        } else {
155            placeOutgoingConnection(connection, phone, request);
156        }
157
158        return connection;
159    }
160
161    @Override
162    public Connection onCreateIncomingConnection(
163            PhoneAccountHandle connectionManagerPhoneAccount,
164            ConnectionRequest request) {
165        Log.v(this, "onCreateIncomingConnection, request: " + request);
166
167        Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
168        if (phone == null) {
169            return Connection.createFailedConnection(DisconnectCause.ERROR_UNSPECIFIED, null);
170        }
171
172        Call call = phone.getRingingCall();
173        if (!call.getState().isRinging()) {
174            Log.v(this, "onCreateIncomingConnection, no ringing call");
175            return Connection.createFailedConnection(DisconnectCause.INCOMING_MISSED,
176                    "Found no ringing call");
177        }
178
179        com.android.internal.telephony.Connection originalConnection = call.getEarliestConnection();
180        if (isOriginalConnectionKnown(originalConnection)) {
181            Log.v(this, "onCreateIncomingConnection, original connection already registered");
182            return Connection.createCanceledConnection();
183        }
184
185        Connection connection = createConnectionFor(phone.getPhoneType(), originalConnection);
186        if (connection == null) {
187            connection = Connection.createCanceledConnection();
188            return Connection.createCanceledConnection();
189        } else {
190            return connection;
191        }
192    }
193
194    @Override
195    public void onConference(Connection connection1, Connection connection2) {
196        if (connection1 instanceof TelephonyConnection &&
197                connection2 instanceof TelephonyConnection) {
198            ((TelephonyConnection) connection1).performConference(
199                (TelephonyConnection) connection2);
200        }
201
202    }
203
204    @Override
205    public void onConnectionAdded(Connection connection) {
206        if (connection instanceof GsmConnection) {
207            mGsmConferenceController.add((GsmConnection) connection);
208        }
209    }
210
211    @Override
212    public void onConnectionRemoved(Connection connection) {
213        if (connection instanceof GsmConnection) {
214            mGsmConferenceController.remove((GsmConnection) connection);
215        }
216    }
217
218    private void placeOutgoingConnection(
219            TelephonyConnection connection, Phone phone, ConnectionRequest request) {
220        String number = connection.getHandle().getSchemeSpecificPart();
221
222        com.android.internal.telephony.Connection originalConnection;
223        try {
224            originalConnection = phone.dial(number, request.getVideoState());
225        } catch (CallStateException e) {
226            Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
227            connection.setDisconnected(DisconnectCause.OUTGOING_FAILURE, e.getMessage());
228            return;
229        }
230
231        if (originalConnection == null) {
232            int disconnectCause = DisconnectCause.OUTGOING_FAILURE;
233
234            // On GSM phones, null connection means that we dialed an MMI code
235            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
236                Log.d(this, "dialed MMI code");
237                disconnectCause = DisconnectCause.DIALED_MMI;
238                final Intent intent = new Intent(this, MMIDialogActivity.class);
239                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
240                        Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
241                startActivity(intent);
242            }
243            Log.d(this, "placeOutgoingConnection, phone.dial returned null");
244            connection.setDisconnected(disconnectCause, "Connection is null");
245        } else {
246            connection.setOriginalConnection(originalConnection);
247        }
248    }
249
250    private TelephonyConnection createConnectionFor(
251            int phoneType, com.android.internal.telephony.Connection originalConnection) {
252        if (phoneType == TelephonyManager.PHONE_TYPE_GSM) {
253            return new GsmConnection(originalConnection);
254        } else if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
255            return new CdmaConnection(originalConnection);
256        } else {
257            return null;
258        }
259    }
260
261    private boolean isOriginalConnectionKnown(
262            com.android.internal.telephony.Connection originalConnection) {
263        for (Connection connection : getAllConnections()) {
264            TelephonyConnection telephonyConnection = (TelephonyConnection) connection;
265            if (connection instanceof TelephonyConnection) {
266                if (telephonyConnection.getOriginalConnection() == originalConnection) {
267                    return true;
268                }
269            }
270        }
271        return false;
272    }
273
274    private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
275        if (isEmergency) {
276            return PhoneFactory.getDefaultPhone();
277        }
278
279        if (Objects.equals(mExpectedComponentName, accountHandle.getComponentName())) {
280            if (accountHandle.getId() != null) {
281                try {
282                    int phoneId = SubscriptionController.getInstance().getPhoneId(
283                            Long.parseLong(accountHandle.getId()));
284                    return PhoneFactory.getPhone(phoneId);
285                } catch (NumberFormatException e) {
286                    Log.w(this, "Could not get subId from account: " + accountHandle.getId());
287                }
288            }
289        }
290        return null;
291    }
292}
293