blob: 0f77567eb553d8e5c2fd2580ce3f3cdfedccd2d6 [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
Santos Cordon682fe6b2014-05-20 08:56:39 -070019import android.content.ComponentName;
20import android.os.Handler;
21import android.os.Message;
Ben Giladc5b22692014-02-18 20:03:22 -080022import android.telecomm.CallServiceDescriptor;
Sailesh Nepalb6141ae2014-02-18 08:45:26 -080023
Santos Cordon682fe6b2014-05-20 08:56:39 -070024import com.android.telecomm.BaseRepository.LookupCallback;
Ben Gilad61925612014-03-11 19:06:36 -070025import com.google.android.collect.Sets;
Santos Cordon682fe6b2014-05-20 08:56:39 -070026import com.google.common.collect.ImmutableList;
Santos Cordon766d04f2014-05-06 10:28:25 -070027import com.google.common.collect.Maps;
Ben Gilad0bf5b912014-01-28 17:55:57 -080028
Santos Cordon682fe6b2014-05-20 08:56:39 -070029import java.util.ArrayList;
Sailesh Nepal18386a82014-03-19 10:22:40 -070030import java.util.Collection;
Ben Gilad0bf5b912014-01-28 17:55:57 -080031import java.util.Iterator;
32import java.util.List;
33import java.util.Map;
34import java.util.Set;
35
36/**
37 * Utility class to place a call using the specified set of call-services and ordered selectors.
Ben Giladc5b22692014-02-18 20:03:22 -080038 * Iterates through the selectors and gets a sorted list of supported call-service descriptors
39 * for each selector. Upon receiving each sorted list (one list per selector), each of the
40 * corresponding call services is then attempted until either the outgoing call is placed, the
Santos Cordon74d420b2014-05-07 14:38:47 -070041 * attempted call is aborted, or the list is exhausted -- whichever occurs first.
Ben Gilad0bf5b912014-01-28 17:55:57 -080042 *
Santos Cordon74d420b2014-05-07 14:38:47 -070043 * Except for the abort case, all other scenarios should terminate with the call notified
Santos Cordon681663d2014-01-30 04:32:15 -080044 * of the result.
Ben Giladd88323b2014-01-29 19:03:19 -080045 *
46 * NOTE(gilad): Currently operating under the assumption that we'll have one timeout per (outgoing)
47 * call attempt. If we (also) like to timeout individual selectors and/or call services, the code
48 * here will need to be re-factored (quite a bit) to support that.
Ben Gilad0bf5b912014-01-28 17:55:57 -080049 */
50final class OutgoingCallProcessor {
51
Santos Cordon682fe6b2014-05-20 08:56:39 -070052 private final static int MSG_EXPIRE = 1;
53
Ben Gilad0bf5b912014-01-28 17:55:57 -080054 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -080055 * The outgoing call this processor is tasked with placing.
56 */
57 private final Call mCall;
58
59 /**
Sailesh Nepalb6141ae2014-02-18 08:45:26 -080060 * The map of currently-available call-service implementations keyed by call-service ID.
Ben Gilad0bf5b912014-01-28 17:55:57 -080061 */
Sailesh Nepalb6141ae2014-02-18 08:45:26 -080062 private final Map<String, CallServiceWrapper> mCallServicesById = Maps.newHashMap();
Ben Gilad0bf5b912014-01-28 17:55:57 -080063
64 /**
Ben Gilad61925612014-03-11 19:06:36 -070065 * The set of attempted call services, used to ensure services are attempted at most once per
66 * outgoing-call attempt.
67 */
68 private final Set<CallServiceWrapper> mAttemptedCallServices = Sets.newHashSet();
69
Santos Cordon682fe6b2014-05-20 08:56:39 -070070 private final CallServiceRepository mCallServiceRepository;
Ben Gilad61925612014-03-11 19:06:36 -070071
Santos Cordon682fe6b2014-05-20 08:56:39 -070072 private final CallServiceSelectorRepository mSelectorRepository;
Ben Giladd88323b2014-01-29 19:03:19 -080073
Santos Cordon682fe6b2014-05-20 08:56:39 -070074 private final Handler mHandler = new Handler() {
75 @Override
76 public void handleMessage(Message msg) {
77 switch (msg.what) {
78 case MSG_EXPIRE:
79 abort();
80 break;
81 }
Ben Gilad61925612014-03-11 19:06:36 -070082 }
83 };
84
Santos Cordon682fe6b2014-05-20 08:56:39 -070085 /**
86 * The duplicate-free list of currently-available call-service descriptors.
87 */
88 private List<CallServiceDescriptor> mCallServiceDescriptors;
Sailesh Nepal18386a82014-03-19 10:22:40 -070089
Ben Gilad0bf5b912014-01-28 17:55:57 -080090 /**
Ben Giladc5b22692014-02-18 20:03:22 -080091 * The iterator over the currently-selected ordered list of call-service descriptors.
Ben Gilad0bf5b912014-01-28 17:55:57 -080092 */
Ben Giladc5b22692014-02-18 20:03:22 -080093 private Iterator<CallServiceDescriptor> mCallServiceDescriptorIterator;
Ben Gilad0bf5b912014-01-28 17:55:57 -080094
Santos Cordon682fe6b2014-05-20 08:56:39 -070095 /**
96 * The list of currently-available call-service selector implementations.
97 */
98 private Collection<CallServiceSelectorWrapper> mSelectors;
99
Sailesh Nepal18386a82014-03-19 10:22:40 -0700100 private Iterator<CallServiceSelectorWrapper> mSelectorIterator;
Ben Gilad0bf5b912014-01-28 17:55:57 -0800101
Santos Cordon682fe6b2014-05-20 08:56:39 -0700102 private AsyncResultCallback<Boolean> mResultCallback;
103
Ben Gilad0bf5b912014-01-28 17:55:57 -0800104 private boolean mIsAborted = false;
105
106 /**
107 * Persists the specified parameters and iterates through the prioritized list of selectors
108 * passing to each selector (read-only versions of) the call object and all available call-
Ben Giladc5b22692014-02-18 20:03:22 -0800109 * service descriptors. Stops once a matching selector is found. Calls with no matching
110 * selectors will eventually be killed by the cleanup/monitor switchboard handler, which will
111 * in turn call the abort method of this processor via {@link OutgoingCallsManager}.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800112 *
113 * @param call The call to place.
Santos Cordon682fe6b2014-05-20 08:56:39 -0700114 * @param callServiceRepository
115 * @param selectorRepository
116 * @param resultCallback The callback on which to return the result.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800117 */
118 OutgoingCallProcessor(
119 Call call,
Santos Cordon682fe6b2014-05-20 08:56:39 -0700120 CallServiceRepository callServiceRepository,
121 CallServiceSelectorRepository selectorRepository,
122 AsyncResultCallback<Boolean> resultCallback) {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800123
Ben Giladd88323b2014-01-29 19:03:19 -0800124 ThreadUtil.checkOnMainThread();
125
Ben Gilad0bf5b912014-01-28 17:55:57 -0800126 mCall = call;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700127 mResultCallback = resultCallback;
128 mCallServiceRepository = callServiceRepository;
129 mSelectorRepository = selectorRepository;
Ben Gilad0bf5b912014-01-28 17:55:57 -0800130 }
131
132 /**
133 * Initiates the attempt to place the call. No-op beyond the first invocation.
134 */
135 void process() {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700136 Log.v(this, "process, mIsAborted: %b", mIsAborted);
Ben Gilad28e8ad62014-03-06 17:01:54 -0800137 if (!mIsAborted) {
Santos Cordon682fe6b2014-05-20 08:56:39 -0700138 // Start the expiration timeout.
139 mHandler.sendEmptyMessageDelayed(MSG_EXPIRE, Timeouts.getNewOutgoingCallMillis());
Ben Giladd88323b2014-01-29 19:03:19 -0800140
Santos Cordon682fe6b2014-05-20 08:56:39 -0700141 // Lookup call services
142 mCallServiceRepository.lookupServices(new LookupCallback<CallServiceWrapper>() {
143 @Override
144 public void onComplete(Collection<CallServiceWrapper> services) {
145 setCallServices(services);
Sailesh Nepal810735e2014-03-18 18:15:46 -0700146 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700147 });
148
149 if (mCall.getCallServiceSelector() == null) {
150 // Lookup selectors
151 mSelectorRepository.lookupServices(
152 new LookupCallback<CallServiceSelectorWrapper>() {
153 @Override
154 public void onComplete(
155 Collection<CallServiceSelectorWrapper> selectors) {
156 setSelectors(selectors);
157 }
158 });
159 } else {
160 setSelectors(ImmutableList.of(mCall.getCallServiceSelector()));
Sailesh Nepal810735e2014-03-18 18:15:46 -0700161 }
Sailesh Nepal810735e2014-03-18 18:15:46 -0700162 }
Ben Gilad61925612014-03-11 19:06:36 -0700163 }
164
165 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -0800166 * Aborts the attempt to place the relevant call. Intended to be invoked by
Santos Cordon681663d2014-01-30 04:32:15 -0800167 * switchboard through the outgoing-calls manager.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800168 */
169 void abort() {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700170 Log.v(this, "abort");
Ben Gilad9c234112014-03-04 16:07:33 -0800171 ThreadUtil.checkOnMainThread();
Santos Cordon682fe6b2014-05-20 08:56:39 -0700172 if (!mIsAborted && mResultCallback != null) {
Ben Gilad9c234112014-03-04 16:07:33 -0800173 mIsAborted = true;
Santos Cordon682fe6b2014-05-20 08:56:39 -0700174
175 // On an abort, we need to check if we already told the call service to place the
176 // call. If so, we need to tell it to abort.
177 // TODO(santoscordon): The call service is saved with the call and so we have to query
178 // the call to get it, which is a bit backwards. Ideally, the call service would be
179 // saved inside this class until the whole thing is complete and then set on the call.
180 CallServiceWrapper callService = mCall.getCallService();
181 if (callService != null) {
182 callService.abort(mCall);
183 }
184
185 sendResult(false);
Ben Gilad9c234112014-03-04 16:07:33 -0800186 }
Ben Gilad0bf5b912014-01-28 17:55:57 -0800187 }
188
Santos Cordon682fe6b2014-05-20 08:56:39 -0700189 boolean isAborted() {
190 return mIsAborted;
191 }
192
Ben Gilad0bf5b912014-01-28 17:55:57 -0800193 /**
Santos Cordon681663d2014-01-30 04:32:15 -0800194 * Completes the outgoing call sequence by setting the call service on the call object. This is
195 * invoked when the call service adapter receives positive confirmation that the call service
196 * placed the call.
197 */
Santos Cordon682fe6b2014-05-20 08:56:39 -0700198 void handleSuccessfulCallAttempt(CallServiceWrapper callService) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700199 Log.v(this, "handleSuccessfulCallAttempt");
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800200 ThreadUtil.checkOnMainThread();
201
202 if (mIsAborted) {
203 // TODO(gilad): Ask the call service to drop the call?
Santos Cordon682fe6b2014-05-20 08:56:39 -0700204 callService.abort(mCall);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800205 return;
206 }
207
Santos Cordon682fe6b2014-05-20 08:56:39 -0700208 sendResult(true);
Santos Cordon681663d2014-01-30 04:32:15 -0800209 }
210
211 /**
212 * Attempts the next call service if the specified call service is the one currently being
213 * attempted.
214 *
215 * @param reason The call-service supplied reason for the failed call attempt.
216 */
217 void handleFailedCallAttempt(String reason) {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700218 Log.v(this, "handleFailedCallAttempt");
Ben Gilad28e8ad62014-03-06 17:01:54 -0800219 if (!mIsAborted) {
220 ThreadUtil.checkOnMainThread();
Ben Gilad28e8ad62014-03-06 17:01:54 -0800221 attemptNextCallService();
222 }
Santos Cordon681663d2014-01-30 04:32:15 -0800223 }
224
225 /**
Sailesh Nepal6ab6fb72014-04-01 20:03:19 -0700226 * Persists the ordered-list of call-service descriptor as selected by the current selector and
227 * starts iterating through the corresponding call services continuing the attempt to place the
228 * call.
229 *
230 * @param descriptors The (ordered) list of call-service descriptor.
231 */
232 void processSelectedCallServices(List<CallServiceDescriptor> descriptors) {
233 Log.v(this, "processSelectedCallServices");
234 if (descriptors == null || descriptors.isEmpty()) {
235 attemptNextSelector();
236 } else if (mCallServiceDescriptorIterator == null) {
237 mCallServiceDescriptorIterator = descriptors.iterator();
238 attemptNextCallService();
239 }
240 }
241
242 /**
Santos Cordon682fe6b2014-05-20 08:56:39 -0700243 * Sets the call services to attempt for this outgoing call.
244 *
245 * @param callServices The call services.
246 */
247 private void setCallServices(Collection<CallServiceWrapper> callServices) {
248 mCallServiceDescriptors = new ArrayList<>();
249
250 // Populate the list and map of call-service descriptors. The list is needed since
251 // it's being passed down to selectors.
252 for (CallServiceWrapper callService : callServices) {
253 CallServiceDescriptor descriptor = callService.getDescriptor();
254 mCallServiceDescriptors.add(descriptor);
255 mCallServicesById.put(descriptor.getCallServiceId(), callService);
256 }
257
258 onLookupComplete();
259 }
260
261 /**
262 * Sets the selectors to attemnpt for this outgoing call.
263 *
264 * @param selectors The call-service selectors.
265 */
266 private void setSelectors(Collection<CallServiceSelectorWrapper> selectors) {
267 mSelectors = adjustForEmergencyCalls(selectors);
268 onLookupComplete();
269 }
270
271 private void onLookupComplete() {
272 if (!mIsAborted && mSelectors != null && mCallServiceDescriptors != null) {
273 if (mSelectorIterator == null) {
274 mSelectorIterator = mSelectors.iterator();
275 attemptNextSelector();
276 }
277 }
278 }
279
280 /**
281 * Updates the specified collection of selectors to accomodate for emergency calls and any
282 * preferred selectors specified in the call object.
283 *
284 * @param selectors The selectors found through the selector repository.
285 */
286 private Collection<CallServiceSelectorWrapper> adjustForEmergencyCalls(
287 Collection<CallServiceSelectorWrapper> selectors) {
288 boolean useEmergencySelector =
289 EmergencyCallServiceSelector.shouldUseSelector(mCall.getHandle());
290 Log.d(this, "processNewOutgoingCall, isEmergency=%b", useEmergencySelector);
291
292 if (useEmergencySelector) {
293 // This is potentially an emergency call so add the emergency selector before the
294 // other selectors.
295 ImmutableList.Builder<CallServiceSelectorWrapper> selectorsBuilder =
296 ImmutableList.builder();
297
298 ComponentName componentName = new ComponentName(
299 TelecommApp.getInstance(), EmergencyCallServiceSelector.class);
300 CallServiceSelectorWrapper emergencySelector =
301 new CallServiceSelectorWrapper(
302 componentName.flattenToShortString(),
303 componentName,
304 CallsManager.getInstance());
305
306 selectorsBuilder.add(emergencySelector);
307 selectorsBuilder.addAll(selectors);
308 selectors = selectorsBuilder.build();
309 }
310
311 return selectors;
312 }
313
314 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -0800315 * Attempts to place the call using the next selector, no-op if no other selectors
316 * are available.
317 */
318 private void attemptNextSelector() {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700319 Log.v(this, "attemptNextSelector, mIsAborted: %b", mIsAborted);
Ben Gilad0bf5b912014-01-28 17:55:57 -0800320 if (mIsAborted) {
321 return;
322 }
323
324 if (mSelectorIterator.hasNext()) {
Sailesh Nepal18386a82014-03-19 10:22:40 -0700325 CallServiceSelectorWrapper selector = mSelectorIterator.next();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800326 mCall.setCallServiceSelector(selector);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700327 selector.select(mCall, mCallServiceDescriptors,
328 new AsyncResultCallback<List<CallServiceDescriptor>>() {
329 @Override
330 public void onResult(List<CallServiceDescriptor> descriptors) {
331 processSelectedCallServices(descriptors);
332 }
333 });
Ben Gilad0bf5b912014-01-28 17:55:57 -0800334 } else {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700335 Log.v(this, "attemptNextSelector, no more selectors, failing");
Sailesh Nepale59bb192014-04-01 18:33:59 -0700336 mCall.clearCallServiceSelector();
Santos Cordon682fe6b2014-05-20 08:56:39 -0700337 sendResult(false);
Ben Gilad0bf5b912014-01-28 17:55:57 -0800338 }
339 }
340
341 /**
Ben Giladc5b22692014-02-18 20:03:22 -0800342 * Attempts to place the call using the call service specified by the next call-service
343 * descriptor of mCallServiceDescriptorIterator. If there are no more call services to
344 * attempt, the process continues to the next call-service selector via
345 * {@link #attemptNextSelector}.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800346 */
347 private void attemptNextCallService() {
Sailesh Nepal810735e2014-03-18 18:15:46 -0700348 Log.v(this, "attemptNextCallService, mIsAborted: %b", mIsAborted);
Ben Gilad0bf5b912014-01-28 17:55:57 -0800349 if (mIsAborted) {
350 return;
351 }
352
Evan Charltonf02e9882014-03-06 12:54:52 -0800353 if (mCallServiceDescriptorIterator != null && mCallServiceDescriptorIterator.hasNext()) {
Ben Giladc5b22692014-02-18 20:03:22 -0800354 CallServiceDescriptor descriptor = mCallServiceDescriptorIterator.next();
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800355 final CallServiceWrapper callService =
356 mCallServicesById.get(descriptor.getCallServiceId());
357
Ben Gilad61925612014-03-11 19:06:36 -0700358 if (callService == null || mAttemptedCallServices.contains(callService)) {
359 // The next call service is either null or has already been attempted, fast forward
360 // to the next.
Santos Cordon681663d2014-01-30 04:32:15 -0800361 attemptNextCallService();
362 } else {
Ben Gilad61925612014-03-11 19:06:36 -0700363 mAttemptedCallServices.add(callService);
Ben Gilad8e55d1d2014-02-26 16:25:56 -0800364 mCall.setCallService(callService);
Santos Cordon682fe6b2014-05-20 08:56:39 -0700365
366 // Increment the associated call count until we get a result. This prevents the call
367 // service from unbinding while we are using it.
368 callService.incrementAssociatedCallCount();
369
370 callService.call(mCall, new AsyncResultCallback<Boolean>() {
371 @Override
372 public void onResult(Boolean wasCallPlaced) {
373 if (wasCallPlaced) {
374 handleSuccessfulCallAttempt(callService);
375 } else {
376 handleFailedCallAttempt("call failed.");
377 }
378
379 // If successful, the call should not have it's own association to keep
380 // the call service bound.
381 callService.decrementAssociatedCallCount();
382 }
383 });
Ben Gilad0bf5b912014-01-28 17:55:57 -0800384 }
385 } else {
Ben Giladc5b22692014-02-18 20:03:22 -0800386 mCallServiceDescriptorIterator = null;
Sailesh Nepale59bb192014-04-01 18:33:59 -0700387 mCall.clearCallService();
Ben Gilad0bf5b912014-01-28 17:55:57 -0800388 attemptNextSelector();
389 }
390 }
Santos Cordon682fe6b2014-05-20 08:56:39 -0700391
392 private void sendResult(boolean wasCallPlaced) {
393 if (mResultCallback != null) {
394 mResultCallback.onResult(wasCallPlaced);
395 mResultCallback = null;
396
397 mHandler.removeMessages(MSG_EXPIRE);
398 } else {
399 Log.wtf(this, "Attempting to return outgoing result twice for call %s", mCall);
400 }
401 }
Ben Gilad0bf5b912014-01-28 17:55:57 -0800402}