blob: 7fc187ba05c90e8e639639eb8f1b6159ad15465e [file] [log] [blame]
Santos Cordon8e8b8d22013-12-19 14:14:05 -08001/*
2 * Copyright (C) 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
17package com.android.telecomm;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
Ben Giladd17443c2014-01-06 11:04:15 -080022import android.content.ServiceConnection;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080023import android.content.pm.PackageManager;
24import android.content.pm.ResolveInfo;
25import android.content.pm.ServiceInfo;
Evan Charlton0958f532014-01-10 16:58:02 -080026import android.os.Handler;
Ben Giladd17443c2014-01-06 11:04:15 -080027import android.os.IBinder;
Evan Charlton0958f532014-01-10 16:58:02 -080028import android.os.Looper;
Santos Cordoncb83fb62014-01-06 10:57:57 -080029import android.os.RemoteException;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080030import android.telecomm.ICallService;
Santos Cordoncb83fb62014-01-06 10:57:57 -080031import android.telecomm.ICallServiceLookupResponse;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080032import android.telecomm.ICallServiceProvider;
33import android.util.Log;
34
Ben Giladd17443c2014-01-06 11:04:15 -080035import com.google.common.base.Preconditions;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080036import com.google.common.collect.Lists;
Ben Giladd17443c2014-01-06 11:04:15 -080037import com.google.common.collect.Sets;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080038
39import java.util.List;
Ben Giladd17443c2014-01-06 11:04:15 -080040import java.util.Set;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080041
42/**
43 * Finds {@link ICallService} and {@link ICallServiceProvider} implementations on the device.
44 * Uses binder APIs to find ICallServiceProviders and calls method on ICallServiceProvider to
45 * find ICallService implementations.
46 * TODO(santoscordon): Add performance timing to async calls.
47 */
48final class CallServiceFinder {
Ben Giladd17443c2014-01-06 11:04:15 -080049
Ben Giladd17443c2014-01-06 11:04:15 -080050 private static final String TAG = CallServiceFinder.class.getSimpleName();
Santos Cordon8e8b8d22013-12-19 14:14:05 -080051
52 /**
Evan Charlton0958f532014-01-10 16:58:02 -080053 * The longest period in milliseconds each lookup cycle is allowed to span over, see
54 * {@link #mLookupTerminator}.
Ben Giladd17443c2014-01-06 11:04:15 -080055 * TODO(gilad): Likely requires tuning.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080056 */
Evan Charlton0958f532014-01-10 16:58:02 -080057 private static final int LOOKUP_TIMEOUT_MS = 100;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080058
Ben Giladd17443c2014-01-06 11:04:15 -080059 /**
60 * Used to retrieve all known ICallServiceProvider implementations from the framework.
61 * TODO(gilad): Move to a more logical place for this to be shared.
62 */
63 static final String CALL_SERVICE_PROVIDER_CLASS_NAME = ICallServiceProvider.class.getName();
Santos Cordon8e8b8d22013-12-19 14:14:05 -080064
Ben Gilad0407fb22014-01-09 16:18:41 -080065 private final Switchboard mSwitchboard;
66
Santos Cordon681663d2014-01-30 04:32:15 -080067 private final OutgoingCallsManager mOutgoingCallsManager;
68
Ben Giladd17443c2014-01-06 11:04:15 -080069 /**
70 * Determines whether or not a lookup cycle is already running.
71 */
72 private boolean mIsLookupInProgress = false;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080073
Ben Giladd17443c2014-01-06 11:04:15 -080074 /**
75 * Used to generate unique lookup-cycle identifiers. Incremented upon initiateLookup calls.
Ben Giladb59769e2014-01-16 11:41:10 -080076 * TODO(gilad): If at all useful, consider porting the cycle ID concept to switchboard and
77 * have it centralized/shared between the two finders.
Ben Giladd17443c2014-01-06 11:04:15 -080078 */
79 private int mNextLookupId = 0;
80
81 /**
Ben Gilad03292d42014-01-16 15:06:16 -080082 * The set of bound call-services. Only populated via initiateLookup scenarios. Entries should
83 * only be removed upon unbinding.
84 * TODO(gilad): Add the necessary logic to keep this set up to date.
85 */
86 private Set<ICallService> mCallServiceRegistry = Sets.newHashSet();
87
88 /**
Ben Giladd17443c2014-01-06 11:04:15 -080089 * The set of bound call-service providers. Only populated via initiateLookup scenarios.
90 * Providers should only be removed upon unbinding.
91 */
92 private Set<ICallServiceProvider> mProviderRegistry = Sets.newHashSet();
93
94 /**
95 * Stores the names of the providers to bind to in one lookup cycle. The set size represents
96 * the number of call-service providers this finder expects to hear back from upon initiating
97 * call-service lookups, see initiateLookup. Whenever all providers respond before the lookup
98 * timeout occurs, the complete set of (available) call services is passed to the switchboard
99 * for further processing of outgoing calls etc. When the timeout occurs before all responds
100 * are received, the partial (potentially empty) set gets passed (to the switchboard) instead.
Ben Giladb59769e2014-01-16 11:41:10 -0800101 * Cached providers do not require finding and hence are excluded from this set. Entries are
102 * removed from this set as providers register.
Ben Giladd17443c2014-01-06 11:04:15 -0800103 */
104 private Set<ComponentName> mUnregisteredProviders;
105
106 /**
107 * Used to interrupt lookup cycles that didn't terminate naturally within the allowed
108 * period, see LOOKUP_TIMEOUT.
109 */
Evan Charlton0958f532014-01-10 16:58:02 -0800110 private final Runnable mLookupTerminator = new Runnable() {
111 @Override
112 public void run() {
113 terminateLookup();
114 }
115 };
116
117 /** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */
118 private final Handler mHandler = new Handler(Looper.getMainLooper());
Ben Giladd17443c2014-01-06 11:04:15 -0800119
120 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800121 * Persists the specified parameters.
122 *
Santos Cordon681663d2014-01-30 04:32:15 -0800123 * @param switchboard The switchboard for this finder to work against.
124 * @param outgoingCallsManager Manager in charge of placing outgoing calls.
Ben Gilad0407fb22014-01-09 16:18:41 -0800125 */
Santos Cordon681663d2014-01-30 04:32:15 -0800126 CallServiceFinder(Switchboard switchboard, OutgoingCallsManager outgoingCallsManager) {
Ben Gilad0407fb22014-01-09 16:18:41 -0800127 mSwitchboard = switchboard;
Santos Cordon681663d2014-01-30 04:32:15 -0800128 mOutgoingCallsManager = outgoingCallsManager;
Ben Gilad0407fb22014-01-09 16:18:41 -0800129 }
130
131 /**
Evan Charlton0958f532014-01-10 16:58:02 -0800132 * Initiates a lookup cycle for call-service providers. Must be called from the UI thread.
Ben Giladd17443c2014-01-06 11:04:15 -0800133 * TODO(gilad): Expand this comment to describe the lookup flow in more detail.
134 *
135 * @param context The relevant application context.
136 */
Evan Charlton0958f532014-01-10 16:58:02 -0800137 void initiateLookup(Context context) {
138 ThreadUtil.checkOnMainThread();
Ben Giladd17443c2014-01-06 11:04:15 -0800139 if (mIsLookupInProgress) {
140 // At most one active lookup is allowed at any given time, bail out.
141 return;
142 }
143
144 List<ComponentName> providerNames = getProviderNames(context);
145 if (providerNames.isEmpty()) {
146 Log.i(TAG, "No ICallServiceProvider implementations found.");
147 updateSwitchboard();
148 return;
149 }
150
151 mIsLookupInProgress = true;
152 mUnregisteredProviders = Sets.newHashSet();
153
154 int lookupId = mNextLookupId++;
155 for (ComponentName name : providerNames) {
156 if (!mProviderRegistry.contains(name)) {
157 // The provider is either not yet registered or has been unregistered
158 // due to unbinding etc.
Ben Gilad80ddb5d2014-01-15 14:48:29 -0800159 bindProvider(name, lookupId, context);
Ben Giladd17443c2014-01-06 11:04:15 -0800160 mUnregisteredProviders.add(name);
161 }
162 }
163
164 int providerCount = providerNames.size();
165 int unregisteredProviderCount = mUnregisteredProviders.size();
166
Ben Giladb59769e2014-01-16 11:41:10 -0800167 Log.i(TAG, "Found " + providerCount + " implementations of ICallServiceProvider, "
Ben Giladd17443c2014-01-06 11:04:15 -0800168 + unregisteredProviderCount + " of which are not currently registered.");
169
170 if (unregisteredProviderCount == 0) {
171 // All known (provider) implementations are already registered, pass control
172 // back to the switchboard.
173 updateSwitchboard();
174 } else {
Evan Charlton0958f532014-01-10 16:58:02 -0800175 // Schedule a lookup terminator to run after LOOKUP_TIMEOUT_MS milliseconds.
176 mHandler.removeCallbacks(mLookupTerminator);
177 mHandler.postDelayed(mLookupTerminator, LOOKUP_TIMEOUT_MS);
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800178 }
179 }
180
181 /**
Ben Giladd17443c2014-01-06 11:04:15 -0800182 * Returns the all-inclusive list of call-service-provider names.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800183 *
Ben Giladd17443c2014-01-06 11:04:15 -0800184 * @param context The relevant/application context to query against.
185 * @return The list containing the (component) names of all known ICallServiceProvider
186 * implementations or the empty list upon no available providers.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800187 */
Ben Giladd17443c2014-01-06 11:04:15 -0800188 private List<ComponentName> getProviderNames(Context context) {
189 // The list of provider names to return to the caller, may be populated below.
190 List<ComponentName> providerNames = Lists.newArrayList();
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800191
192 PackageManager packageManager = context.getPackageManager();
Ben Giladd17443c2014-01-06 11:04:15 -0800193 Intent intent = new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME);
194 for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
195 ServiceInfo serviceInfo = entry.serviceInfo;
196 if (serviceInfo != null) {
197 // The entry resolves to a proper service, add it to the list of provider names.
198 providerNames.add(
199 new ComponentName(serviceInfo.packageName, serviceInfo.name));
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800200 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800201 }
202
Ben Giladd17443c2014-01-06 11:04:15 -0800203 return providerNames;
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800204 }
205
206 /**
Ben Gilad80ddb5d2014-01-15 14:48:29 -0800207 * Attempts to bind the specified provider and have it register upon successful binding. Also
208 * performs the necessary wiring to unregister the provider upon un-binding.
209 *
210 * @param providerName The component name of the relevant provider.
211 * @param lookupId The lookup-cycle ID.
212 * @param context The relevant application context.
213 */
Ben Giladb59769e2014-01-16 11:41:10 -0800214 private void bindProvider(
Ben Gilad80ddb5d2014-01-15 14:48:29 -0800215 final ComponentName providerName, final int lookupId, Context context) {
216
217 Preconditions.checkNotNull(providerName);
218 Preconditions.checkNotNull(context);
219
220 Intent serviceIntent =
221 new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME).setComponent(providerName);
222 Log.i(TAG, "Binding to ICallServiceProvider through " + serviceIntent);
223
224 // Connection object for the service binding.
225 ServiceConnection connection = new ServiceConnection() {
226 @Override
227 public void onServiceConnected(ComponentName className, IBinder service) {
228 ICallServiceProvider provider = ICallServiceProvider.Stub.asInterface(service);
229 registerProvider(lookupId, providerName, provider);
230 }
231
232 @Override
233 public void onServiceDisconnected(ComponentName className) {
234 unregisterProvider(providerName);
235 }
236 };
237
238 if (!context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
239 // TODO(santoscordon): Handle error.
240 }
241 }
242
243 /**
Santos Cordoncb83fb62014-01-06 10:57:57 -0800244 * Queries the supplied provider asynchronously for its CallServices and passes the list through
245 * to {@link #registerCallServices} which will relinquish control back to switchboard.
Ben Giladd17443c2014-01-06 11:04:15 -0800246 *
247 * @param lookupId The lookup-cycle ID.
248 * @param providerName The component name of the relevant provider.
249 * @param provider The provider object to register.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800250 */
Ben Giladd17443c2014-01-06 11:04:15 -0800251 private void registerProvider(
Santos Cordoncb83fb62014-01-06 10:57:57 -0800252 final int lookupId,
253 final ComponentName providerName,
254 final ICallServiceProvider provider) {
Ben Giladd17443c2014-01-06 11:04:15 -0800255
Santos Cordoncb83fb62014-01-06 10:57:57 -0800256 // Query the provider for {@link ICallService} implementations.
257 try {
258 provider.lookupCallServices(new ICallServiceLookupResponse.Stub() {
259 @Override
Evan Charlton18cc42f2014-01-28 14:44:11 -0800260 public void setCallServices(final List<IBinder> binderList) {
261 mHandler.post(new Runnable() {
262 @Override public void run() {
263 Set<ICallService> callServices = Sets.newHashSet();
264 for (IBinder binder : binderList) {
265 callServices.add(ICallService.Stub.asInterface(binder));
266 }
267 registerCallServices(lookupId, providerName, provider, callServices);
268 }
269 });
Santos Cordoncb83fb62014-01-06 10:57:57 -0800270 }
271 });
272 } catch (RemoteException e) {
273 Log.e(TAG, "Could not retrieve call services from: " + providerName);
274 }
275 }
276
277 /**
278 * Registers the {@link CallService}s for the specified provider and performs the necessary
279 * bookkeeping to potentially return control to the switchboard before the timeout for the
280 * current lookup cycle.
Santos Cordoncb83fb62014-01-06 10:57:57 -0800281 *
282 * @param lookupId The lookup-cycle ID.
283 * @param providerName The component name of the relevant provider.
284 * @param provider The provider associated with callServices.
285 * @param callServices The {@link CallService}s to register.
286 */
Evan Charlton18cc42f2014-01-28 14:44:11 -0800287 private void registerCallServices(
Santos Cordoncb83fb62014-01-06 10:57:57 -0800288 int lookupId,
289 ComponentName providerName,
290 ICallServiceProvider provider,
Evan Charlton18cc42f2014-01-28 14:44:11 -0800291 Set<ICallService> callServices) {
292 ThreadUtil.checkOnMainThread();
Santos Cordoncb83fb62014-01-06 10:57:57 -0800293
294 // TODO(santoscordon): When saving the call services into this class, also add code to
Santos Cordon681663d2014-01-30 04:32:15 -0800295 // unregister (remove) the call services upon disconnect. Potentially use
296 // RemoteCallbackList.
Santos Cordoncb83fb62014-01-06 10:57:57 -0800297
298 if (mUnregisteredProviders.remove(providerName)) {
299 mProviderRegistry.add(provider);
Santos Cordon681663d2014-01-30 04:32:15 -0800300 for (ICallService callService : callServices) {
301 try {
302 CallServiceAdapter adapter = new CallServiceAdapter(mOutgoingCallsManager);
303 callService.setCallServiceAdapter(adapter);
304 mCallServiceRegistry.add(callService);
305 } catch (RemoteException e) {
306 Log.e(TAG, "Failed to set call-service adapter.");
307 }
308 }
Ben Gilad03292d42014-01-16 15:06:16 -0800309
310 // TODO(gilad): Introduce a map to retain the association between call services
311 // and the corresponding provider such that mCallServiceRegistry can be updated
312 // upon unregisterProvider calls.
313
Santos Cordoncb83fb62014-01-06 10:57:57 -0800314 if (mUnregisteredProviders.size() < 1) {
315 terminateLookup(); // No other providers to wait for.
316 }
317 } else {
318 Log.i(TAG, "Received multiple lists of call services in lookup " + lookupId +
319 " from " + providerName);
320 }
Ben Giladd17443c2014-01-06 11:04:15 -0800321 }
322
323 /**
324 * Unregisters the specified provider.
325 *
326 * @param providerName The component name of the relevant provider.
327 */
328 private void unregisterProvider(ComponentName providerName) {
Evan Charlton18cc42f2014-01-28 14:44:11 -0800329 ThreadUtil.checkOnMainThread();
Ben Giladd17443c2014-01-06 11:04:15 -0800330 mProviderRegistry.remove(providerName);
331 }
332
333 /**
334 * Timeouts the current lookup cycle, see LookupTerminator.
335 */
336 private void terminateLookup() {
Evan Charlton0958f532014-01-10 16:58:02 -0800337 mHandler.removeCallbacks(mLookupTerminator);
Ben Giladd17443c2014-01-06 11:04:15 -0800338
339 updateSwitchboard();
340 mIsLookupInProgress = false;
341 }
342
343 /**
344 * Updates the switchboard passing the relevant call services (as opposed
345 * to call-service providers).
346 */
347 private void updateSwitchboard() {
Evan Charlton0958f532014-01-10 16:58:02 -0800348 ThreadUtil.checkOnMainThread();
Ben Gilad03292d42014-01-16 15:06:16 -0800349 mSwitchboard.setCallServices(mCallServiceRegistry);
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800350 }
351}