blob: fa5ee14c638fa23f8f3c21ca88e17b2240f96aea [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
Santos Cordon8e8b8d22013-12-19 14:14:05 -080050 /**
Ben Giladd17443c2014-01-06 11:04:15 -080051 * Helper class to register/unregister call-service providers.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080052 */
Ben Giladd17443c2014-01-06 11:04:15 -080053 private class ProviderRegistrar {
54
Santos Cordon8e8b8d22013-12-19 14:14:05 -080055 /**
Ben Giladd17443c2014-01-06 11:04:15 -080056 * The name of the call-service provider that is expected to register with this finder.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080057 */
Ben Giladd17443c2014-01-06 11:04:15 -080058 private ComponentName mProviderName;
59
60 /**
61 * A unique identifier for a given lookup cycle, see nextLookupId.
62 * TODO(gilad): Potentially unnecessary, consider removing.
63 */
64 int mLookupId;
65
66 /**
67 * Persists the specified parameters.
68 *
69 * @param providerName The component name of the relevant provider.
70 * @param lookupId The lookup-cycle ID.
71 */
72 ProviderRegistrar(ComponentName providerName, int lookupId) {
73 this.mProviderName = providerName;
74 this.mLookupId = lookupId;
75 }
76
77 ComponentName getProviderName() {
78 return mProviderName;
79 }
80
81 /**
82 * Registers the specified call-service provider.
83 *
84 * @param provider The provider object to register.
85 */
86 void register(ICallServiceProvider provider) {
87 registerProvider(mLookupId, mProviderName, provider);
88 }
89
90 /** Unregisters this provider. */
91 void unregister() {
92 unregisterProvider(mProviderName);
93 }
94 }
95
96 /**
97 * Wrapper around ICallServiceProvider, mostly used for binding etc.
98 *
99 * TODO(gilad): Consider making this wrapper unnecessary.
100 */
101 private class ProviderWrapper {
102
103 /**
104 * Persists the specified parameters and attempts to bind the specified provider.
105 *
106 * TODO(gilad): Consider embedding ProviderRegistrar into this class and do away
107 * with the former, or vice versa.
108 *
109 * @param context The relevant application context.
110 * @param registrar The registrar with which to register and unregister this provider.
111 */
112 ProviderWrapper(Context context, final ProviderRegistrar registrar) {
Evan Charlton0958f532014-01-10 16:58:02 -0800113 ComponentName name = registrar.getProviderName();
114 Preconditions.checkNotNull(name);
115 Preconditions.checkNotNull(context);
Ben Giladd17443c2014-01-06 11:04:15 -0800116
Evan Charlton0958f532014-01-10 16:58:02 -0800117 Intent serviceIntent = new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME).setComponent(name);
118 Log.i(TAG, "Binding to ICallServiceProvider through " + serviceIntent);
Ben Giladd17443c2014-01-06 11:04:15 -0800119
Evan Charlton0958f532014-01-10 16:58:02 -0800120 // Connection object for the service binding.
121 ServiceConnection connection = new ServiceConnection() {
122 @Override
123 public void onServiceConnected(ComponentName className, IBinder service) {
124 registrar.register(ICallServiceProvider.Stub.asInterface(service));
125 }
Ben Giladd17443c2014-01-06 11:04:15 -0800126
Evan Charlton0958f532014-01-10 16:58:02 -0800127 @Override
128 public void onServiceDisconnected(ComponentName className) {
129 registrar.unregister();
130 }
131 };
Ben Giladd17443c2014-01-06 11:04:15 -0800132
Evan Charlton0958f532014-01-10 16:58:02 -0800133 if (!context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
134 // TODO(santoscordon): Handle error.
135 }
Ben Giladd17443c2014-01-06 11:04:15 -0800136 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800137 }
138
Ben Giladd17443c2014-01-06 11:04:15 -0800139 private static final String TAG = CallServiceFinder.class.getSimpleName();
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800140
141 /**
Evan Charlton0958f532014-01-10 16:58:02 -0800142 * The longest period in milliseconds each lookup cycle is allowed to span over, see
143 * {@link #mLookupTerminator}.
Ben Giladd17443c2014-01-06 11:04:15 -0800144 * TODO(gilad): Likely requires tuning.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800145 */
Evan Charlton0958f532014-01-10 16:58:02 -0800146 private static final int LOOKUP_TIMEOUT_MS = 100;
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800147
Ben Giladd17443c2014-01-06 11:04:15 -0800148 /**
149 * Used to retrieve all known ICallServiceProvider implementations from the framework.
150 * TODO(gilad): Move to a more logical place for this to be shared.
151 */
152 static final String CALL_SERVICE_PROVIDER_CLASS_NAME = ICallServiceProvider.class.getName();
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800153
Ben Gilad0407fb22014-01-09 16:18:41 -0800154 private final Switchboard mSwitchboard;
155
Ben Giladd17443c2014-01-06 11:04:15 -0800156 /**
157 * Determines whether or not a lookup cycle is already running.
158 */
159 private boolean mIsLookupInProgress = false;
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800160
Ben Giladd17443c2014-01-06 11:04:15 -0800161 /**
162 * Used to generate unique lookup-cycle identifiers. Incremented upon initiateLookup calls.
163 */
164 private int mNextLookupId = 0;
165
166 /**
167 * The set of bound call-service providers. Only populated via initiateLookup scenarios.
168 * Providers should only be removed upon unbinding.
169 */
170 private Set<ICallServiceProvider> mProviderRegistry = Sets.newHashSet();
171
172 /**
173 * Stores the names of the providers to bind to in one lookup cycle. The set size represents
174 * the number of call-service providers this finder expects to hear back from upon initiating
175 * call-service lookups, see initiateLookup. Whenever all providers respond before the lookup
176 * timeout occurs, the complete set of (available) call services is passed to the switchboard
177 * for further processing of outgoing calls etc. When the timeout occurs before all responds
178 * are received, the partial (potentially empty) set gets passed (to the switchboard) instead.
179 * Note that cached providers do not require finding and hence are excluded from this count.
180 * Also noteworthy is that providers are dynamically removed from this set as they register.
181 */
182 private Set<ComponentName> mUnregisteredProviders;
183
184 /**
185 * Used to interrupt lookup cycles that didn't terminate naturally within the allowed
186 * period, see LOOKUP_TIMEOUT.
187 */
Evan Charlton0958f532014-01-10 16:58:02 -0800188 private final Runnable mLookupTerminator = new Runnable() {
189 @Override
190 public void run() {
191 terminateLookup();
192 }
193 };
194
195 /** Used to run code (e.g. messages, Runnables) on the main (UI) thread. */
196 private final Handler mHandler = new Handler(Looper.getMainLooper());
Ben Giladd17443c2014-01-06 11:04:15 -0800197
198 /**
Ben Gilad0407fb22014-01-09 16:18:41 -0800199 * Persists the specified parameters.
200 *
201 * @param switchboard The switchboard for this finer to work against.
202 */
203 CallServiceFinder(Switchboard switchboard) {
204 mSwitchboard = switchboard;
205 }
206
207 /**
Evan Charlton0958f532014-01-10 16:58:02 -0800208 * Initiates a lookup cycle for call-service providers. Must be called from the UI thread.
Ben Giladd17443c2014-01-06 11:04:15 -0800209 * TODO(gilad): Expand this comment to describe the lookup flow in more detail.
210 *
211 * @param context The relevant application context.
212 */
Evan Charlton0958f532014-01-10 16:58:02 -0800213 void initiateLookup(Context context) {
214 ThreadUtil.checkOnMainThread();
Ben Giladd17443c2014-01-06 11:04:15 -0800215 if (mIsLookupInProgress) {
216 // At most one active lookup is allowed at any given time, bail out.
217 return;
218 }
219
220 List<ComponentName> providerNames = getProviderNames(context);
221 if (providerNames.isEmpty()) {
222 Log.i(TAG, "No ICallServiceProvider implementations found.");
223 updateSwitchboard();
224 return;
225 }
226
227 mIsLookupInProgress = true;
228 mUnregisteredProviders = Sets.newHashSet();
229
230 int lookupId = mNextLookupId++;
231 for (ComponentName name : providerNames) {
232 if (!mProviderRegistry.contains(name)) {
233 // The provider is either not yet registered or has been unregistered
234 // due to unbinding etc.
235 ProviderRegistrar registrar = new ProviderRegistrar(name, lookupId);
236 new ProviderWrapper(context, registrar);
237 mUnregisteredProviders.add(name);
238 }
239 }
240
241 int providerCount = providerNames.size();
242 int unregisteredProviderCount = mUnregisteredProviders.size();
243
244 Log.i(TAG, "Found " + providerCount + " implementations for ICallServiceProvider, "
245 + unregisteredProviderCount + " of which are not currently registered.");
246
247 if (unregisteredProviderCount == 0) {
248 // All known (provider) implementations are already registered, pass control
249 // back to the switchboard.
250 updateSwitchboard();
251 } else {
Evan Charlton0958f532014-01-10 16:58:02 -0800252 // Schedule a lookup terminator to run after LOOKUP_TIMEOUT_MS milliseconds.
253 mHandler.removeCallbacks(mLookupTerminator);
254 mHandler.postDelayed(mLookupTerminator, LOOKUP_TIMEOUT_MS);
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800255 }
256 }
257
258 /**
Ben Giladd17443c2014-01-06 11:04:15 -0800259 * Returns the all-inclusive list of call-service-provider names.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800260 *
Ben Giladd17443c2014-01-06 11:04:15 -0800261 * @param context The relevant/application context to query against.
262 * @return The list containing the (component) names of all known ICallServiceProvider
263 * implementations or the empty list upon no available providers.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800264 */
Ben Giladd17443c2014-01-06 11:04:15 -0800265 private List<ComponentName> getProviderNames(Context context) {
266 // The list of provider names to return to the caller, may be populated below.
267 List<ComponentName> providerNames = Lists.newArrayList();
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800268
269 PackageManager packageManager = context.getPackageManager();
Ben Giladd17443c2014-01-06 11:04:15 -0800270 Intent intent = new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME);
271 for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
272 ServiceInfo serviceInfo = entry.serviceInfo;
273 if (serviceInfo != null) {
274 // The entry resolves to a proper service, add it to the list of provider names.
275 providerNames.add(
276 new ComponentName(serviceInfo.packageName, serviceInfo.name));
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800277 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800278 }
279
Ben Giladd17443c2014-01-06 11:04:15 -0800280 return providerNames;
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800281 }
282
283 /**
Santos Cordoncb83fb62014-01-06 10:57:57 -0800284 * Queries the supplied provider asynchronously for its CallServices and passes the list through
285 * to {@link #registerCallServices} which will relinquish control back to switchboard.
Ben Giladd17443c2014-01-06 11:04:15 -0800286 *
287 * @param lookupId The lookup-cycle ID.
288 * @param providerName The component name of the relevant provider.
289 * @param provider The provider object to register.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800290 */
Ben Giladd17443c2014-01-06 11:04:15 -0800291 private void registerProvider(
Santos Cordoncb83fb62014-01-06 10:57:57 -0800292 final int lookupId,
293 final ComponentName providerName,
294 final ICallServiceProvider provider) {
Ben Giladd17443c2014-01-06 11:04:15 -0800295
Santos Cordoncb83fb62014-01-06 10:57:57 -0800296 // Query the provider for {@link ICallService} implementations.
297 try {
298 provider.lookupCallServices(new ICallServiceLookupResponse.Stub() {
299 @Override
300 public void onResult(List<IBinder> binderList) {
301 List<ICallService> callServices = Lists.newArrayList();
302 for (IBinder binder : binderList) {
303 callServices.add(ICallService.Stub.asInterface(binder));
304 }
305 registerCallServices(lookupId, providerName, provider, callServices);
306 }
307 });
308 } catch (RemoteException e) {
309 Log.e(TAG, "Could not retrieve call services from: " + providerName);
310 }
311 }
312
313 /**
314 * Registers the {@link CallService}s for the specified provider and performs the necessary
315 * bookkeeping to potentially return control to the switchboard before the timeout for the
316 * current lookup cycle.
317 * TODO(santoscordon): Consider replacing this method's use of synchronized with a Handler
318 * queue.
319 *
320 * @param lookupId The lookup-cycle ID.
321 * @param providerName The component name of the relevant provider.
322 * @param provider The provider associated with callServices.
323 * @param callServices The {@link CallService}s to register.
324 */
325 synchronized private void registerCallServices(
326 int lookupId,
327 ComponentName providerName,
328 ICallServiceProvider provider,
329 List<ICallService> callServices) {
330
331 // TODO(santoscordon): When saving the call services into this class, also add code to
332 // unregister (remove) the call services upon disconnect. Potenially use RemoteCallbackList.
333
334 if (mUnregisteredProviders.remove(providerName)) {
335 mProviderRegistry.add(provider);
336 if (mUnregisteredProviders.size() < 1) {
337 terminateLookup(); // No other providers to wait for.
338 }
339 } else {
340 Log.i(TAG, "Received multiple lists of call services in lookup " + lookupId +
341 " from " + providerName);
342 }
Ben Giladd17443c2014-01-06 11:04:15 -0800343 }
344
345 /**
346 * Unregisters the specified provider.
347 *
348 * @param providerName The component name of the relevant provider.
349 */
350 private void unregisterProvider(ComponentName providerName) {
351 mProviderRegistry.remove(providerName);
352 }
353
354 /**
355 * Timeouts the current lookup cycle, see LookupTerminator.
356 */
357 private void terminateLookup() {
Evan Charlton0958f532014-01-10 16:58:02 -0800358 mHandler.removeCallbacks(mLookupTerminator);
Ben Giladd17443c2014-01-06 11:04:15 -0800359
360 updateSwitchboard();
361 mIsLookupInProgress = false;
362 }
363
364 /**
365 * Updates the switchboard passing the relevant call services (as opposed
366 * to call-service providers).
367 */
368 private void updateSwitchboard() {
Evan Charlton0958f532014-01-10 16:58:02 -0800369 ThreadUtil.checkOnMainThread();
370 // TODO(gilad): More here.
371 mSwitchboard.setCallServices(null);
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800372 }
373}