blob: bb1ef3ab6e3ccc3a2b1c1ece075fe75d941736a3 [file] [log] [blame]
Santos Cordon8e8b8d22013-12-19 14:14:05 -08001/*
2 * Copyright 2013, 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
Ben Gilad9f2bed32013-12-12 17:43:26 -080017package com.android.telecomm;
18
Ben Gilad0bf5b912014-01-28 17:55:57 -080019import com.google.common.base.Preconditions;
Evan Charltonac1aa9e2014-01-03 13:47:12 -080020import com.google.common.collect.Lists;
Ben Gilad0bf5b912014-01-28 17:55:57 -080021import com.google.common.collect.Maps;
Ben Gilad0407fb22014-01-09 16:18:41 -080022import com.google.common.collect.Sets;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080023
24import android.content.Context;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080025import android.telecomm.ICallService;
Ben Giladb59769e2014-01-16 11:41:10 -080026import android.telecomm.ICallServiceSelector;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080027
Ben Giladb59769e2014-01-16 11:41:10 -080028import java.util.Collection;
Ben Gilad9f2bed32013-12-12 17:43:26 -080029import java.util.List;
Ben Gilad0bf5b912014-01-28 17:55:57 -080030import java.util.Map;
Ben Gilad0407fb22014-01-09 16:18:41 -080031import java.util.Set;
Ben Gilad9f2bed32013-12-12 17:43:26 -080032
Santos Cordon8e8b8d22013-12-19 14:14:05 -080033/**
34 * Switchboard is responsible for (1) selecting the {@link ICallService} through which to make
35 * outgoing calls and (2) switching active calls between transports (each ICallService is
36 * considered a different transport type).
37 * TODO(santoscordon): Need to add comments on the switchboard optimizer once that it is place.
Ben Gilad0407fb22014-01-09 16:18:41 -080038 * TODO(gilad): Add a monitor thread to wake up periodically and check for stale pending calls
Ben Gilad0bf5b912014-01-28 17:55:57 -080039 * that may need to be terminated, see mNewOutgoingCalls and mPendingOutgoingCalls.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080040 */
Ben Giladd17443c2014-01-06 11:04:15 -080041final class Switchboard {
Ben Gilad2495d572014-01-09 17:26:19 -080042
Ben Giladb59769e2014-01-16 11:41:10 -080043 private CallServiceFinder mCallServiceFinder = new CallServiceFinder(this);
44
45 private CallServiceSelectorFinder mSelectorFinder = new CallServiceSelectorFinder(this);
46
Ben Gilad03292d42014-01-16 15:06:16 -080047 /**
48 * The set of currently available call-service implementations, see {@link CallServiceFinder}.
49 * TODO(gilad): Null out once the active-call count goes to zero.
50 */
51 private Set<ICallService> mCallServices;
Ben Giladb59769e2014-01-16 11:41:10 -080052
Ben Gilad03292d42014-01-16 15:06:16 -080053 /**
54 * The set of currently available call-service-selector implementations,
55 * see {@link CallServiceSelectorFinder}.
56 * TODO(gilad): Null out once the active-call count goes to zero.
57 */
58 private Set<ICallServiceSelector> mSelectors;
Ben Gilad0407fb22014-01-09 16:18:41 -080059
Ben Gilad0bf5b912014-01-28 17:55:57 -080060 private Set<Call> mNewOutgoingCalls = Sets.newLinkedHashSet();
61
62 private Set<Call> mPendingOutgoingCalls = Sets.newLinkedHashSet();
63
64 private Map<Call, OutgoingCallProcessor> outgoingCallProcessors = Maps.newHashMap();
Ben Giladd17443c2014-01-06 11:04:15 -080065
Santos Cordon8e8b8d22013-12-19 14:14:05 -080066 /**
Ben Gilad8bdaa462014-02-05 12:53:19 -080067 * Attempts to place an outgoing call to the specified handle.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080068 *
Ben Gilad0407fb22014-01-09 16:18:41 -080069 * @param handle The handle to dial.
70 * @param contactInfo Information about the entity being called.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080071 * @param context The application context.
72 */
Ben Giladd17443c2014-01-06 11:04:15 -080073 void placeOutgoingCall(String handle, ContactInfo contactInfo, Context context) {
Evan Charlton0958f532014-01-10 16:58:02 -080074 ThreadUtil.checkOnMainThread();
Ben Giladb59769e2014-01-16 11:41:10 -080075
76 // TODO(gilad): Consider creating the call object even earlier, e.g. in CallsManager.
77 Call call = new Call(handle, contactInfo);
78 boolean bailout = false;
79 if (isNullOrEmpty(mCallServices)) {
80 mCallServiceFinder.initiateLookup(context);
81 bailout = true;
82 }
83 if (isNullOrEmpty(mSelectors)) {
84 mSelectorFinder.initiateLookup(context);
85 bailout = true;
86 }
87
88 if (bailout) {
89 // Unable to process the call without either call service, selectors, or both.
90 // Store the call for deferred processing and bail out.
Ben Gilad0bf5b912014-01-28 17:55:57 -080091 mNewOutgoingCalls.add(call);
Ben Giladb59769e2014-01-16 11:41:10 -080092 return;
93 }
94
Ben Gilad0bf5b912014-01-28 17:55:57 -080095 processNewOutgoingCall(call);
Ben Gilad0407fb22014-01-09 16:18:41 -080096 }
Ben Gilad9f2bed32013-12-12 17:43:26 -080097
Ben Gilad0407fb22014-01-09 16:18:41 -080098 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -080099 * Persists the specified set of call services and attempts to place any pending outgoing
100 * calls. Intended to be invoked by {@link CallServiceFinder} exclusively.
Ben Gilad0407fb22014-01-09 16:18:41 -0800101 *
Ben Gilad03292d42014-01-16 15:06:16 -0800102 * @param callServices The potentially-partial set of call services. Partial since the lookup
103 * process is time-boxed, such that some providers/call-services may be slow to respond and
104 * hence effectively omitted from the specified list.
Ben Gilad0407fb22014-01-09 16:18:41 -0800105 */
Ben Gilad03292d42014-01-16 15:06:16 -0800106 void setCallServices(Set<ICallService> callServices) {
Evan Charlton0958f532014-01-10 16:58:02 -0800107 ThreadUtil.checkOnMainThread();
Ben Gilad0407fb22014-01-09 16:18:41 -0800108
Ben Giladb59769e2014-01-16 11:41:10 -0800109 mCallServices = callServices;
Ben Gilad0bf5b912014-01-28 17:55:57 -0800110 processNewOutgoingCalls();
Ben Giladb59769e2014-01-16 11:41:10 -0800111 }
112
113 /**
114 * Persists the specified list of selectors and attempts to connect any pending outgoing
Ben Gilad0bf5b912014-01-28 17:55:57 -0800115 * calls. Intended to be invoked by {@link CallServiceSelectorFinder} exclusively.
Ben Giladb59769e2014-01-16 11:41:10 -0800116 *
Ben Gilad03292d42014-01-16 15:06:16 -0800117 * @param selectors The potentially-partial set of selectors. Partial since the lookup
118 * procedure is time-boxed such that some selectors may be slow to respond and hence
119 * effectively omitted from the specified set.
Ben Giladb59769e2014-01-16 11:41:10 -0800120 */
Ben Gilad03292d42014-01-16 15:06:16 -0800121 void setSelectors(Set<ICallServiceSelector> selectors) {
Ben Giladb59769e2014-01-16 11:41:10 -0800122 ThreadUtil.checkOnMainThread();
123
Ben Gilad134cf092014-01-16 18:26:12 -0800124 // TODO(gilad): Add logic to include the built-in selectors (e.g. for dealing with
125 // emergency calls) and order the entire set prior to the assignment below. If the
126 // built-in selectors can be implemented in a manner that does not require binding,
127 // that's probably preferred. May want to use a LinkedHashSet for the sorted set.
Ben Giladb59769e2014-01-16 11:41:10 -0800128 mSelectors = selectors;
Ben Gilad0bf5b912014-01-28 17:55:57 -0800129 processNewOutgoingCalls();
Ben Giladb59769e2014-01-16 11:41:10 -0800130 }
131
132 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -0800133 * Handles the case where an outgoing call has been successfully placed,
134 * see {@link OutgoingCallProcessor}.
Ben Giladb59769e2014-01-16 11:41:10 -0800135 */
Ben Gilad0bf5b912014-01-28 17:55:57 -0800136 void handleSuccessfulOutgoingCall(Call call) {
137 // TODO(gilad): More here.
138
139 // Process additional (new) calls, if any.
140 processNewOutgoingCalls();
141 }
142
143 /**
144 * Handles the case where an outgoing call could not be proceed by any of the
145 * selector/call-service implementations, see {@link OutgoingCallProcessor}.
146 */
147 void handleFailedOutgoingCall(Call call) {
148 // TODO(gilad): More here.
149
150 // Process additional (new) calls, if any.
151 processNewOutgoingCalls();
152 }
153
154 /**
155 * Attempts to process the next new outgoing calls that have not yet been expired.
156 */
157 private void processNewOutgoingCalls() {
158 if (isNullOrEmpty(mCallServices) || isNullOrEmpty(mSelectors)) {
159 // At least one call service and one selector are required to process outgoing calls.
160 return;
161 }
162
163 if (!mNewOutgoingCalls.isEmpty()) {
164 Call call = mNewOutgoingCalls.iterator().next();
165 mNewOutgoingCalls.remove(call);
166 mPendingOutgoingCalls.add(call);
167
168 // Specifically only attempt to place one call at a time such that call services
169 // can be freed from needing to deal with concurrent requests.
170 processNewOutgoingCall(call);
Ben Gilad0407fb22014-01-09 16:18:41 -0800171 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800172 }
173
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800174 /**
Ben Giladb59769e2014-01-16 11:41:10 -0800175 * Attempts to place the specified call.
176 *
Ben Gilad0bf5b912014-01-28 17:55:57 -0800177 * @param call The call to place.
Ben Giladb59769e2014-01-16 11:41:10 -0800178 */
Ben Gilad0bf5b912014-01-28 17:55:57 -0800179 private void processNewOutgoingCall(Call call) {
Ben Giladb59769e2014-01-16 11:41:10 -0800180
Ben Gilad0bf5b912014-01-28 17:55:57 -0800181 Preconditions.checkNotNull(mCallServices);
182 Preconditions.checkNotNull(mSelectors);
Ben Giladb59769e2014-01-16 11:41:10 -0800183
Ben Gilad0bf5b912014-01-28 17:55:57 -0800184 // Convert to (duplicate-free) list to aid index-based iteration, see the comment under
185 // setSelectors regarding using LinkedHashSet instead.
186 List<ICallServiceSelector> selectors = Lists.newArrayList();
187 selectors.addAll(mSelectors);
188
189 // Create the processor for this (outgoing) call and store it in a map such that call
190 // attempts can be aborted etc.
191 // TODO(gilad): Consider passing mSelector as an immutable set.
192 OutgoingCallProcessor processor =
193 new OutgoingCallProcessor(call, mCallServices, selectors, this);
194
195 outgoingCallProcessors.put(call, processor);
196 processor.process();
Ben Giladb59769e2014-01-16 11:41:10 -0800197 }
198
199 /**
200 * Determines whether or not the specified collection is either null or empty.
201 *
202 * @param collection Either null or the collection object to be evaluated.
203 * @return True if the collection is null or empty.
204 */
205 @SuppressWarnings("rawtypes")
206 private boolean isNullOrEmpty(Collection collection) {
207 return collection == null || collection.isEmpty();
208 }
209
210 /**
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800211 * Places an outgoing call to the handle passed in. Given a list of {@link ICallServices},
212 * select one and place a call to the handle.
213 * TODO(santoscordon): How does the CallService selection process work?
Ben Giladd17443c2014-01-06 11:04:15 -0800214 * TODO(gilad): Wire this logic from CallServiceFinder.updateSwitchboard.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800215 *
216 * @param handle The handle to dial.
217 * @param contactInfo Information about the entity being called.
218 * @param callServices The list of available {@link ICallService}s.
219 */
Ben Giladd17443c2014-01-06 11:04:15 -0800220// private void placeOutgoingCallInternal(
221// String handle,
222// ContactInfo contactInfo,
223// List<ICallService> callServices) throws CallServiceUnavailableException {
224//
225// Log.i(TAG, "Placing and outgoing call.");
226//
227// if (callServices.isEmpty()) {
228// // No call services, bail out.
229// // TODO(contacts-team): Add logging?
230// // TODO(santoscordon): Does this actually go anywhere considering this method is now
231// // asynchronous?
232// throw new CallServiceUnavailableException("No CallService found.");
233// }
234//
235// List<ICallService> compatibleCallServices = Lists.newArrayList();
236// for (ICallService service : callServices) {
237// // TODO(santoscordon): This code needs to be updated to an asynchronous response
238// // callback from isCompatibleWith().
239// /* if (service.isCompatibleWith(handle)) {
240// // NOTE(android-contacts): If we end up taking the liberty to issue
241// // calls not using the explicit user input (in case one is provided)
242// // and instead pull an alternative method of communication from the
243// // specified user-info object, it may be desirable to give precedence
244// // to services that can in fact respect the user's intent.
245// compatibleCallServices.add(service);
246// }
247// */
248// }
249//
250// if (compatibleCallServices.isEmpty()) {
251// // None of the available call services is suitable for making this call.
252// // TODO(contacts-team): Same here re logging.
253// throw new CallServiceUnavailableException("No compatible CallService found.");
254// }
255//
256// // NOTE(android-team): At this point we can also prompt the user for
257// // preference, i.e. instead of the logic just below.
258// if (compatibleCallServices.size() > 1) {
259// compatibleCallServices = sort(compatibleCallServices);
260// }
261// for (ICallService service : compatibleCallServices) {
262// try {
263// service.call(handle);
264// return;
265// } catch (RemoteException e) {
266// // TODO(santoscordon): Need some proxy for ICallService so that we don't have to
267// // avoid RemoteExceptionHandling everywhere. Take a look at how InputMethodService
268// // handles this.
269// }
270// // catch (OutgoingCallException ignored) {
271// // TODO(santoscordon): Figure out how OutgoingCallException falls into this. Should
272// // RemoteExceptions also be converted to OutgoingCallExceptions thrown by call()?
273// }
274// }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800275}