blob: 20c7ee87e417a3d15fd1b3389a1c1dd8490f2071 [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 Cordon8e8b8d22013-12-19 14:14:05 -080027import android.telecomm.ICallService;
28import android.telecomm.ICallServiceProvider;
29import android.util.Log;
30
Ben Giladd17443c2014-01-06 11:04:15 -080031import com.google.common.base.Preconditions;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080032import com.google.common.collect.Lists;
Ben Giladd17443c2014-01-06 11:04:15 -080033import com.google.common.collect.Sets;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080034
35import java.util.List;
Ben Giladd17443c2014-01-06 11:04:15 -080036import java.util.Set;
37import java.util.Timer;
38import java.util.TimerTask;
Santos Cordon8e8b8d22013-12-19 14:14:05 -080039
40/**
41 * Finds {@link ICallService} and {@link ICallServiceProvider} implementations on the device.
42 * Uses binder APIs to find ICallServiceProviders and calls method on ICallServiceProvider to
43 * find ICallService implementations.
44 * TODO(santoscordon): Add performance timing to async calls.
45 */
46final class CallServiceFinder {
Ben Giladd17443c2014-01-06 11:04:15 -080047
Santos Cordon8e8b8d22013-12-19 14:14:05 -080048 /**
Ben Giladd17443c2014-01-06 11:04:15 -080049 * Helper class to register/unregister call-service providers.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080050 */
Ben Giladd17443c2014-01-06 11:04:15 -080051 private class ProviderRegistrar {
52
Santos Cordon8e8b8d22013-12-19 14:14:05 -080053 /**
Ben Giladd17443c2014-01-06 11:04:15 -080054 * The name of the call-service provider that is expected to register with this finder.
Santos Cordon8e8b8d22013-12-19 14:14:05 -080055 */
Ben Giladd17443c2014-01-06 11:04:15 -080056 private ComponentName mProviderName;
57
58 /**
59 * A unique identifier for a given lookup cycle, see nextLookupId.
60 * TODO(gilad): Potentially unnecessary, consider removing.
61 */
62 int mLookupId;
63
64 /**
65 * Persists the specified parameters.
66 *
67 * @param providerName The component name of the relevant provider.
68 * @param lookupId The lookup-cycle ID.
69 */
70 ProviderRegistrar(ComponentName providerName, int lookupId) {
71 this.mProviderName = providerName;
72 this.mLookupId = lookupId;
73 }
74
75 ComponentName getProviderName() {
76 return mProviderName;
77 }
78
79 /**
80 * Registers the specified call-service provider.
81 *
82 * @param provider The provider object to register.
83 */
84 void register(ICallServiceProvider provider) {
85 registerProvider(mLookupId, mProviderName, provider);
86 }
87
88 /** Unregisters this provider. */
89 void unregister() {
90 unregisterProvider(mProviderName);
91 }
92 }
93
94 /**
95 * Wrapper around ICallServiceProvider, mostly used for binding etc.
96 *
97 * TODO(gilad): Consider making this wrapper unnecessary.
98 */
99 private class ProviderWrapper {
100
101 /**
102 * Persists the specified parameters and attempts to bind the specified provider.
103 *
104 * TODO(gilad): Consider embedding ProviderRegistrar into this class and do away
105 * with the former, or vice versa.
106 *
107 * @param context The relevant application context.
108 * @param registrar The registrar with which to register and unregister this provider.
109 */
110 ProviderWrapper(Context context, final ProviderRegistrar registrar) {
111 ComponentName name = registrar.getProviderName();
112 Preconditions.checkNotNull(name);
113 Preconditions.checkNotNull(context);
114
115 Intent serviceIntent = new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME).setComponent(name);
116 Log.i(TAG, "Binding to ICallServiceProvider through " + serviceIntent);
117
118 // Connection object for the service binding.
119 ServiceConnection connection = new ServiceConnection() {
120 @Override
121 public void onServiceConnected(ComponentName className, IBinder service) {
122 registrar.register(ICallServiceProvider.Stub.asInterface(service));
123 }
124
125 @Override
126 public void onServiceDisconnected(ComponentName className) {
127 registrar.unregister();
128 }
129 };
130
131 if (!context.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
132 // TODO(santoscordon): Handle error.
133 }
134 }
135 }
136
137 /**
138 * A timer task to ensure each lookup cycle is time-bound, see LOOKUP_TIMEOUT.
139 */
140 private class LookupTerminator extends TimerTask {
141 @Override
142 public void run() {
143 terminateLookup();
144 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800145 }
146
147 /** Used to identify log entries by this class */
Ben Giladd17443c2014-01-06 11:04:15 -0800148 private static final String TAG = CallServiceFinder.class.getSimpleName();
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800149
150 /**
Ben Giladd17443c2014-01-06 11:04:15 -0800151 * The longest period in milliseconds each lookup cycle is allowed to span over, see mTimer.
152 * TODO(gilad): Likely requires tuning.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800153 */
Ben Giladd17443c2014-01-06 11:04:15 -0800154 private static final int LOOKUP_TIMEOUT = 100;
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800155
Ben Giladd17443c2014-01-06 11:04:15 -0800156 /**
157 * Used to retrieve all known ICallServiceProvider implementations from the framework.
158 * TODO(gilad): Move to a more logical place for this to be shared.
159 */
160 static final String CALL_SERVICE_PROVIDER_CLASS_NAME = ICallServiceProvider.class.getName();
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800161
Ben Gilad0407fb22014-01-09 16:18:41 -0800162 private final Switchboard mSwitchboard;
163
Ben Giladd17443c2014-01-06 11:04:15 -0800164 /**
165 * Determines whether or not a lookup cycle is already running.
166 */
167 private boolean mIsLookupInProgress = false;
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800168
Ben Giladd17443c2014-01-06 11:04:15 -0800169 /**
170 * Used to generate unique lookup-cycle identifiers. Incremented upon initiateLookup calls.
171 */
172 private int mNextLookupId = 0;
173
174 /**
175 * The set of bound call-service providers. Only populated via initiateLookup scenarios.
176 * Providers should only be removed upon unbinding.
177 */
178 private Set<ICallServiceProvider> mProviderRegistry = Sets.newHashSet();
179
180 /**
181 * Stores the names of the providers to bind to in one lookup cycle. The set size represents
182 * the number of call-service providers this finder expects to hear back from upon initiating
183 * call-service lookups, see initiateLookup. Whenever all providers respond before the lookup
184 * timeout occurs, the complete set of (available) call services is passed to the switchboard
185 * for further processing of outgoing calls etc. When the timeout occurs before all responds
186 * are received, the partial (potentially empty) set gets passed (to the switchboard) instead.
187 * Note that cached providers do not require finding and hence are excluded from this count.
188 * Also noteworthy is that providers are dynamically removed from this set as they register.
189 */
190 private Set<ComponentName> mUnregisteredProviders;
191
192 /**
193 * Used to interrupt lookup cycles that didn't terminate naturally within the allowed
194 * period, see LOOKUP_TIMEOUT.
195 */
196 private Timer mTimer;
197
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 /**
Ben Giladd17443c2014-01-06 11:04:15 -0800208 * Initiates a lookup cycle for call-service providers.
209 * TODO(gilad): Expand this comment to describe the lookup flow in more detail.
210 *
211 * @param context The relevant application context.
212 */
Ben Gilad0407fb22014-01-09 16:18:41 -0800213 synchronized void initiateLookup(Context context) {
Ben Giladd17443c2014-01-06 11:04:15 -0800214 if (mIsLookupInProgress) {
215 // At most one active lookup is allowed at any given time, bail out.
216 return;
217 }
218
219 List<ComponentName> providerNames = getProviderNames(context);
220 if (providerNames.isEmpty()) {
221 Log.i(TAG, "No ICallServiceProvider implementations found.");
222 updateSwitchboard();
223 return;
224 }
225
226 mIsLookupInProgress = true;
227 mUnregisteredProviders = Sets.newHashSet();
228
229 int lookupId = mNextLookupId++;
230 for (ComponentName name : providerNames) {
231 if (!mProviderRegistry.contains(name)) {
232 // The provider is either not yet registered or has been unregistered
233 // due to unbinding etc.
234 ProviderRegistrar registrar = new ProviderRegistrar(name, lookupId);
235 new ProviderWrapper(context, registrar);
236 mUnregisteredProviders.add(name);
237 }
238 }
239
240 int providerCount = providerNames.size();
241 int unregisteredProviderCount = mUnregisteredProviders.size();
242
243 Log.i(TAG, "Found " + providerCount + " implementations for ICallServiceProvider, "
244 + unregisteredProviderCount + " of which are not currently registered.");
245
246 if (unregisteredProviderCount == 0) {
247 // All known (provider) implementations are already registered, pass control
248 // back to the switchboard.
249 updateSwitchboard();
250 } else {
251 // Start the timeout for this lookup cycle.
252 // TODO(gilad): Consider reusing the same timer instead of creating new ones.
253 if (mTimer != null) {
254 // Shouldn't be running but better safe than sorry.
255 mTimer.cancel();
256 }
257 mTimer = new Timer();
258 mTimer.schedule(new LookupTerminator(), LOOKUP_TIMEOUT);
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800259 }
260 }
261
262 /**
Ben Giladd17443c2014-01-06 11:04:15 -0800263 * Returns the all-inclusive list of call-service-provider names.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800264 *
Ben Giladd17443c2014-01-06 11:04:15 -0800265 * @param context The relevant/application context to query against.
266 * @return The list containing the (component) names of all known ICallServiceProvider
267 * implementations or the empty list upon no available providers.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800268 */
Ben Giladd17443c2014-01-06 11:04:15 -0800269 private List<ComponentName> getProviderNames(Context context) {
270 // The list of provider names to return to the caller, may be populated below.
271 List<ComponentName> providerNames = Lists.newArrayList();
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800272
273 PackageManager packageManager = context.getPackageManager();
Ben Giladd17443c2014-01-06 11:04:15 -0800274 Intent intent = new Intent(CALL_SERVICE_PROVIDER_CLASS_NAME);
275 for (ResolveInfo entry : packageManager.queryIntentServices(intent, 0)) {
276 ServiceInfo serviceInfo = entry.serviceInfo;
277 if (serviceInfo != null) {
278 // The entry resolves to a proper service, add it to the list of provider names.
279 providerNames.add(
280 new ComponentName(serviceInfo.packageName, serviceInfo.name));
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800281 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800282 }
283
Ben Giladd17443c2014-01-06 11:04:15 -0800284 return providerNames;
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800285 }
286
287 /**
Ben Giladd17443c2014-01-06 11:04:15 -0800288 * Registers the specified provider and performs the necessary bookkeeping to potentially
289 * return control to the switchboard before the timeout for the current lookup cycle.
290 *
291 * @param lookupId The lookup-cycle ID.
292 * @param providerName The component name of the relevant provider.
293 * @param provider The provider object to register.
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800294 */
Ben Giladd17443c2014-01-06 11:04:15 -0800295 private void registerProvider(
296 int lookupId, ComponentName providerName, ICallServiceProvider provider) {
297
298 if (mUnregisteredProviders.remove(providerName)) {
299 mProviderRegistry.add(provider);
300 if (mUnregisteredProviders.size() < 1) {
301 terminateLookup(); // No other providers to wait for.
302 }
303 }
304 }
305
306 /**
307 * Unregisters the specified provider.
308 *
309 * @param providerName The component name of the relevant provider.
310 */
311 private void unregisterProvider(ComponentName providerName) {
312 mProviderRegistry.remove(providerName);
313 }
314
315 /**
316 * Timeouts the current lookup cycle, see LookupTerminator.
317 */
318 private void terminateLookup() {
319 if (mTimer != null) {
320 mTimer.cancel(); // Terminate the timer thread.
321 }
322
323 updateSwitchboard();
324 mIsLookupInProgress = false;
325 }
326
327 /**
328 * Updates the switchboard passing the relevant call services (as opposed
329 * to call-service providers).
330 */
331 private void updateSwitchboard() {
332 synchronized (mProviderRegistry) {
333 // TODO(gilad): More here.
Ben Gilad0407fb22014-01-09 16:18:41 -0800334 mSwitchboard.setCallServices(null);
Ben Giladd17443c2014-01-06 11:04:15 -0800335 }
Santos Cordon8e8b8d22013-12-19 14:14:05 -0800336 }
337}