blob: 2208f411847b133b74f2da6b4329134263dca22f [file] [log] [blame]
/*
* Copyright (C) 2022 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.adservices;
import static com.android.adservices.AdServicesCommon.ACTION_AD_SELECTION_SERVICE;
import static com.android.adservices.AdServicesCommon.ACTION_AD_SERVICES_COMMON_SERVICE;
import static com.android.adservices.AdServicesCommon.ACTION_CUSTOM_AUDIENCE_SERVICE;
import static com.android.adservices.AdServicesCommon.ACTION_MEASUREMENT_SERVICE;
import static com.android.adservices.AdServicesCommon.ACTION_TOPICS_SERVICE;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.IBinder;
import com.android.internal.annotations.GuardedBy;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* Service binder that connects to a service in the APK.
*
* TODO: Make it robust. Currently this class ignores edge cases.
* TODO: Clean up the log
*
* @hide
*/
class AndroidServiceBinder<T> extends ServiceBinder<T> {
// TODO: Revisit it.
private static final int BIND_FLAGS = Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT;
// TODO(b/218519915): have a better timeout handling.
private static final int BINDER_CONNECTION_TIMEOUT_MS = 5_000;
private final String mServiceIntentAction;
private final Function<IBinder, T> mBinderConverter;
private final Context mContext;
// A CountDownloadLatch which will be opened when the connection is established or any error
// occurs.
private CountDownLatch mConnectionCountDownLatch;
private final Object mLock = new Object();
@GuardedBy("mLock")
private T mService;
@GuardedBy("mLock")
private ServiceConnection mServiceConnection;
protected AndroidServiceBinder(Context context, String serviceIntentAction,
Function<IBinder, T> converter) {
mServiceIntentAction = serviceIntentAction;
mContext = context;
mBinderConverter = converter;
}
public T getService() {
synchronized (mLock) {
// If we already have a service, just return it.
if (mService != null) {
// Note there's a chance the service dies right after we return here,
// but we can't avoid that.
return mService;
}
// If there's no pending bindService(), we need to start one.
if (mServiceConnection == null) {
// There's no other pending connection, creating one.
ComponentName componentName = getServiceComponentName();
if (componentName == null) {
LogUtil.e("Failed to find AdServices service");
return null;
}
final Intent intent = new Intent().setComponent(componentName);
LogUtil.d("bindService: " + mServiceIntentAction);
// This latch will open when the connection is established or any error occurs.
mConnectionCountDownLatch = new CountDownLatch(1);
mServiceConnection = new AdServicesServiceConnection();
// We use Runnable::run so that the callback is called on a binder thread.
// Otherwise we'd use the main thread, which could cause a deadlock.
final boolean success =
mContext.bindService(intent, BIND_FLAGS, Runnable::run, mServiceConnection);
if (!success) {
LogUtil.e("Failed to bindService: " + intent);
mServiceConnection = null;
return null;
} else {
LogUtil.d("bindService() succeeded...");
}
} else {
LogUtil.d("bindService() already pending...");
}
}
// Then wait for connection result.
// Note: We must not hold the lock while waiting for the connection since the
// onServiceConnected callback also needs to acquire the lock. This would cause a deadlock.
try {
// TODO(b/218519915): Better timeout handling
mConnectionCountDownLatch.await(BINDER_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new RuntimeException("Thread interrupted"); // TODO Handle it better.
}
synchronized (mLock) {
if (mService == null) {
throw new RuntimeException("Failed to connect to the service");
}
return mService;
}
}
// A class to handle the connection to the AdService Services.
private class AdServicesServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtil.d("onServiceConnected " + mServiceIntentAction);
synchronized (mLock) {
mService = mBinderConverter.apply(service);
}
// Connection is established, open the latch.
mConnectionCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtil.d("onServiceDisconnected " + mServiceIntentAction);
synchronized (mLock) {
mService = null;
}
mConnectionCountDownLatch.countDown();
}
@Override
public void onBindingDied(ComponentName name) {
LogUtil.d("onBindingDied " + mServiceIntentAction);
synchronized (mLock) {
mService = null;
}
mConnectionCountDownLatch.countDown();
}
@Override
public void onNullBinding(ComponentName name) {
LogUtil.e("onNullBinding shouldn't happen: " + mServiceIntentAction);
synchronized (mLock) {
mService = null;
}
mConnectionCountDownLatch.countDown();
}
}
@Nullable
private ComponentName getServiceComponentName() {
if (!mServiceIntentAction.equals(ACTION_TOPICS_SERVICE)
&& !mServiceIntentAction.equals(ACTION_MEASUREMENT_SERVICE)
&& !mServiceIntentAction.equals(ACTION_CUSTOM_AUDIENCE_SERVICE)
&& !mServiceIntentAction.equals(ACTION_AD_SELECTION_SERVICE)
&& !mServiceIntentAction.equals(ACTION_AD_SERVICES_COMMON_SERVICE)) {
LogUtil.e("Bad service intent action: " + mServiceIntentAction);
return null;
}
final Intent intent = new Intent(mServiceIntentAction);
final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
PackageManager.GET_SERVICES);
if (resolveInfo == null) {
LogUtil.e("Failed to find resolveInfo for adServices service");
return null;
}
final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
if (serviceInfo == null) {
LogUtil.e("Failed to find serviceInfo for adServices service");
return null;
}
return new ComponentName(serviceInfo.packageName, serviceInfo.name);
}
@Override
public void unbindFromService() {
synchronized (mLock) {
if (mService == null || mServiceConnection == null) {
return; // Nothing to release.
}
LogUtil.d("unbinding...");
mContext.unbindService(mServiceConnection);
mServiceConnection = null;
mService = null;
}
}
}