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; |
| 21 | import com.google.common.collect.Sets; |
| 22 | |
| 23 | import android.os.RemoteException; |
| 24 | import android.telecomm.CallInfo; |
| 25 | import android.telecomm.ICallService; |
| 26 | import android.telecomm.ICallServiceSelector; |
| 27 | |
| 28 | import java.util.Iterator; |
| 29 | import java.util.List; |
| 30 | import java.util.Map; |
| 31 | import java.util.Set; |
| 32 | |
| 33 | /** |
| 34 | * Utility class to place a call using the specified set of call-services and ordered selectors. |
| 35 | * Iterates through the selectors and gets a sorted list of supported call service IDs for each |
| 36 | * selector. Upon receiving each sorted list (one list per selector), each of the corresponding |
| 37 | * call services is then attempted until either the outgoing call is placed, the attempted call |
| 38 | * is aborted (by the switchboard), or the list is exhausted -- whichever occurs first. |
| 39 | * |
| 40 | * Except for the abort case, all other scenarios should terminate with the switchboard notified. |
| 41 | */ |
| 42 | final class OutgoingCallProcessor { |
| 43 | |
| 44 | /** |
| 45 | * The (singleton) Telecomm switchboard instance. |
| 46 | */ |
| 47 | private final Switchboard mSwitchboard; |
| 48 | |
| 49 | /** |
| 50 | * The outgoing call this processor is tasked with placing. |
| 51 | */ |
| 52 | private final Call mCall; |
| 53 | |
| 54 | /** |
| 55 | * The (read-only) object derived from mCall above to pass through outside of the Telecomm |
| 56 | * package. |
| 57 | */ |
| 58 | private final CallInfo mCallInfo; |
| 59 | |
| 60 | /** |
| 61 | * The set of currently-available call-service IDs. |
| 62 | */ |
| 63 | private final Set<String> mCallServiceIds = Sets.newHashSet(); |
| 64 | |
| 65 | /** |
| 66 | * The map of currently-available call-service implementations keyed by call-service ID. |
| 67 | */ |
| 68 | private final Map<String, ICallService> mCallServicesById = Maps.newHashMap(); |
| 69 | |
| 70 | /** |
| 71 | * The set of currently-available call-service selector implementations. |
| 72 | */ |
| 73 | private final List<ICallServiceSelector> mSelectors; |
| 74 | |
| 75 | /** |
| 76 | * The iterator over the currently-selected ordered list of call-service IDs. |
| 77 | */ |
| 78 | private Iterator<String> mCallServiceIdIterator; |
| 79 | |
| 80 | private Iterator<ICallServiceSelector> mSelectorIterator; |
| 81 | |
| 82 | private boolean mIsAborted = false; |
| 83 | |
| 84 | /** |
| 85 | * Persists the specified parameters and iterates through the prioritized list of selectors |
| 86 | * passing to each selector (read-only versions of) the call object and all available call- |
| 87 | * service IDs. Stops once a matching selector is found. Calls with no matching selectors |
| 88 | * will eventually be killed by the cleanup/monitor switchboard handler, which will in turn |
| 89 | * call the abort method of this processor. |
| 90 | * |
| 91 | * @param call The call to place. |
| 92 | * @param callServices The available call-service implementations. |
| 93 | * @param selectors The available call-service selector implementations. |
| 94 | * @param switchboard The switchboard for this processor to work against. |
| 95 | */ |
| 96 | OutgoingCallProcessor( |
| 97 | Call call, |
| 98 | Set<ICallService> callServices, |
| 99 | List<ICallServiceSelector> selectors, |
| 100 | Switchboard switchboard) { |
| 101 | |
| 102 | Preconditions.checkNotNull(callServices); |
| 103 | Preconditions.checkNotNull(selectors); |
| 104 | |
| 105 | mCall = call; |
| 106 | mCallInfo = new CallInfo(call.getId(), call.getHandle()); |
| 107 | mSelectors = selectors; |
| 108 | mSwitchboard = switchboard; |
| 109 | |
| 110 | // Populate the list and map of call-service IDs. The list is needed since |
| 111 | // it's being passed down to selectors. |
| 112 | for (ICallService callService : callServices) { |
| 113 | // TOOD(gilad): Implement callService.getId() and use that instead. |
| 114 | String id = "xyz"; |
| 115 | mCallServiceIds.add(id); |
| 116 | mCallServicesById.put(id, callService); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | /** |
| 121 | * Initiates the attempt to place the call. No-op beyond the first invocation. |
| 122 | */ |
| 123 | void process() { |
| 124 | if (mSelectors.isEmpty() || mCallServiceIds.isEmpty()) { |
| 125 | // TODO(gilad): Consider adding a failure message/type to differentiate the various |
| 126 | // cases, or potentially throw an exception in this case. |
| 127 | mSwitchboard.handleFailedOutgoingCall(mCall); |
| 128 | } else if (mSelectorIterator == null) { |
| 129 | mSelectorIterator = mSelectors.iterator(); |
| 130 | attemptNextSelector(); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | /** |
| 135 | * Aborts the attempt to place the relevant call. Intended to be invoked by |
| 136 | * switchboard. |
| 137 | */ |
| 138 | void abort() { |
| 139 | mIsAborted = true; |
| 140 | } |
| 141 | |
| 142 | /** |
| 143 | * Attempts to place the call using the next selector, no-op if no other selectors |
| 144 | * are available. |
| 145 | */ |
| 146 | private void attemptNextSelector() { |
| 147 | if (mIsAborted) { |
| 148 | return; |
| 149 | } |
| 150 | |
| 151 | if (mSelectorIterator.hasNext()) { |
| 152 | ICallServiceSelector selector = mSelectorIterator.next(); |
| 153 | |
| 154 | // TODO(gilad): Refactor to pass (CallInfo, List<String>, response) passing |
| 155 | // mCallInfo as the 1st and mCallServiceIds (or an immutable version thereof) |
| 156 | // as the 2nd parameter. |
| 157 | // selector.select(handle, callServiceBinders, response); |
| 158 | |
| 159 | // TODO(gilad): Get the list of call-service IDs (asynchronically), store it |
| 160 | // (in setSelectedCallServiceIds), and then invoke attemptNextCallService. |
| 161 | |
| 162 | // NOTE(gilad): Currently operating under the assumption that we'll have one timeout |
| 163 | // per (outgoing) call attempt. If we (also) like to timeout individual selectors |
| 164 | // and/or call services, the code here will need to be refactored (quite a bit) to |
| 165 | // support that. |
| 166 | |
| 167 | } else { |
| 168 | mSwitchboard.handleFailedOutgoingCall(mCall); |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | /** |
| 173 | * Persists the ordered-list of call service IDs as selected by the current selector and |
| 174 | * starts iterating through the corresponding call services in the continuing attempt to |
| 175 | * place the call. |
| 176 | * TODO(gilad): Get this to be invoked upon selector.select responses. |
| 177 | * |
| 178 | * @selectedCallServiceIds The (ordered) list of call service IDs. |
| 179 | */ |
| 180 | private void setSelectedCallServiceIds(List<String> selectedCallServiceIds) { |
| 181 | if (selectedCallServiceIds == null || selectedCallServiceIds.isEmpty()) { |
| 182 | attemptNextSelector(); |
| 183 | } else if (mCallServiceIdIterator == null) { |
| 184 | mCallServiceIdIterator = selectedCallServiceIds.iterator(); |
| 185 | attemptNextCallService(); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * TODO(gilad): Add comment. |
| 191 | */ |
| 192 | private void attemptNextCallService() { |
| 193 | if (mIsAborted) { |
| 194 | return; |
| 195 | } |
| 196 | |
| 197 | if (mCallServiceIdIterator.hasNext()) { |
| 198 | String id = mCallServiceIdIterator.next(); |
| 199 | ICallService callService = mCallServicesById.get(id); |
| 200 | if (callService != null) { |
| 201 | try { |
| 202 | // TODO(gilad): Refactor to pass a CallInfo object instead. |
| 203 | callService.call(mCallInfo); |
| 204 | } catch (RemoteException e) { |
| 205 | // TODO(gilad): Log etc. |
| 206 | attemptNextCallService(); |
| 207 | } |
| 208 | } |
| 209 | } else { |
| 210 | mCallServiceIdIterator = null; |
| 211 | attemptNextSelector(); |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | /** |
| 216 | * Handles the successful outgoing-call case. |
| 217 | */ |
| 218 | private void handleSuccessfulOutgoingCall() { |
| 219 | // TODO(gilad): More here? |
| 220 | |
| 221 | abort(); // Shouldn't be necessary but better safe than sorry. |
| 222 | mSwitchboard.handleSuccessfulOutgoingCall(mCall); |
| 223 | } |
| 224 | |
| 225 | /** |
| 226 | * Handles the failed outgoing-call case. |
| 227 | */ |
| 228 | private void handleFailedOutgoingCall() { |
| 229 | // TODO(gilad): Implement. |
| 230 | attemptNextCallService(); |
| 231 | } |
| 232 | } |