blob: c33b17e474c1edfdb25a8f7ec4bca1572d3e0765 [file] [log] [blame]
Ben Gilad0bf5b912014-01-28 17:55:57 -08001/*
2 * Copyright 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.telecomm;
18
Ihab Awad55a34282014-06-18 10:31:09 -070019import android.net.Uri;
Santos Cordon682fe6b2014-05-20 08:56:39 -070020import android.os.Handler;
21import android.os.Message;
Ben Giladc5b22692014-02-18 20:03:22 -080022import android.telecomm.CallServiceDescriptor;
Ihab Awad0fea3f22014-06-03 18:45:05 -070023import android.telephony.DisconnectCause;
Ihab Awad55a34282014-06-18 10:31:09 -070024import android.telephony.PhoneNumberUtils;
Sailesh Nepalb6141ae2014-02-18 08:45:26 -080025
Santos Cordon682fe6b2014-05-20 08:56:39 -070026import com.android.telecomm.BaseRepository.LookupCallback;
Ben Gilad61925612014-03-11 19:06:36 -070027import com.google.android.collect.Sets;
Santos Cordon766d04f2014-05-06 10:28:25 -070028import com.google.common.collect.Maps;
Ben Gilad0bf5b912014-01-28 17:55:57 -080029
Santos Cordon682fe6b2014-05-20 08:56:39 -070030import java.util.ArrayList;
Sailesh Nepal18386a82014-03-19 10:22:40 -070031import java.util.Collection;
Ben Gilad0bf5b912014-01-28 17:55:57 -080032import java.util.Iterator;
33import java.util.List;
34import java.util.Map;
35import java.util.Set;
36
37/**
Sailesh Nepalc92c4362014-07-04 18:33:21 -070038 * Utility class to place a call using the specified set of call-services. Each of the connection
39 * services is then attempted until either the outgoing call is placed, the attempted call is
40 * aborted, or the list is exhausted -- whichever occurs first.
Ben Gilad0bf5b912014-01-28 17:55:57 -080041 *
Santos Cordon74d420b2014-05-07 14:38:47 -070042 * Except for the abort case, all other scenarios should terminate with the call notified
Santos Cordon681663d2014-01-30 04:32:15 -080043 * of the result.
Ben Gilad0bf5b912014-01-28 17:55:57 -080044 */
45final class OutgoingCallProcessor {
46
47 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -080048 * The outgoing call this processor is tasked with placing.
49 */
50 private final Call mCall;
51
52 /**
Sailesh Nepalb6141ae2014-02-18 08:45:26 -080053 * The map of currently-available call-service implementations keyed by call-service ID.
Ben Gilad0bf5b912014-01-28 17:55:57 -080054 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -070055 private final Map<String, ConnectionServiceWrapper> mConnectionServicesById = Maps.newHashMap();
Ben Gilad0bf5b912014-01-28 17:55:57 -080056
57 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -070058 * The set of attempted connection services, used to ensure services are attempted at most once
59 * per outgoing-call attempt.
Ben Gilad61925612014-03-11 19:06:36 -070060 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -070061 private final Set<ConnectionServiceWrapper> mAttemptedConnectionServices = Sets.newHashSet();
Ben Gilad61925612014-03-11 19:06:36 -070062
Santos Cordon682fe6b2014-05-20 08:56:39 -070063 private final CallServiceRepository mCallServiceRepository;
Ben Gilad61925612014-03-11 19:06:36 -070064
Santos Cordon682fe6b2014-05-20 08:56:39 -070065 /**
66 * The duplicate-free list of currently-available call-service descriptors.
67 */
68 private List<CallServiceDescriptor> mCallServiceDescriptors;
Sailesh Nepal18386a82014-03-19 10:22:40 -070069
Ben Gilad0bf5b912014-01-28 17:55:57 -080070 /**
Ben Giladc5b22692014-02-18 20:03:22 -080071 * The iterator over the currently-selected ordered list of call-service descriptors.
Ben Gilad0bf5b912014-01-28 17:55:57 -080072 */
Ben Giladc5b22692014-02-18 20:03:22 -080073 private Iterator<CallServiceDescriptor> mCallServiceDescriptorIterator;
Ben Gilad0bf5b912014-01-28 17:55:57 -080074
Sailesh Nepal5a73b032014-06-25 15:53:21 -070075 private OutgoingCallResponse mResultCallback;
Santos Cordon682fe6b2014-05-20 08:56:39 -070076
Ben Gilad0bf5b912014-01-28 17:55:57 -080077 private boolean mIsAborted = false;
78
Ihab Awad0fea3f22014-06-03 18:45:05 -070079 private int mLastErrorCode = 0;
80
81 private String mLastErrorMsg = null;
82
Ben Gilad0bf5b912014-01-28 17:55:57 -080083 /**
Ihab Awad55a34282014-06-18 10:31:09 -070084 * Persists the specified parameters and iterates through the prioritized list of call
Sailesh Nepalc92c4362014-07-04 18:33:21 -070085 * services. Stops once a matching connection service is found. Calls with no matching
86 * connection service will eventually be killed by the cleanup/monitor switchboard handler.
Ben Gilad0bf5b912014-01-28 17:55:57 -080087 *
88 * @param call The call to place.
Santos Cordon682fe6b2014-05-20 08:56:39 -070089 * @param callServiceRepository
Santos Cordon682fe6b2014-05-20 08:56:39 -070090 * @param resultCallback The callback on which to return the result.
Ben Gilad0bf5b912014-01-28 17:55:57 -080091 */
92 OutgoingCallProcessor(
93 Call call,
Santos Cordon682fe6b2014-05-20 08:56:39 -070094 CallServiceRepository callServiceRepository,
Sailesh Nepal5a73b032014-06-25 15:53:21 -070095 OutgoingCallResponse resultCallback) {
Ben Gilad0bf5b912014-01-28 17:55:57 -080096
Ben Giladd88323b2014-01-29 19:03:19 -080097 ThreadUtil.checkOnMainThread();
98
Ben Gilad0bf5b912014-01-28 17:55:57 -080099 mCall = call;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700100 mResultCallback = resultCallback;
101 mCallServiceRepository = callServiceRepository;
Ben Gilad0bf5b912014-01-28 17:55:57 -0800102 }
103
104 /**
105 * Initiates the attempt to place the call. No-op beyond the first invocation.
106 */
107 void process() {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700108 Log.v(this, "process, mIsAborted: %b", mIsAborted);
Ben Gilad28e8ad62014-03-06 17:01:54 -0800109 if (!mIsAborted) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700110 // Lookup connection services
111 mCallServiceRepository.lookupServices(new LookupCallback<ConnectionServiceWrapper>() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700112 @Override
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700113 public void onComplete(Collection<ConnectionServiceWrapper> services) {
114 setConnectionServices(services);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700115 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700116 });
Sailesh Nepal810735e2014-03-18 18:15:46 -0700117 }
Ben Gilad61925612014-03-11 19:06:36 -0700118 }
119
120 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -0800121 * Aborts the attempt to place the relevant call. Intended to be invoked by
Santos Cordon681663d2014-01-30 04:32:15 -0800122 * switchboard through the outgoing-calls manager.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800123 */
124 void abort() {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700125 Log.v(this, "abort");
Ben Gilad9c234112014-03-04 16:07:33 -0800126 ThreadUtil.checkOnMainThread();
Santos Cordon682fe6b2014-05-20 08:56:39 -0700127 if (!mIsAborted && mResultCallback != null) {
Ben Gilad9c234112014-03-04 16:07:33 -0800128 mIsAborted = true;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700129
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700130 // On an abort, we need to check if we already told the connection service to place the
Santos Cordon682fe6b2014-05-20 08:56:39 -0700131 // call. If so, we need to tell it to abort.
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700132 // TODO(santoscordon): The conneciton service is saved with the call and so we have to
133 // query the call to get it, which is a bit backwards. Ideally, the connection service
134 // would be saved inside this class until the whole thing is complete and then set on
135 // the call.
136 ConnectionServiceWrapper service = mCall.getConnectionService();
137 if (service != null) {
138 service.abort(mCall);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700139 }
140
Ihab Awad0fea3f22014-06-03 18:45:05 -0700141 // We consider a deliberate abort to be a "normal" disconnect, not
142 // requiring special reporting.
143 sendResult(false, DisconnectCause.LOCAL, null);
Ben Gilad9c234112014-03-04 16:07:33 -0800144 }
Ben Gilad0bf5b912014-01-28 17:55:57 -0800145 }
146
Santos Cordon682fe6b2014-05-20 08:56:39 -0700147 boolean isAborted() {
148 return mIsAborted;
149 }
150
Ben Gilad0bf5b912014-01-28 17:55:57 -0800151 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700152 * Completes the outgoing call sequence by setting the connection service on the call object.
153 * This is invoked when the connection service adapter receives positive confirmation that the
154 * connection service placed the call.
Santos Cordon681663d2014-01-30 04:32:15 -0800155 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700156 void handleSuccessfulCallAttempt(ConnectionServiceWrapper service) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700157 Log.v(this, "handleSuccessfulCallAttempt");
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800158 ThreadUtil.checkOnMainThread();
159
160 if (mIsAborted) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700161 service.abort(mCall);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800162 return;
163 }
164
Ihab Awad0fea3f22014-06-03 18:45:05 -0700165 sendResult(true, DisconnectCause.NOT_DISCONNECTED, null);
Santos Cordon681663d2014-01-30 04:32:15 -0800166 }
167
168 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700169 * Attempts the next connection service if the specified connection service is the one currently
170 * being attempted.
Santos Cordon681663d2014-01-30 04:32:15 -0800171 *
Ihab Awad0fea3f22014-06-03 18:45:05 -0700172 * @param errorCode The reason for the failure, one of {@link DisconnectCause}.
173 * @param errorMsg Optional text reason for the failure.
Santos Cordon681663d2014-01-30 04:32:15 -0800174 */
Ihab Awad0fea3f22014-06-03 18:45:05 -0700175 void handleFailedCallAttempt(int errorCode, String errorMsg) {
176 Log.v(this, "handleFailedCallAttempt %s %s", DisconnectCause.toString(errorCode), errorMsg);
177 // Store latest error code and message. If this is our last available attempt at placing
178 // a call, these error details will be considered "the" cause of the failure.
179 mLastErrorCode = errorCode;
180 mLastErrorMsg = errorMsg;
Ben Gilad28e8ad62014-03-06 17:01:54 -0800181 if (!mIsAborted) {
182 ThreadUtil.checkOnMainThread();
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700183 attemptNextConnectionService();
Ben Gilad28e8ad62014-03-06 17:01:54 -0800184 }
Santos Cordon681663d2014-01-30 04:32:15 -0800185 }
186
187 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700188 * Sets the connection services to attempt for this outgoing call.
Santos Cordon682fe6b2014-05-20 08:56:39 -0700189 *
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700190 * @param services The connection services.
Santos Cordon682fe6b2014-05-20 08:56:39 -0700191 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700192 private void setConnectionServices(Collection<ConnectionServiceWrapper> services) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700193 mCallServiceDescriptors = new ArrayList<>();
194
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700195 // Populate the list and map of call-service descriptors.
196 for (ConnectionServiceWrapper service : services) {
197 CallServiceDescriptor descriptor = service.getDescriptor();
Sailesh Nepala1b3bca2014-06-24 15:17:44 -0700198 // TODO(sail): Remove once there's a way to pick the service.
199 if (descriptor.getServiceComponent().getPackageName().equals(
200 "com.google.android.talk")) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700201 Log.i(this, "Moving connection service %s to top of list", descriptor);
Sailesh Nepala1b3bca2014-06-24 15:17:44 -0700202 mCallServiceDescriptors.add(0, descriptor);
203 } else {
204 mCallServiceDescriptors.add(descriptor);
205 }
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700206 mConnectionServicesById.put(descriptor.getConnectionServiceId(), service);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700207 }
208
Ihab Awad55a34282014-06-18 10:31:09 -0700209 adjustCallServiceDescriptorsForEmergency();
Santos Cordon682fe6b2014-05-20 08:56:39 -0700210
Ihab Awad55a34282014-06-18 10:31:09 -0700211 mCallServiceDescriptorIterator = mCallServiceDescriptors.iterator();
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700212 attemptNextConnectionService();
Ben Gilad0bf5b912014-01-28 17:55:57 -0800213 }
214
215 /**
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700216 * Attempts to place the call using the connection service specified by the next call-service
Ihab Awad55a34282014-06-18 10:31:09 -0700217 * descriptor of mCallServiceDescriptorIterator.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800218 */
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700219 private void attemptNextConnectionService() {
220 Log.v(this, "attemptNextConnectionService, mIsAborted: %b", mIsAborted);
Ben Gilad0bf5b912014-01-28 17:55:57 -0800221 if (mIsAborted) {
222 return;
223 }
224
Evan Charltonf02e9882014-03-06 12:54:52 -0800225 if (mCallServiceDescriptorIterator != null && mCallServiceDescriptorIterator.hasNext()) {
Ben Giladc5b22692014-02-18 20:03:22 -0800226 CallServiceDescriptor descriptor = mCallServiceDescriptorIterator.next();
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700227 final ConnectionServiceWrapper service =
228 mConnectionServicesById.get(descriptor.getConnectionServiceId());
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800229
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700230 if (service == null || mAttemptedConnectionServices.contains(service)) {
231 // The next connection service is either null or has already been attempted, fast
232 // forward to the next.
233 attemptNextConnectionService();
Santos Cordon681663d2014-01-30 04:32:15 -0800234 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700235 mAttemptedConnectionServices.add(service);
236 mCall.setConnectionService(service);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700237
238 // Increment the associated call count until we get a result. This prevents the call
239 // service from unbinding while we are using it.
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700240 service.incrementAssociatedCallCount();
Santos Cordon682fe6b2014-05-20 08:56:39 -0700241
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700242 Log.i(this, "Attempting to call from %s", service.getDescriptor());
243 service.call(mCall, new OutgoingCallResponse() {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700244 @Override
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700245 public void onOutgoingCallSuccess() {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700246 handleSuccessfulCallAttempt(service);
247 service.decrementAssociatedCallCount();
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700248 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700249
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700250 @Override
251 public void onOutgoingCallFailure(int code, String msg) {
252 handleFailedCallAttempt(code, msg);
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700253 service.decrementAssociatedCallCount();
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700254 }
255
256 @Override
257 public void onOutgoingCallCancel() {
258 abort();
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700259 service.decrementAssociatedCallCount();
Santos Cordon682fe6b2014-05-20 08:56:39 -0700260 }
261 });
Ben Gilad0bf5b912014-01-28 17:55:57 -0800262 }
263 } else {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700264 Log.v(this, "attemptNextConnectionService, no more service descriptors, failing");
Ben Giladc5b22692014-02-18 20:03:22 -0800265 mCallServiceDescriptorIterator = null;
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700266 mCall.clearConnectionService();
Ihab Awad55a34282014-06-18 10:31:09 -0700267 sendResult(false, mLastErrorCode, mLastErrorMsg);
Ben Gilad0bf5b912014-01-28 17:55:57 -0800268 }
269 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700270
Ihab Awad0fea3f22014-06-03 18:45:05 -0700271 private void sendResult(boolean wasCallPlaced, int errorCode, String errorMsg) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700272 if (mResultCallback != null) {
Sailesh Nepal5a73b032014-06-25 15:53:21 -0700273 if (mIsAborted) {
274 mResultCallback.onOutgoingCallCancel();
275 } else if (wasCallPlaced) {
276 mResultCallback.onOutgoingCallSuccess();
277 } else {
278 mResultCallback.onOutgoingCallFailure(errorCode, errorMsg);
279 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700280 mResultCallback = null;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700281 } else {
282 Log.wtf(this, "Attempting to return outgoing result twice for call %s", mCall);
283 }
284 }
Ihab Awad55a34282014-06-18 10:31:09 -0700285
286 // If we are possibly attempting to call a local emergency number, ensure that the
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700287 // plain PSTN connection service, if it exists, is attempted first.
Ihab Awad55a34282014-06-18 10:31:09 -0700288 private void adjustCallServiceDescriptorsForEmergency() {
Santos Cordon5924bea2014-06-18 06:39:51 -0700289 for (int i = 0; i < mCallServiceDescriptors.size(); i++) {
290 if (shouldProcessAsEmergency(mCall.getHandle())) {
Sailesh Nepalc92c4362014-07-04 18:33:21 -0700291 if (TelephonyUtil.isPstnConnectionService(mCallServiceDescriptors.get(i))) {
Ihab Awad55a34282014-06-18 10:31:09 -0700292 mCallServiceDescriptors.add(0, mCallServiceDescriptors.remove(i));
293 return;
294 }
Santos Cordon5924bea2014-06-18 06:39:51 -0700295 } else {
296 if (mCallServiceDescriptors.get(i).getServiceComponent().getPackageName().equals(
297 "com.android.telecomm.tests")) {
298 mCallServiceDescriptors.add(0, mCallServiceDescriptors.remove(i));
299 }
Ihab Awad55a34282014-06-18 10:31:09 -0700300 }
301 }
302 }
303
304 private boolean shouldProcessAsEmergency(Uri handle) {
305 return PhoneNumberUtils.isPotentialLocalEmergencyNumber(
306 TelecommApp.getInstance(), handle.getSchemeSpecificPart());
307 }
Ben Gilad0bf5b912014-01-28 17:55:57 -0800308}