blob: a1658e67c2179bbf7878fb8a889831125ab98c53 [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;
25import android.os.RemoteException;
26import android.telecomm.ICallService;
Ben Giladb59769e2014-01-16 11:41:10 -080027import android.telecomm.ICallServiceSelector;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080028import android.util.Log;
29
Ben Gilad9f2bed32013-12-12 17:43:26 -080030import com.android.telecomm.exceptions.CallServiceUnavailableException;
31import com.android.telecomm.exceptions.OutgoingCallException;
32
Ben Gilad0bf5b912014-01-28 17:55:57 -080033import java.util.ArrayList;
Ben Giladb59769e2014-01-16 11:41:10 -080034import java.util.Collection;
Ben Gilad9f2bed32013-12-12 17:43:26 -080035import java.util.List;
Ben Gilad0bf5b912014-01-28 17:55:57 -080036import java.util.Map;
Ben Gilad0407fb22014-01-09 16:18:41 -080037import java.util.Set;
Ben Gilad9f2bed32013-12-12 17:43:26 -080038
Santos Cordon8e8b8d22013-12-19 14:14:05 -080039/**
40 * Switchboard is responsible for (1) selecting the {@link ICallService} through which to make
41 * outgoing calls and (2) switching active calls between transports (each ICallService is
42 * considered a different transport type).
43 * TODO(santoscordon): Need to add comments on the switchboard optimizer once that it is place.
Ben Gilad0407fb22014-01-09 16:18:41 -080044 * TODO(gilad): Add a monitor thread to wake up periodically and check for stale pending calls
Ben Gilad0bf5b912014-01-28 17:55:57 -080045 * that may need to be terminated, see mNewOutgoingCalls and mPendingOutgoingCalls.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080046 */
Ben Giladd17443c2014-01-06 11:04:15 -080047final class Switchboard {
Ben Gilad2495d572014-01-09 17:26:19 -080048
Santos Cordon8e8b8d22013-12-19 14:14:05 -080049 private static final String TAG = Switchboard.class.getSimpleName();
Ben Gilad9f2bed32013-12-12 17:43:26 -080050
Ben Giladb59769e2014-01-16 11:41:10 -080051 private CallServiceFinder mCallServiceFinder = new CallServiceFinder(this);
52
53 private CallServiceSelectorFinder mSelectorFinder = new CallServiceSelectorFinder(this);
54
Ben Gilad03292d42014-01-16 15:06:16 -080055 /**
56 * The set of currently available call-service implementations, see {@link CallServiceFinder}.
57 * TODO(gilad): Null out once the active-call count goes to zero.
58 */
59 private Set<ICallService> mCallServices;
Ben Giladb59769e2014-01-16 11:41:10 -080060
Ben Gilad03292d42014-01-16 15:06:16 -080061 /**
62 * The set of currently available call-service-selector implementations,
63 * see {@link CallServiceSelectorFinder}.
64 * TODO(gilad): Null out once the active-call count goes to zero.
65 */
66 private Set<ICallServiceSelector> mSelectors;
Ben Gilad0407fb22014-01-09 16:18:41 -080067
Ben Gilad0bf5b912014-01-28 17:55:57 -080068 private Set<Call> mNewOutgoingCalls = Sets.newLinkedHashSet();
69
70 private Set<Call> mPendingOutgoingCalls = Sets.newLinkedHashSet();
71
72 private Map<Call, OutgoingCallProcessor> outgoingCallProcessors = Maps.newHashMap();
Ben Giladd17443c2014-01-06 11:04:15 -080073
Santos Cordon8e8b8d22013-12-19 14:14:05 -080074 /**
75 * Places an outgoing call to the handle passed in. Method asynchronously collects
76 * {@link ICallService} implementations and passes them along with the handle and contactInfo
77 * to {@link #placeOutgoingCallInternal} to actually place the call.
Ben Giladb59769e2014-01-16 11:41:10 -080078 * TODO(gilad): Update.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080079 *
Ben Gilad0407fb22014-01-09 16:18:41 -080080 * @param handle The handle to dial.
81 * @param contactInfo Information about the entity being called.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080082 * @param context The application context.
83 */
Ben Giladd17443c2014-01-06 11:04:15 -080084 void placeOutgoingCall(String handle, ContactInfo contactInfo, Context context) {
Evan Charlton0958f532014-01-10 16:58:02 -080085 ThreadUtil.checkOnMainThread();
Ben Giladb59769e2014-01-16 11:41:10 -080086
87 // TODO(gilad): Consider creating the call object even earlier, e.g. in CallsManager.
88 Call call = new Call(handle, contactInfo);
89 boolean bailout = false;
90 if (isNullOrEmpty(mCallServices)) {
91 mCallServiceFinder.initiateLookup(context);
92 bailout = true;
93 }
94 if (isNullOrEmpty(mSelectors)) {
95 mSelectorFinder.initiateLookup(context);
96 bailout = true;
97 }
98
99 if (bailout) {
100 // Unable to process the call without either call service, selectors, or both.
101 // Store the call for deferred processing and bail out.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800102 mNewOutgoingCalls.add(call);
Ben Giladb59769e2014-01-16 11:41:10 -0800103 return;
104 }
105
Ben Gilad0bf5b912014-01-28 17:55:57 -0800106 processNewOutgoingCall(call);
Ben Gilad0407fb22014-01-09 16:18:41 -0800107 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800108
Ben Gilad0407fb22014-01-09 16:18:41 -0800109 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -0800110 * Persists the specified set of call services and attempts to place any pending outgoing
111 * calls. Intended to be invoked by {@link CallServiceFinder} exclusively.
Ben Gilad0407fb22014-01-09 16:18:41 -0800112 *
Ben Gilad03292d42014-01-16 15:06:16 -0800113 * @param callServices The potentially-partial set of call services. Partial since the lookup
114 * process is time-boxed, such that some providers/call-services may be slow to respond and
115 * hence effectively omitted from the specified list.
Ben Gilad0407fb22014-01-09 16:18:41 -0800116 */
Ben Gilad03292d42014-01-16 15:06:16 -0800117 void setCallServices(Set<ICallService> callServices) {
Evan Charlton0958f532014-01-10 16:58:02 -0800118 ThreadUtil.checkOnMainThread();
Ben Gilad0407fb22014-01-09 16:18:41 -0800119
Ben Giladb59769e2014-01-16 11:41:10 -0800120 mCallServices = callServices;
Ben Gilad0bf5b912014-01-28 17:55:57 -0800121 processNewOutgoingCalls();
Ben Giladb59769e2014-01-16 11:41:10 -0800122 }
123
124 /**
125 * Persists the specified list of selectors and attempts to connect any pending outgoing
Ben Gilad0bf5b912014-01-28 17:55:57 -0800126 * calls. Intended to be invoked by {@link CallServiceSelectorFinder} exclusively.
Ben Giladb59769e2014-01-16 11:41:10 -0800127 *
Ben Gilad03292d42014-01-16 15:06:16 -0800128 * @param selectors The potentially-partial set of selectors. Partial since the lookup
129 * procedure is time-boxed such that some selectors may be slow to respond and hence
130 * effectively omitted from the specified set.
Ben Giladb59769e2014-01-16 11:41:10 -0800131 */
Ben Gilad03292d42014-01-16 15:06:16 -0800132 void setSelectors(Set<ICallServiceSelector> selectors) {
Ben Giladb59769e2014-01-16 11:41:10 -0800133 ThreadUtil.checkOnMainThread();
134
Ben Gilad134cf092014-01-16 18:26:12 -0800135 // TODO(gilad): Add logic to include the built-in selectors (e.g. for dealing with
136 // emergency calls) and order the entire set prior to the assignment below. If the
137 // built-in selectors can be implemented in a manner that does not require binding,
138 // that's probably preferred. May want to use a LinkedHashSet for the sorted set.
Ben Giladb59769e2014-01-16 11:41:10 -0800139 mSelectors = selectors;
Ben Gilad0bf5b912014-01-28 17:55:57 -0800140 processNewOutgoingCalls();
Ben Giladb59769e2014-01-16 11:41:10 -0800141 }
142
143 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -0800144 * Handles the case where an outgoing call has been successfully placed,
145 * see {@link OutgoingCallProcessor}.
Ben Giladb59769e2014-01-16 11:41:10 -0800146 */
Ben Gilad0bf5b912014-01-28 17:55:57 -0800147 void handleSuccessfulOutgoingCall(Call call) {
148 // TODO(gilad): More here.
149
150 // Process additional (new) calls, if any.
151 processNewOutgoingCalls();
152 }
153
154 /**
155 * Handles the case where an outgoing call could not be proceed by any of the
156 * selector/call-service implementations, see {@link OutgoingCallProcessor}.
157 */
158 void handleFailedOutgoingCall(Call call) {
159 // TODO(gilad): More here.
160
161 // Process additional (new) calls, if any.
162 processNewOutgoingCalls();
163 }
164
165 /**
166 * Attempts to process the next new outgoing calls that have not yet been expired.
167 */
168 private void processNewOutgoingCalls() {
169 if (isNullOrEmpty(mCallServices) || isNullOrEmpty(mSelectors)) {
170 // At least one call service and one selector are required to process outgoing calls.
171 return;
172 }
173
174 if (!mNewOutgoingCalls.isEmpty()) {
175 Call call = mNewOutgoingCalls.iterator().next();
176 mNewOutgoingCalls.remove(call);
177 mPendingOutgoingCalls.add(call);
178
179 // Specifically only attempt to place one call at a time such that call services
180 // can be freed from needing to deal with concurrent requests.
181 processNewOutgoingCall(call);
Ben Gilad0407fb22014-01-09 16:18:41 -0800182 }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800183 }
184
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800185 /**
Ben Giladb59769e2014-01-16 11:41:10 -0800186 * Attempts to place the specified call.
187 *
Ben Gilad0bf5b912014-01-28 17:55:57 -0800188 * @param call The call to place.
Ben Giladb59769e2014-01-16 11:41:10 -0800189 */
Ben Gilad0bf5b912014-01-28 17:55:57 -0800190 private void processNewOutgoingCall(Call call) {
Ben Giladb59769e2014-01-16 11:41:10 -0800191
Ben Gilad0bf5b912014-01-28 17:55:57 -0800192 Preconditions.checkNotNull(mCallServices);
193 Preconditions.checkNotNull(mSelectors);
Ben Giladb59769e2014-01-16 11:41:10 -0800194
Ben Gilad0bf5b912014-01-28 17:55:57 -0800195 // Convert to (duplicate-free) list to aid index-based iteration, see the comment under
196 // setSelectors regarding using LinkedHashSet instead.
197 List<ICallServiceSelector> selectors = Lists.newArrayList();
198 selectors.addAll(mSelectors);
199
200 // Create the processor for this (outgoing) call and store it in a map such that call
201 // attempts can be aborted etc.
202 // TODO(gilad): Consider passing mSelector as an immutable set.
203 OutgoingCallProcessor processor =
204 new OutgoingCallProcessor(call, mCallServices, selectors, this);
205
206 outgoingCallProcessors.put(call, processor);
207 processor.process();
Ben Giladb59769e2014-01-16 11:41:10 -0800208 }
209
210 /**
211 * Determines whether or not the specified collection is either null or empty.
212 *
213 * @param collection Either null or the collection object to be evaluated.
214 * @return True if the collection is null or empty.
215 */
216 @SuppressWarnings("rawtypes")
217 private boolean isNullOrEmpty(Collection collection) {
218 return collection == null || collection.isEmpty();
219 }
220
221 /**
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800222 * Places an outgoing call to the handle passed in. Given a list of {@link ICallServices},
223 * select one and place a call to the handle.
224 * TODO(santoscordon): How does the CallService selection process work?
Ben Giladd17443c2014-01-06 11:04:15 -0800225 * TODO(gilad): Wire this logic from CallServiceFinder.updateSwitchboard.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800226 *
227 * @param handle The handle to dial.
228 * @param contactInfo Information about the entity being called.
229 * @param callServices The list of available {@link ICallService}s.
230 */
Ben Giladd17443c2014-01-06 11:04:15 -0800231// private void placeOutgoingCallInternal(
232// String handle,
233// ContactInfo contactInfo,
234// List<ICallService> callServices) throws CallServiceUnavailableException {
235//
236// Log.i(TAG, "Placing and outgoing call.");
237//
238// if (callServices.isEmpty()) {
239// // No call services, bail out.
240// // TODO(contacts-team): Add logging?
241// // TODO(santoscordon): Does this actually go anywhere considering this method is now
242// // asynchronous?
243// throw new CallServiceUnavailableException("No CallService found.");
244// }
245//
246// List<ICallService> compatibleCallServices = Lists.newArrayList();
247// for (ICallService service : callServices) {
248// // TODO(santoscordon): This code needs to be updated to an asynchronous response
249// // callback from isCompatibleWith().
250// /* if (service.isCompatibleWith(handle)) {
251// // NOTE(android-contacts): If we end up taking the liberty to issue
252// // calls not using the explicit user input (in case one is provided)
253// // and instead pull an alternative method of communication from the
254// // specified user-info object, it may be desirable to give precedence
255// // to services that can in fact respect the user's intent.
256// compatibleCallServices.add(service);
257// }
258// */
259// }
260//
261// if (compatibleCallServices.isEmpty()) {
262// // None of the available call services is suitable for making this call.
263// // TODO(contacts-team): Same here re logging.
264// throw new CallServiceUnavailableException("No compatible CallService found.");
265// }
266//
267// // NOTE(android-team): At this point we can also prompt the user for
268// // preference, i.e. instead of the logic just below.
269// if (compatibleCallServices.size() > 1) {
270// compatibleCallServices = sort(compatibleCallServices);
271// }
272// for (ICallService service : compatibleCallServices) {
273// try {
274// service.call(handle);
275// return;
276// } catch (RemoteException e) {
277// // TODO(santoscordon): Need some proxy for ICallService so that we don't have to
278// // avoid RemoteExceptionHandling everywhere. Take a look at how InputMethodService
279// // handles this.
280// }
281// // catch (OutgoingCallException ignored) {
282// // TODO(santoscordon): Figure out how OutgoingCallException falls into this. Should
283// // RemoteExceptions also be converted to OutgoingCallExceptions thrown by call()?
284// }
285// }
Ben Gilad9f2bed32013-12-12 17:43:26 -0800286}