blob: 14fa9ed683d758276cb0bbe27790712352ebcdc1 [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/**
38 * Utility class to place a call using the specified set of call-services and ordered selectors.
Ben Giladc5b22692014-02-18 20:03:22 -080039 * Iterates through the selectors and gets a sorted list of supported call-service descriptors
40 * for each selector. Upon receiving each sorted list (one list per selector), each of the
41 * corresponding call services is then attempted until either the outgoing call is placed, the
Santos Cordon74d420b2014-05-07 14:38:47 -070042 * attempted call is aborted, or the list is exhausted -- whichever occurs first.
Ben Gilad0bf5b912014-01-28 17:55:57 -080043 *
Santos Cordon74d420b2014-05-07 14:38:47 -070044 * Except for the abort case, all other scenarios should terminate with the call notified
Santos Cordon681663d2014-01-30 04:32:15 -080045 * of the result.
Ben Giladd88323b2014-01-29 19:03:19 -080046 *
47 * NOTE(gilad): Currently operating under the assumption that we'll have one timeout per (outgoing)
48 * call attempt. If we (also) like to timeout individual selectors and/or call services, the code
49 * here will need to be re-factored (quite a bit) to support that.
Ben Gilad0bf5b912014-01-28 17:55:57 -080050 */
51final class OutgoingCallProcessor {
52
Santos Cordon682fe6b2014-05-20 08:56:39 -070053 private final static int MSG_EXPIRE = 1;
54
Ben Gilad0bf5b912014-01-28 17:55:57 -080055 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -080056 * The outgoing call this processor is tasked with placing.
57 */
58 private final Call mCall;
59
60 /**
Sailesh Nepalb6141ae2014-02-18 08:45:26 -080061 * The map of currently-available call-service implementations keyed by call-service ID.
Ben Gilad0bf5b912014-01-28 17:55:57 -080062 */
Sailesh Nepalb6141ae2014-02-18 08:45:26 -080063 private final Map<String, CallServiceWrapper> mCallServicesById = Maps.newHashMap();
Ben Gilad0bf5b912014-01-28 17:55:57 -080064
65 /**
Ben Gilad61925612014-03-11 19:06:36 -070066 * The set of attempted call services, used to ensure services are attempted at most once per
67 * outgoing-call attempt.
68 */
69 private final Set<CallServiceWrapper> mAttemptedCallServices = Sets.newHashSet();
70
Santos Cordon682fe6b2014-05-20 08:56:39 -070071 private final CallServiceRepository mCallServiceRepository;
Ben Gilad61925612014-03-11 19:06:36 -070072
Santos Cordon682fe6b2014-05-20 08:56:39 -070073 private final Handler mHandler = new Handler() {
74 @Override
75 public void handleMessage(Message msg) {
76 switch (msg.what) {
77 case MSG_EXPIRE:
78 abort();
79 break;
80 }
Ben Gilad61925612014-03-11 19:06:36 -070081 }
82 };
83
Santos Cordon682fe6b2014-05-20 08:56:39 -070084 /**
85 * The duplicate-free list of currently-available call-service descriptors.
86 */
87 private List<CallServiceDescriptor> mCallServiceDescriptors;
Sailesh Nepal18386a82014-03-19 10:22:40 -070088
Ben Gilad0bf5b912014-01-28 17:55:57 -080089 /**
Ben Giladc5b22692014-02-18 20:03:22 -080090 * The iterator over the currently-selected ordered list of call-service descriptors.
Ben Gilad0bf5b912014-01-28 17:55:57 -080091 */
Ben Giladc5b22692014-02-18 20:03:22 -080092 private Iterator<CallServiceDescriptor> mCallServiceDescriptorIterator;
Ben Gilad0bf5b912014-01-28 17:55:57 -080093
Santos Cordon682fe6b2014-05-20 08:56:39 -070094 private AsyncResultCallback<Boolean> mResultCallback;
95
Ben Gilad0bf5b912014-01-28 17:55:57 -080096 private boolean mIsAborted = false;
97
Ihab Awad0fea3f22014-06-03 18:45:05 -070098 private int mLastErrorCode = 0;
99
100 private String mLastErrorMsg = null;
101
Ben Gilad0bf5b912014-01-28 17:55:57 -0800102 /**
Ihab Awad55a34282014-06-18 10:31:09 -0700103 * Persists the specified parameters and iterates through the prioritized list of call
104 * services. Stops once a matching call service is found. Calls with no matching
105 * call service will eventually be killed by the cleanup/monitor switchboard handler.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800106 *
107 * @param call The call to place.
Santos Cordon682fe6b2014-05-20 08:56:39 -0700108 * @param callServiceRepository
Santos Cordon682fe6b2014-05-20 08:56:39 -0700109 * @param resultCallback The callback on which to return the result.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800110 */
111 OutgoingCallProcessor(
112 Call call,
Santos Cordon682fe6b2014-05-20 08:56:39 -0700113 CallServiceRepository callServiceRepository,
Santos Cordon682fe6b2014-05-20 08:56:39 -0700114 AsyncResultCallback<Boolean> resultCallback) {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800115
Ben Giladd88323b2014-01-29 19:03:19 -0800116 ThreadUtil.checkOnMainThread();
117
Ben Gilad0bf5b912014-01-28 17:55:57 -0800118 mCall = call;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700119 mResultCallback = resultCallback;
120 mCallServiceRepository = callServiceRepository;
Ben Gilad0bf5b912014-01-28 17:55:57 -0800121 }
122
123 /**
124 * Initiates the attempt to place the call. No-op beyond the first invocation.
125 */
126 void process() {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700127 Log.v(this, "process, mIsAborted: %b", mIsAborted);
Ben Gilad28e8ad62014-03-06 17:01:54 -0800128 if (!mIsAborted) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700129 // Start the expiration timeout.
130 mHandler.sendEmptyMessageDelayed(MSG_EXPIRE, Timeouts.getNewOutgoingCallMillis());
Ben Giladd88323b2014-01-29 19:03:19 -0800131
Santos Cordon682fe6b2014-05-20 08:56:39 -0700132 // Lookup call services
133 mCallServiceRepository.lookupServices(new LookupCallback<CallServiceWrapper>() {
134 @Override
135 public void onComplete(Collection<CallServiceWrapper> services) {
136 setCallServices(services);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700137 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700138 });
Sailesh Nepal810735e2014-03-18 18:15:46 -0700139 }
Ben Gilad61925612014-03-11 19:06:36 -0700140 }
141
142 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -0800143 * Aborts the attempt to place the relevant call. Intended to be invoked by
Santos Cordon681663d2014-01-30 04:32:15 -0800144 * switchboard through the outgoing-calls manager.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800145 */
146 void abort() {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700147 Log.v(this, "abort");
Ben Gilad9c234112014-03-04 16:07:33 -0800148 ThreadUtil.checkOnMainThread();
Santos Cordon682fe6b2014-05-20 08:56:39 -0700149 if (!mIsAborted && mResultCallback != null) {
Ben Gilad9c234112014-03-04 16:07:33 -0800150 mIsAborted = true;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700151
152 // On an abort, we need to check if we already told the call service to place the
153 // call. If so, we need to tell it to abort.
154 // TODO(santoscordon): The call service is saved with the call and so we have to query
155 // the call to get it, which is a bit backwards. Ideally, the call service would be
156 // saved inside this class until the whole thing is complete and then set on the call.
157 CallServiceWrapper callService = mCall.getCallService();
158 if (callService != null) {
159 callService.abort(mCall);
160 }
161
Ihab Awad0fea3f22014-06-03 18:45:05 -0700162 // We consider a deliberate abort to be a "normal" disconnect, not
163 // requiring special reporting.
164 sendResult(false, DisconnectCause.LOCAL, null);
Ben Gilad9c234112014-03-04 16:07:33 -0800165 }
Ben Gilad0bf5b912014-01-28 17:55:57 -0800166 }
167
Santos Cordon682fe6b2014-05-20 08:56:39 -0700168 boolean isAborted() {
169 return mIsAborted;
170 }
171
Ben Gilad0bf5b912014-01-28 17:55:57 -0800172 /**
Santos Cordon681663d2014-01-30 04:32:15 -0800173 * Completes the outgoing call sequence by setting the call service on the call object. This is
174 * invoked when the call service adapter receives positive confirmation that the call service
175 * placed the call.
176 */
Santos Cordon682fe6b2014-05-20 08:56:39 -0700177 void handleSuccessfulCallAttempt(CallServiceWrapper callService) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700178 Log.v(this, "handleSuccessfulCallAttempt");
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800179 ThreadUtil.checkOnMainThread();
180
181 if (mIsAborted) {
182 // TODO(gilad): Ask the call service to drop the call?
Santos Cordon682fe6b2014-05-20 08:56:39 -0700183 callService.abort(mCall);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800184 return;
185 }
186
Ihab Awad0fea3f22014-06-03 18:45:05 -0700187 sendResult(true, DisconnectCause.NOT_DISCONNECTED, null);
Santos Cordon681663d2014-01-30 04:32:15 -0800188 }
189
190 /**
191 * Attempts the next call service if the specified call service is the one currently being
192 * attempted.
193 *
Ihab Awad0fea3f22014-06-03 18:45:05 -0700194 * @param errorCode The reason for the failure, one of {@link DisconnectCause}.
195 * @param errorMsg Optional text reason for the failure.
Santos Cordon681663d2014-01-30 04:32:15 -0800196 */
Ihab Awad0fea3f22014-06-03 18:45:05 -0700197 void handleFailedCallAttempt(int errorCode, String errorMsg) {
198 Log.v(this, "handleFailedCallAttempt %s %s", DisconnectCause.toString(errorCode), errorMsg);
199 // Store latest error code and message. If this is our last available attempt at placing
200 // a call, these error details will be considered "the" cause of the failure.
201 mLastErrorCode = errorCode;
202 mLastErrorMsg = errorMsg;
Ben Gilad28e8ad62014-03-06 17:01:54 -0800203 if (!mIsAborted) {
204 ThreadUtil.checkOnMainThread();
Ben Gilad28e8ad62014-03-06 17:01:54 -0800205 attemptNextCallService();
206 }
Santos Cordon681663d2014-01-30 04:32:15 -0800207 }
208
209 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700210 * Sets the call services to attempt for this outgoing call.
211 *
212 * @param callServices The call services.
213 */
214 private void setCallServices(Collection<CallServiceWrapper> callServices) {
215 mCallServiceDescriptors = new ArrayList<>();
216
217 // Populate the list and map of call-service descriptors. The list is needed since
218 // it's being passed down to selectors.
219 for (CallServiceWrapper callService : callServices) {
220 CallServiceDescriptor descriptor = callService.getDescriptor();
Sailesh Nepala1b3bca2014-06-24 15:17:44 -0700221 // TODO(sail): Remove once there's a way to pick the service.
222 if (descriptor.getServiceComponent().getPackageName().equals(
223 "com.google.android.talk")) {
224 Log.i(this, "Moving call service %s to top of list", descriptor);
225 mCallServiceDescriptors.add(0, descriptor);
226 } else {
227 mCallServiceDescriptors.add(descriptor);
228 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700229 mCallServicesById.put(descriptor.getCallServiceId(), callService);
230 }
231
Ihab Awad55a34282014-06-18 10:31:09 -0700232 adjustCallServiceDescriptorsForEmergency();
Santos Cordon682fe6b2014-05-20 08:56:39 -0700233
Ihab Awad55a34282014-06-18 10:31:09 -0700234 mCallServiceDescriptorIterator = mCallServiceDescriptors.iterator();
235 attemptNextCallService();
Ben Gilad0bf5b912014-01-28 17:55:57 -0800236 }
237
238 /**
Ben Giladc5b22692014-02-18 20:03:22 -0800239 * Attempts to place the call using the call service specified by the next call-service
Ihab Awad55a34282014-06-18 10:31:09 -0700240 * descriptor of mCallServiceDescriptorIterator.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800241 */
242 private void attemptNextCallService() {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700243 Log.v(this, "attemptNextCallService, mIsAborted: %b", mIsAborted);
Ben Gilad0bf5b912014-01-28 17:55:57 -0800244 if (mIsAborted) {
245 return;
246 }
247
Evan Charltonf02e9882014-03-06 12:54:52 -0800248 if (mCallServiceDescriptorIterator != null && mCallServiceDescriptorIterator.hasNext()) {
Ben Giladc5b22692014-02-18 20:03:22 -0800249 CallServiceDescriptor descriptor = mCallServiceDescriptorIterator.next();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800250 final CallServiceWrapper callService =
251 mCallServicesById.get(descriptor.getCallServiceId());
252
Ben Gilad61925612014-03-11 19:06:36 -0700253 if (callService == null || mAttemptedCallServices.contains(callService)) {
254 // The next call service is either null or has already been attempted, fast forward
255 // to the next.
Santos Cordon681663d2014-01-30 04:32:15 -0800256 attemptNextCallService();
257 } else {
Ben Gilad61925612014-03-11 19:06:36 -0700258 mAttemptedCallServices.add(callService);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800259 mCall.setCallService(callService);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700260
261 // Increment the associated call count until we get a result. This prevents the call
262 // service from unbinding while we are using it.
263 callService.incrementAssociatedCallCount();
264
265 callService.call(mCall, new AsyncResultCallback<Boolean>() {
266 @Override
Ihab Awad0fea3f22014-06-03 18:45:05 -0700267 public void onResult(Boolean wasCallPlaced, int errorCode, String errorMsg) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700268 if (wasCallPlaced) {
269 handleSuccessfulCallAttempt(callService);
270 } else {
Ihab Awad0fea3f22014-06-03 18:45:05 -0700271 handleFailedCallAttempt(errorCode, errorMsg);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700272 }
273
274 // If successful, the call should not have it's own association to keep
275 // the call service bound.
276 callService.decrementAssociatedCallCount();
277 }
278 });
Ben Gilad0bf5b912014-01-28 17:55:57 -0800279 }
280 } else {
Ihab Awad55a34282014-06-18 10:31:09 -0700281 Log.v(this, "attemptNextCallService, no more service descriptors, failing");
Ben Giladc5b22692014-02-18 20:03:22 -0800282 mCallServiceDescriptorIterator = null;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700283 mCall.clearCallService();
Ihab Awad55a34282014-06-18 10:31:09 -0700284 sendResult(false, mLastErrorCode, mLastErrorMsg);
Ben Gilad0bf5b912014-01-28 17:55:57 -0800285 }
286 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700287
Ihab Awad0fea3f22014-06-03 18:45:05 -0700288 private void sendResult(boolean wasCallPlaced, int errorCode, String errorMsg) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700289 if (mResultCallback != null) {
Ihab Awad0fea3f22014-06-03 18:45:05 -0700290 mResultCallback.onResult(wasCallPlaced, errorCode, errorMsg);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700291 mResultCallback = null;
292
293 mHandler.removeMessages(MSG_EXPIRE);
294 } else {
295 Log.wtf(this, "Attempting to return outgoing result twice for call %s", mCall);
296 }
297 }
Ihab Awad55a34282014-06-18 10:31:09 -0700298
299 // If we are possibly attempting to call a local emergency number, ensure that the
300 // plain PSTN call service, if it exists, is attempted first.
301 private void adjustCallServiceDescriptorsForEmergency() {
302 if (shouldProcessAsEmergency(mCall.getHandle())) {
303 for (int i = 0; i < mCallServiceDescriptors.size(); i++) {
304 if (TelephonyUtil.isPstnCallService(mCallServiceDescriptors.get(i))) {
305 mCallServiceDescriptors.add(0, mCallServiceDescriptors.remove(i));
306 return;
307 }
308 }
309 }
310 }
311
312 private boolean shouldProcessAsEmergency(Uri handle) {
313 return PhoneNumberUtils.isPotentialLocalEmergencyNumber(
314 TelecommApp.getInstance(), handle.getSchemeSpecificPart());
315 }
Ben Gilad0bf5b912014-01-28 17:55:57 -0800316}