Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package com.android.telecomm; |
| 18 | |
| 19 | import com.google.common.base.Preconditions; |
| 20 | import com.google.common.collect.Maps; |
Ben Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 21 | import com.google.common.collect.Lists; |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 22 | |
Ben Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 23 | import android.os.Handler; |
| 24 | import android.os.Looper; |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 25 | import android.os.RemoteException; |
| 26 | import android.telecomm.CallInfo; |
| 27 | import android.telecomm.ICallService; |
Ben Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 28 | import android.telecomm.ICallServiceSelectionResponse; |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 29 | import android.telecomm.ICallServiceSelector; |
| 30 | |
| 31 | import java.util.Iterator; |
| 32 | import java.util.List; |
| 33 | import java.util.Map; |
| 34 | import 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 Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 44 | * |
| 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 Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 48 | */ |
| 49 | final 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 Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 68 | * The duplicate-free list of currently-available call-service IDs. |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 69 | */ |
Ben Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 70 | private final List<String> mCallServiceIds = Lists.newArrayList(); |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 71 | |
| 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 Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 82 | /** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */ |
| 83 | private final Handler mHandler = new Handler(Looper.getMainLooper()); |
| 84 | |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 85 | /** |
| 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 Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 112 | ThreadUtil.checkOnMainThread(); |
| 113 | |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 114 | 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 Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 136 | ThreadUtil.checkOnMainThread(); |
| 137 | |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 138 | 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 Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 153 | ThreadUtil.checkOnMainThread(); |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 154 | 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 Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 168 | ICallServiceSelectionResponse.Stub response = createSelectionResponse(); |
| 169 | try { |
| 170 | selector.select(mCallInfo, mCallServiceIds, response); |
| 171 | } catch (RemoteException e) { |
| 172 | attemptNextSelector(); |
| 173 | } |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 174 | |
| 175 | } else { |
| 176 | mSwitchboard.handleFailedOutgoingCall(mCall); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /** |
Ben Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 181 | * @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 Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 200 | * 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 Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 203 | * |
| 204 | * @selectedCallServiceIds The (ordered) list of call service IDs. |
| 205 | */ |
Ben Gilad | d88323b | 2014-01-29 19:03:19 -0800 | [diff] [blame^] | 206 | private void processSelectedCallServiceIds(List<String> selectedCallServiceIds) { |
Ben Gilad | 0bf5b91 | 2014-01-28 17:55:57 -0800 | [diff] [blame] | 207 | 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 | } |