blob: 43e2485e0c78f9335a8e3aadcaa9277a7d7a2faf [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;
Ben Giladd17443c2014-01-06 11:04:15 -080026import android.os.IBinder;
Santos Cordoncb83fb62014-01-06 10:57:57 -080027import android.os.Bundle;
28import android.os.RemoteException;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080029import android.telecomm.ICallService;
Santos Cordoncb83fb62014-01-06 10:57:57 -080030import android.telecomm.ICallServiceLookupResponse;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080031import android.telecomm.ICallServiceProvider;
32import android.util.Log;
33
Ben Giladd17443c2014-01-06 11:04:15 -080034import com.google.common.base.Preconditions;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080035import com.google.common.collect.Lists;
Ben Giladd17443c2014-01-06 11:04:15 -080036import com.google.common.collect.Sets;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080037
38import java.util.List;
Ben Giladd17443c2014-01-06 11:04:15 -080039import java.util.Set;
40import java.util.Timer;
41import java.util.TimerTask;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080042
43/**
44 * Finds {@link ICallService} and {@link ICallServiceProvider} implementations on the device.
45 * Uses binder APIs to find ICallServiceProviders and calls method on ICallServiceProvider to
46 * find ICallService implementations.
47 * TODO(santoscordon): Add performance timing to async calls.
48 */
49final class CallServiceFinder {
Ben Giladd17443c2014-01-06 11:04:15 -080050
Santos Cordon8e8b8d22013-12-19 14:14:05 -080051 /**
Ben Giladd17443c2014-01-06 11:04:15 -080052 * Helper class to register/unregister call-service providers.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080053 */
Ben Giladd17443c2014-01-06 11:04:15 -080054 private class ProviderRegistrar {
55
Santos Cordon8e8b8d22013-12-19 14:14:05 -080056 /**
Ben Giladd17443c2014-01-06 11:04:15 -080057 * The name of the call-service provider that is expected to register with this finder.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080058 */
Ben Giladd17443c2014-01-06 11:04:15 -080059 private ComponentName mProviderName;
60
61 /**
62 * A unique identifier for a given lookup cycle, see nextLookupId.
63 * TODO(gilad): Potentially unnecessary, consider removing.
64 */
65 int mLookupId;
66
67 /**
68 * Persists the specified parameters.
69 *
70 * @param providerName The component name of the relevant provider.
71 * @param lookupId The lookup-cycle ID.
72 */
73 ProviderRegistrar(ComponentName providerName, int lookupId) {
74 this.mProviderName = providerName;
75 this.mLookupId = lookupId;
76 }
77
78 ComponentName getProviderName() {
79 return mProviderName;
80 }
81
82 /**
83 * Registers the specified call-service provider.
84 *
85 * @param provider The provider object to register.
86 */
87 void register(ICallServiceProvider provider) {
88 registerProvider(mLookupId, mProviderName, provider);
89 }
90
91 /** Unregisters this provider. */
92 void unregister() {
93 unregisterProvider(mProviderName);
94 }
95 }
96
97 /**
98 * Wrapper around ICallServiceProvider, mostly used for binding etc.
99 *
100 * TODO(gilad): Consider making this wrapper unnecessary.
101 */
102 private class ProviderWrapper {
103
104 /**
105 * Persists the specified parameters and attempts to bind the specified provider.
106 *
107 * TODO(gilad): Consider embedding ProviderRegistrar into this class and do away
108 * with the former, or vice versa.
109 *
110 * @param context The relevant application context.
111 * @param registrar The registrar with which to register and unregister this provider.
112 */
113 ProviderWrapper(Context context, final ProviderRegistrar registrar) {
114 ComponentName name = registrar.getProviderName();
115 Preconditions.checkNotNull(name);
116 Preconditions.checkNotNull(context);
117
118 Intent serviceIntent = new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME).setComponent(name);
119 Log.i(TAG, "Binding to ICallServiceProvider through " + serviceIntent);
120
121 // Connection object for the service binding.
122 ServiceConnection connection = new ServiceConnection() {
123 @Override
124 public void onServiceConnected(ComponentName className, IBinder service) {
125 registrar.register(ICallServiceProvider.Stub.asInterface(service));
126 }
127
128 @Override
129 public void onServiceDisconnected(ComponentName className) {
130 registrar.unregister();
131 }
132 };
133
134 if (!context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
135 // TODO(santoscordon): Handle error.
136 }
137 }
138 }
139
140 /**
141 * A timer task to ensure each lookup cycle is time-bound, see LOOKUP_TIMEOUT.
142 */
143 private class LookupTerminator extends TimerTask {
144 @Override
145 public void run() {
146 terminateLookup();
147 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800148 }
149
Ben Giladd17443c2014-01-06 11:04:15 -0800150 private static final String TAG = CallServiceFinder.class.getSimpleName();
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800151
152 /**
Ben Giladd17443c2014-01-06 11:04:15 -0800153 * The longest period in milliseconds each lookup cycle is allowed to span over, see mTimer.
154 * TODO(gilad): Likely requires tuning.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800155 */
Ben Giladd17443c2014-01-06 11:04:15 -0800156 private static final int LOOKUP_TIMEOUT = 100;
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800157
Ben Giladd17443c2014-01-06 11:04:15 -0800158 /**
159 * Used to retrieve all known ICallServiceProvider implementations from the framework.
160 * TODO(gilad): Move to a more logical place for this to be shared.
161 */
162 static final String CALL_SERVICE_PROVIDER_CLASS_NAME = ICallServiceProvider.class.getName();
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800163
Ben Gilad0407fb22014-01-09 16:18:41 -0800164 private final Switchboard mSwitchboard;
165
Ben Giladd17443c2014-01-06 11:04:15 -0800166 /**
167 * Determines whether or not a lookup cycle is already running.
168 */
169 private boolean mIsLookupInProgress = false;
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800170
Ben Giladd17443c2014-01-06 11:04:15 -0800171 /**
172 * Used to generate unique lookup-cycle identifiers. Incremented upon initiateLookup calls.
173 */
174 private int mNextLookupId = 0;
175
176 /**
177 * The set of bound call-service providers. Only populated via initiateLookup scenarios.
178 * Providers should only be removed upon unbinding.
179 */
180 private Set<ICallServiceProvider> mProviderRegistry = Sets.newHashSet();
181
182 /**
183 * Stores the names of the providers to bind to in one lookup cycle. The set size represents
184 * the number of call-service providers this finder expects to hear back from upon initiating
185 * call-service lookups, see initiateLookup. Whenever all providers respond before the lookup
186 * timeout occurs, the complete set of (available) call services is passed to the switchboard
187 * for further processing of outgoing calls etc. When the timeout occurs before all responds
188 * are received, the partial (potentially empty) set gets passed (to the switchboard) instead.
189 * Note that cached providers do not require finding and hence are excluded from this count.
190 * Also noteworthy is that providers are dynamically removed from this set as they register.
191 */
192 private Set<ComponentName> mUnregisteredProviders;
193
194 /**
195 * Used to interrupt lookup cycles that didn't terminate naturally within the allowed
196 * period, see LOOKUP_TIMEOUT.
197 */
198 private Timer mTimer;
199
200 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800201 * Persists the specified parameters.
202 *
203 * @param switchboard The switchboard for this finer to work against.
204 */
205 CallServiceFinder(Switchboard switchboard) {
206 mSwitchboard = switchboard;
207 }
208
209 /**
Ben Giladd17443c2014-01-06 11:04:15 -0800210 * Initiates a lookup cycle for call-service providers.
211 * TODO(gilad): Expand this comment to describe the lookup flow in more detail.
212 *
213 * @param context The relevant application context.
214 */
Ben Gilad0407fb22014-01-09 16:18:41 -0800215 synchronized void initiateLookup(Context context) {
Ben Giladd17443c2014-01-06 11:04:15 -0800216 if (mIsLookupInProgress) {
217 // At most one active lookup is allowed at any given time, bail out.
218 return;
219 }
220
221 List<ComponentName> providerNames = getProviderNames(context);
222 if (providerNames.isEmpty()) {
223 Log.i(TAG, "No ICallServiceProvider implementations found.");
224 updateSwitchboard();
225 return;
226 }
227
228 mIsLookupInProgress = true;
229 mUnregisteredProviders = Sets.newHashSet();
230
231 int lookupId = mNextLookupId++;
232 for (ComponentName name : providerNames) {
233 if (!mProviderRegistry.contains(name)) {
234 // The provider is either not yet registered or has been unregistered
235 // due to unbinding etc.
236 ProviderRegistrar registrar = new ProviderRegistrar(name, lookupId);
237 new ProviderWrapper(context, registrar);
238 mUnregisteredProviders.add(name);
239 }
240 }
241
242 int providerCount = providerNames.size();
243 int unregisteredProviderCount = mUnregisteredProviders.size();
244
245 Log.i(TAG, "Found " + providerCount + " implementations for ICallServiceProvider, "
246 + unregisteredProviderCount + " of which are not currently registered.");
247
248 if (unregisteredProviderCount == 0) {
249 // All known (provider) implementations are already registered, pass control
250 // back to the switchboard.
251 updateSwitchboard();
252 } else {
253 // Start the timeout for this lookup cycle.
254 // TODO(gilad): Consider reusing the same timer instead of creating new ones.
255 if (mTimer != null) {
256 // Shouldn't be running but better safe than sorry.
257 mTimer.cancel();
258 }
259 mTimer = new Timer();
260 mTimer.schedule(new LookupTerminator(), LOOKUP_TIMEOUT);
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800261 }
262 }
263
264 /**
Ben Giladd17443c2014-01-06 11:04:15 -0800265 * Returns the all-inclusive list of call-service-provider names.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800266 *
Ben Giladd17443c2014-01-06 11:04:15 -0800267 * @param context The relevant/application context to query against.
268 * @return The list containing the (component) names of all known ICallServiceProvider
269 * implementations or the empty list upon no available providers.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800270 */
Ben Giladd17443c2014-01-06 11:04:15 -0800271 private List<ComponentName> getProviderNames(Context context) {
272 // The list of provider names to return to the caller, may be populated below.
273 List<ComponentName> providerNames = Lists.newArrayList();
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800274
275 PackageManager packageManager = context.getPackageManager();
Ben Giladd17443c2014-01-06 11:04:15 -0800276 Intent intent = new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME);
277 for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
278 ServiceInfo serviceInfo = entry.serviceInfo;
279 if (serviceInfo != null) {
280 // The entry resolves to a proper service, add it to the list of provider names.
281 providerNames.add(
282 new ComponentName(serviceInfo.packageName, serviceInfo.name));
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800283 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800284 }
285
Ben Giladd17443c2014-01-06 11:04:15 -0800286 return providerNames;
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800287 }
288
289 /**
Santos Cordoncb83fb62014-01-06 10:57:57 -0800290 * Queries the supplied provider asynchronously for its CallServices and passes the list through
291 * to {@link #registerCallServices} which will relinquish control back to switchboard.
Ben Giladd17443c2014-01-06 11:04:15 -0800292 *
293 * @param lookupId The lookup-cycle ID.
294 * @param providerName The component name of the relevant provider.
295 * @param provider The provider object to register.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800296 */
Ben Giladd17443c2014-01-06 11:04:15 -0800297 private void registerProvider(
Santos Cordoncb83fb62014-01-06 10:57:57 -0800298 final int lookupId,
299 final ComponentName providerName,
300 final ICallServiceProvider provider) {
Ben Giladd17443c2014-01-06 11:04:15 -0800301
Santos Cordoncb83fb62014-01-06 10:57:57 -0800302 // Query the provider for {@link ICallService} implementations.
303 try {
304 provider.lookupCallServices(new ICallServiceLookupResponse.Stub() {
305 @Override
306 public void onResult(List<IBinder> binderList) {
307 List<ICallService> callServices = Lists.newArrayList();
308 for (IBinder binder : binderList) {
309 callServices.add(ICallService.Stub.asInterface(binder));
310 }
311 registerCallServices(lookupId, providerName, provider, callServices);
312 }
313 });
314 } catch (RemoteException e) {
315 Log.e(TAG, "Could not retrieve call services from: " + providerName);
316 }
317 }
318
319 /**
320 * Registers the {@link CallService}s for the specified provider and performs the necessary
321 * bookkeeping to potentially return control to the switchboard before the timeout for the
322 * current lookup cycle.
323 * TODO(santoscordon): Consider replacing this method's use of synchronized with a Handler
324 * queue.
325 *
326 * @param lookupId The lookup-cycle ID.
327 * @param providerName The component name of the relevant provider.
328 * @param provider The provider associated with callServices.
329 * @param callServices The {@link CallService}s to register.
330 */
331 synchronized private void registerCallServices(
332 int lookupId,
333 ComponentName providerName,
334 ICallServiceProvider provider,
335 List<ICallService> callServices) {
336
337 // TODO(santoscordon): When saving the call services into this class, also add code to
338 // unregister (remove) the call services upon disconnect. Potenially use RemoteCallbackList.
339
340 if (mUnregisteredProviders.remove(providerName)) {
341 mProviderRegistry.add(provider);
342 if (mUnregisteredProviders.size() < 1) {
343 terminateLookup(); // No other providers to wait for.
344 }
345 } else {
346 Log.i(TAG, "Received multiple lists of call services in lookup " + lookupId +
347 " from " + providerName);
348 }
Ben Giladd17443c2014-01-06 11:04:15 -0800349 }
350
351 /**
352 * Unregisters the specified provider.
353 *
354 * @param providerName The component name of the relevant provider.
355 */
356 private void unregisterProvider(ComponentName providerName) {
357 mProviderRegistry.remove(providerName);
358 }
359
360 /**
361 * Timeouts the current lookup cycle, see LookupTerminator.
362 */
363 private void terminateLookup() {
364 if (mTimer != null) {
365 mTimer.cancel(); // Terminate the timer thread.
366 }
367
368 updateSwitchboard();
369 mIsLookupInProgress = false;
370 }
371
372 /**
373 * Updates the switchboard passing the relevant call services (as opposed
374 * to call-service providers).
375 */
376 private void updateSwitchboard() {
377 synchronized (mProviderRegistry) {
378 // TODO(gilad): More here.
Ben Gilad0407fb22014-01-09 16:18:41 -0800379 mSwitchboard.setCallServices(null);
Ben Giladd17443c2014-01-06 11:04:15 -0800380 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800381 }
382}