| /* |
| * Copyright 2014, The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.telecomm; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Lists; |
| |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.RemoteException; |
| import android.telecomm.CallState; |
| import android.telecomm.CallServiceDescriptor; |
| |
| import com.android.internal.telecomm.ICallServiceSelectionResponse; |
| import com.android.internal.telecomm.ICallServiceSelector; |
| import com.android.telecomm.ServiceBinder.BindCallback; |
| |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Utility class to place a call using the specified set of call-services and ordered selectors. |
| * Iterates through the selectors and gets a sorted list of supported call-service descriptors |
| * for each selector. Upon receiving each sorted list (one list per selector), each of the |
| * corresponding call services is then attempted until either the outgoing call is placed, the |
| * attempted call is aborted (by the switchboard), or the list is exhausted -- whichever occurs |
| * first. |
| * |
| * Except for the abort case, all other scenarios should terminate with the switchboard notified |
| * of the result. |
| * |
| * NOTE(gilad): Currently operating under the assumption that we'll have one timeout per (outgoing) |
| * call attempt. If we (also) like to timeout individual selectors and/or call services, the code |
| * here will need to be re-factored (quite a bit) to support that. |
| */ |
| final class OutgoingCallProcessor { |
| |
| /** |
| * The outgoing call this processor is tasked with placing. |
| */ |
| private final Call mCall; |
| |
| /** |
| * The duplicate-free list of currently-available call-service descriptors. |
| */ |
| private final List<CallServiceDescriptor> mCallServiceDescriptors = Lists.newArrayList(); |
| |
| /** |
| * The map of currently-available call-service implementations keyed by call-service ID. |
| */ |
| private final Map<String, CallServiceWrapper> mCallServicesById = Maps.newHashMap(); |
| |
| /** |
| * The list of currently-available call-service selector implementations. |
| */ |
| private final List<ICallServiceSelector> mSelectors; |
| |
| /** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */ |
| private final Handler mHandler = new Handler(Looper.getMainLooper()); |
| |
| /** Manages all outgoing call processors. */ |
| private final OutgoingCallsManager mOutgoingCallsManager; |
| |
| private final Switchboard mSwitchboard; |
| |
| /** |
| * The iterator over the currently-selected ordered list of call-service descriptors. |
| */ |
| private Iterator<CallServiceDescriptor> mCallServiceDescriptorIterator; |
| |
| private Iterator<ICallServiceSelector> mSelectorIterator; |
| |
| private boolean mIsAborted = false; |
| |
| /** |
| * Persists the specified parameters and iterates through the prioritized list of selectors |
| * passing to each selector (read-only versions of) the call object and all available call- |
| * service descriptors. Stops once a matching selector is found. Calls with no matching |
| * selectors will eventually be killed by the cleanup/monitor switchboard handler, which will |
| * in turn call the abort method of this processor via {@link OutgoingCallsManager}. |
| * |
| * @param call The call to place. |
| * @param callServices The available call-service implementations. |
| * @param selectors The available call-service selector implementations. |
| * @param outgoingCallsManager Manager of all outgoing call processors. |
| * @param switchboard The switchboard. |
| */ |
| OutgoingCallProcessor( |
| Call call, |
| Set<CallServiceWrapper> callServices, |
| List<ICallServiceSelector> selectors, |
| OutgoingCallsManager outgoingCallsManager, |
| Switchboard switchboard) { |
| |
| ThreadUtil.checkOnMainThread(); |
| |
| mCall = call; |
| mSelectors = selectors; |
| mOutgoingCallsManager = outgoingCallsManager; |
| mSwitchboard = switchboard; |
| |
| // Populate the list and map of call-service descriptors. The list is needed since |
| // it's being passed down to selectors. |
| for (CallServiceWrapper callService : callServices) { |
| CallServiceDescriptor descriptor = callService.getDescriptor(); |
| mCallServiceDescriptors.add(descriptor); |
| mCallServicesById.put(descriptor.getCallServiceId(), callService); |
| } |
| } |
| |
| /** |
| * Initiates the attempt to place the call. No-op beyond the first invocation. |
| */ |
| void process() { |
| if (!mIsAborted) { |
| // Only process un-aborted calls. |
| ThreadUtil.checkOnMainThread(); |
| |
| if (mSelectorIterator == null) { |
| mSelectorIterator = mSelectors.iterator(); |
| attemptNextSelector(); |
| } |
| } |
| } |
| |
| /** |
| * Aborts the attempt to place the relevant call. Intended to be invoked by |
| * switchboard through the outgoing-calls manager. |
| */ |
| void abort() { |
| ThreadUtil.checkOnMainThread(); |
| if (!mIsAborted) { |
| mCall.abort(); |
| mIsAborted = true; |
| } |
| } |
| |
| /** |
| * Completes the outgoing call sequence by setting the call service on the call object. This is |
| * invoked when the call service adapter receives positive confirmation that the call service |
| * placed the call. |
| */ |
| void handleSuccessfulCallAttempt() { |
| ThreadUtil.checkOnMainThread(); |
| |
| if (mIsAborted) { |
| // TODO(gilad): Ask the call service to drop the call? |
| return; |
| } |
| |
| mCall.setState(CallState.DIALING); |
| mSwitchboard.handleSuccessfulOutgoingCall(mCall); |
| } |
| |
| /** |
| * Attempts the next call service if the specified call service is the one currently being |
| * attempted. |
| * |
| * @param reason The call-service supplied reason for the failed call attempt. |
| */ |
| void handleFailedCallAttempt(String reason) { |
| if (!mIsAborted) { |
| ThreadUtil.checkOnMainThread(); |
| |
| mCall.clearCallService(); |
| mCall.clearCallServiceSelector(); |
| attemptNextCallService(); |
| } |
| } |
| |
| /** |
| * Attempts to place the call using the next selector, no-op if no other selectors |
| * are available. |
| */ |
| private void attemptNextSelector() { |
| if (mIsAborted) { |
| return; |
| } |
| |
| if (mSelectorIterator.hasNext()) { |
| ICallServiceSelector selector = mSelectorIterator.next(); |
| mCall.setCallServiceSelector(selector); |
| |
| ICallServiceSelectionResponse.Stub response = createSelectionResponse(); |
| try { |
| selector.select(mCall.toCallInfo(), mCallServiceDescriptors, response); |
| } catch (RemoteException e) { |
| attemptNextSelector(); |
| } |
| |
| } else { |
| mOutgoingCallsManager.handleFailedOutgoingCall(mCall); |
| } |
| } |
| |
| /** |
| * @return A new selection-response object that's wired to run on the main (UI) thread. |
| */ |
| private ICallServiceSelectionResponse.Stub createSelectionResponse() { |
| return new ICallServiceSelectionResponse.Stub() { |
| @Override public void setSelectedCallServiceDescriptors( |
| final List<CallServiceDescriptor> selectedCallServiceDescriptors) { |
| |
| Runnable runnable = new Runnable() { |
| @Override public void run() { |
| processSelectedCallServiceDescriptors(selectedCallServiceDescriptors); |
| } |
| }; |
| |
| mHandler.post(runnable); |
| } |
| }; |
| } |
| |
| /** |
| * Persists the ordered-list of call-service descriptor as selected by the current selector and |
| * starts iterating through the corresponding call services continuing the attempt to place the |
| * call. |
| * |
| * @selectedCallServiceDescriptors The (ordered) list of call-service descriptor. |
| */ |
| private void processSelectedCallServiceDescriptors( |
| List<CallServiceDescriptor> selectedCallServiceDescriptors) { |
| |
| if (selectedCallServiceDescriptors == null || selectedCallServiceDescriptors.isEmpty()) { |
| attemptNextSelector(); |
| } else if (mCallServiceDescriptorIterator == null) { |
| mCallServiceDescriptorIterator = selectedCallServiceDescriptors.iterator(); |
| attemptNextCallService(); |
| } |
| } |
| |
| /** |
| * Attempts to place the call using the call service specified by the next call-service |
| * descriptor of mCallServiceDescriptorIterator. If there are no more call services to |
| * attempt, the process continues to the next call-service selector via |
| * {@link #attemptNextSelector}. |
| */ |
| private void attemptNextCallService() { |
| if (mIsAborted) { |
| return; |
| } |
| |
| if (mCallServiceDescriptorIterator != null && mCallServiceDescriptorIterator.hasNext()) { |
| CallServiceDescriptor descriptor = mCallServiceDescriptorIterator.next(); |
| final CallServiceWrapper callService = |
| mCallServicesById.get(descriptor.getCallServiceId()); |
| |
| if (callService == null) { |
| attemptNextCallService(); |
| } else { |
| BindCallback callback = new BindCallback() { |
| @Override public void onSuccess() { |
| callService.call(mCall.toCallInfo()); |
| } |
| @Override public void onFailure() { |
| attemptNextCallService(); |
| } |
| }; |
| |
| mCall.setCallService(callService); |
| |
| // TODO(santoscordon): Consider making bind private to CallServiceWrapper and having |
| // CSWrapper.call() do the bind automatically. |
| callService.bind(callback); |
| } |
| } else { |
| mCallServiceDescriptorIterator = null; |
| attemptNextSelector(); |
| } |
| } |
| } |