blob: 4059ad6a555fdd3bea6aafc02c9c116e07a67c97 [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
19import com.google.common.base.Preconditions;
20import com.google.common.collect.Maps;
Ben Giladd88323b2014-01-29 19:03:19 -080021import com.google.common.collect.Lists;
Ben Gilad0bf5b912014-01-28 17:55:57 -080022
Ben Giladd88323b2014-01-29 19:03:19 -080023import android.os.Handler;
24import android.os.Looper;
Ben Gilad0bf5b912014-01-28 17:55:57 -080025import android.os.RemoteException;
26import android.telecomm.CallInfo;
27import android.telecomm.ICallService;
Ben Giladd88323b2014-01-29 19:03:19 -080028import android.telecomm.ICallServiceSelectionResponse;
Ben Gilad0bf5b912014-01-28 17:55:57 -080029import android.telecomm.ICallServiceSelector;
30
31import 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.
38 * Iterates through the selectors and gets a sorted list of supported call service IDs for each
39 * selector. Upon receiving each sorted list (one list per selector), each of the corresponding
40 * call services is then attempted until either the outgoing call is placed, the attempted call
41 * is aborted (by the switchboard), or the list is exhausted -- whichever occurs first.
42 *
43 * Except for the abort case, all other scenarios should terminate with the switchboard notified.
Ben Giladd88323b2014-01-29 19:03:19 -080044 *
45 * NOTE(gilad): Currently operating under the assumption that we'll have one timeout per (outgoing)
46 * call attempt. If we (also) like to timeout individual selectors and/or call services, the code
47 * here will need to be re-factored (quite a bit) to support that.
Ben Gilad0bf5b912014-01-28 17:55:57 -080048 */
49final class OutgoingCallProcessor {
50
51 /**
52 * The (singleton) Telecomm switchboard instance.
53 */
54 private final Switchboard mSwitchboard;
55
56 /**
57 * The outgoing call this processor is tasked with placing.
58 */
59 private final Call mCall;
60
61 /**
62 * The (read-only) object derived from mCall above to pass through outside of the Telecomm
63 * package.
64 */
65 private final CallInfo mCallInfo;
66
67 /**
Ben Giladd88323b2014-01-29 19:03:19 -080068 * The duplicate-free list of currently-available call-service IDs.
Ben Gilad0bf5b912014-01-28 17:55:57 -080069 */
Ben Giladd88323b2014-01-29 19:03:19 -080070 private final List<String> mCallServiceIds = Lists.newArrayList();
Ben Gilad0bf5b912014-01-28 17:55:57 -080071
72 /**
73 * The map of currently-available call-service implementations keyed by call-service ID.
74 */
75 private final Map<String, ICallService> mCallServicesById = Maps.newHashMap();
76
77 /**
78 * The set of currently-available call-service selector implementations.
79 */
80 private final List<ICallServiceSelector> mSelectors;
81
Ben Giladd88323b2014-01-29 19:03:19 -080082 /** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */
83 private final Handler mHandler = new Handler(Looper.getMainLooper());
84
Ben Gilad0bf5b912014-01-28 17:55:57 -080085 /**
86 * The iterator over the currently-selected ordered list of call-service IDs.
87 */
88 private Iterator<String> mCallServiceIdIterator;
89
90 private Iterator<ICallServiceSelector> mSelectorIterator;
91
92 private boolean mIsAborted = false;
93
94 /**
95 * Persists the specified parameters and iterates through the prioritized list of selectors
96 * passing to each selector (read-only versions of) the call object and all available call-
97 * service IDs. Stops once a matching selector is found. Calls with no matching selectors
98 * will eventually be killed by the cleanup/monitor switchboard handler, which will in turn
99 * call the abort method of this processor.
100 *
101 * @param call The call to place.
102 * @param callServices The available call-service implementations.
103 * @param selectors The available call-service selector implementations.
104 * @param switchboard The switchboard for this processor to work against.
105 */
106 OutgoingCallProcessor(
107 Call call,
108 Set<ICallService> callServices,
109 List<ICallServiceSelector> selectors,
110 Switchboard switchboard) {
111
Ben Giladd88323b2014-01-29 19:03:19 -0800112 ThreadUtil.checkOnMainThread();
113
Ben Gilad0bf5b912014-01-28 17:55:57 -0800114 Preconditions.checkNotNull(callServices);
115 Preconditions.checkNotNull(selectors);
116
117 mCall = call;
118 mCallInfo = new CallInfo(call.getId(), call.getHandle());
119 mSelectors = selectors;
120 mSwitchboard = switchboard;
121
122 // Populate the list and map of call-service IDs. The list is needed since
123 // it's being passed down to selectors.
124 for (ICallService callService : callServices) {
125 // TOOD(gilad): Implement callService.getId() and use that instead.
126 String id = "xyz";
127 mCallServiceIds.add(id);
128 mCallServicesById.put(id, callService);
129 }
130 }
131
132 /**
133 * Initiates the attempt to place the call. No-op beyond the first invocation.
134 */
135 void process() {
Ben Giladd88323b2014-01-29 19:03:19 -0800136 ThreadUtil.checkOnMainThread();
137
Ben Gilad0bf5b912014-01-28 17:55:57 -0800138 if (mSelectors.isEmpty() || mCallServiceIds.isEmpty()) {
139 // TODO(gilad): Consider adding a failure message/type to differentiate the various
140 // cases, or potentially throw an exception in this case.
141 mSwitchboard.handleFailedOutgoingCall(mCall);
142 } else if (mSelectorIterator == null) {
143 mSelectorIterator = mSelectors.iterator();
144 attemptNextSelector();
145 }
146 }
147
148 /**
149 * Aborts the attempt to place the relevant call. Intended to be invoked by
150 * switchboard.
151 */
152 void abort() {
Ben Giladd88323b2014-01-29 19:03:19 -0800153 ThreadUtil.checkOnMainThread();
Ben Gilad0bf5b912014-01-28 17:55:57 -0800154 mIsAborted = true;
155 }
156
157 /**
158 * Attempts to place the call using the next selector, no-op if no other selectors
159 * are available.
160 */
161 private void attemptNextSelector() {
162 if (mIsAborted) {
163 return;
164 }
165
166 if (mSelectorIterator.hasNext()) {
167 ICallServiceSelector selector = mSelectorIterator.next();
Ben Giladd88323b2014-01-29 19:03:19 -0800168 ICallServiceSelectionResponse.Stub response = createSelectionResponse();
169 try {
170 selector.select(mCallInfo, mCallServiceIds, response);
171 } catch (RemoteException e) {
172 attemptNextSelector();
173 }
Ben Gilad0bf5b912014-01-28 17:55:57 -0800174
175 } else {
176 mSwitchboard.handleFailedOutgoingCall(mCall);
177 }
178 }
179
180 /**
Ben Giladd88323b2014-01-29 19:03:19 -0800181 * @return A new selection-response object that's wired to run on the main (UI) thread.
182 */
183 private ICallServiceSelectionResponse.Stub createSelectionResponse() {
184 return new ICallServiceSelectionResponse.Stub() {
185 @Override public void setSelectedCallServiceIds(
186 final List<String> selectedCallServiceIds) {
187
188 Runnable runnable = new Runnable() {
189 @Override public void run() {
190 processSelectedCallServiceIds(selectedCallServiceIds);
191 }
192 };
193
194 mHandler.post(runnable);
195 }
196 };
197 }
198
199 /**
Ben Gilad0bf5b912014-01-28 17:55:57 -0800200 * Persists the ordered-list of call service IDs as selected by the current selector and
201 * starts iterating through the corresponding call services in the continuing attempt to
202 * place the call.
Ben Gilad0bf5b912014-01-28 17:55:57 -0800203 *
204 * @selectedCallServiceIds The (ordered) list of call service IDs.
205 */
Ben Giladd88323b2014-01-29 19:03:19 -0800206 private void processSelectedCallServiceIds(List<String> selectedCallServiceIds) {
Ben Gilad0bf5b912014-01-28 17:55:57 -0800207 if (selectedCallServiceIds == null || selectedCallServiceIds.isEmpty()) {
208 attemptNextSelector();
209 } else if (mCallServiceIdIterator == null) {
210 mCallServiceIdIterator = selectedCallServiceIds.iterator();
211 attemptNextCallService();
212 }
213 }
214
215 /**
216 * TODO(gilad): Add comment.
217 */
218 private void attemptNextCallService() {
219 if (mIsAborted) {
220 return;
221 }
222
223 if (mCallServiceIdIterator.hasNext()) {
224 String id = mCallServiceIdIterator.next();
225 ICallService callService = mCallServicesById.get(id);
226 if (callService != null) {
227 try {
228 // TODO(gilad): Refactor to pass a CallInfo object instead.
229 callService.call(mCallInfo);
230 } catch (RemoteException e) {
231 // TODO(gilad): Log etc.
232 attemptNextCallService();
233 }
234 }
235 } else {
236 mCallServiceIdIterator = null;
237 attemptNextSelector();
238 }
239 }
240
241 /**
242 * Handles the successful outgoing-call case.
243 */
244 private void handleSuccessfulOutgoingCall() {
245 // TODO(gilad): More here?
246
247 abort(); // Shouldn't be necessary but better safe than sorry.
248 mSwitchboard.handleSuccessfulOutgoingCall(mCall);
249 }
250
251 /**
252 * Handles the failed outgoing-call case.
253 */
254 private void handleFailedOutgoingCall() {
255 // TODO(gilad): Implement.
256 attemptNextCallService();
257 }
258}