blob: 101b940cd54ae59790488d48c60ab005b9900ac9 [file] [log] [blame]
/*
* Copyright (C) 2014 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.telecomm;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.IInterface;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import java.util.Set;
/**
* Abstract class to perform the work of binding and unbinding to the specified service interface.
* Subclasses supply the service intent and component name and this class will invoke protected
* methods when the class is bound, unbound, or upon failure.
*/
abstract class ServiceBinder<ServiceInterface extends IInterface> {
/**
* Callback to notify after a binding succeeds or fails.
*/
interface BindCallback {
public void onSuccess();
public void onFailure();
}
private final class ServiceBinderConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
ThreadUtil.checkOnMainThread();
// Unbind request was queued so unbind immediately.
if (mIsBindingAborted) {
clearAbort();
mContext.unbindService(this);
handleFailedConnection();
return;
}
mServiceConnection = this;
mBinder = binder;
setServiceInterface(binder);
handleSuccessfulConnection();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
mServiceConnection = null;
clearAbort();
handleServiceDisconnected();
}
}
/** The application context. */
private final Context mContext;
/** The intent action to use when binding through {@link Context#bindService}. */
private final String mServiceAction;
/** The component name of the service to bind to. */
private final ComponentName mComponentName;
/** The set of callbacks waiting for notification of the binding's success or failure. */
private final Set<BindCallback> mCallbacks = Sets.newHashSet();
/** Used to bind and unbind from the service. */
private ServiceConnection mServiceConnection;
/** The binder provided by {@link ServiceConnection#onServiceConnected} */
private IBinder mBinder;
/**
* Indicates that an unbind request was made when the service was not yet bound. If the service
* successfully connects when this is true, it should be unbound immediately.
*/
private boolean mIsBindingAborted;
/**
* Persists the specified parameters and initializes the new instance.
*
* @param serviceAction The intent-action used with {@link Context#bindService}.
* @param componentName The component name of the service with which to bind.
*/
protected ServiceBinder(String serviceAction, ComponentName componentName) {
Preconditions.checkState(!Strings.isNullOrEmpty(serviceAction));
Preconditions.checkNotNull(componentName);
mContext = TelecommApp.getInstance();
mServiceAction = serviceAction;
mComponentName = componentName;
}
/**
* Performs an asynchronous bind to the service if not already bound.
*
* @param callback The callback to notify of the binding's success or failure.
* @return The result of {#link Context#bindService} or true if already bound.
*/
final boolean bind(BindCallback callback) {
ThreadUtil.checkOnMainThread();
// Reset any abort request if we're asked to bind again.
clearAbort();
// If we are already waiting on a binding request, simply append to the list of waiting
// callbacks.
if (!mCallbacks.isEmpty()) {
mCallbacks.add(callback);
return true;
}
if (mServiceConnection == null) {
mCallbacks.add(callback);
Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
ServiceConnection connection = new ServiceBinderConnection();
if (!mContext.bindService(serviceIntent, connection, Context.BIND_AUTO_CREATE)) {
handleFailedConnection();
return false;
}
} else {
Preconditions.checkNotNull(mBinder);
handleSuccessfulConnection();
}
return true;
}
/**
* Unbinds from the service if already bound, no-op otherwise.
*/
final void unbind() {
ThreadUtil.checkOnMainThread();
if (mServiceConnection == null) {
// We're not yet bound, so queue up an abort request.
mIsBindingAborted = true;
} else {
mContext.unbindService(mServiceConnection);
mServiceConnection = null;
mBinder = null;
}
}
ComponentName getComponentName() {
return mComponentName;
}
/**
* Notifies all the outstanding callbacks that the service is successfully bound. The list of
* outstanding callbacks is cleared afterwards.
*/
private void handleSuccessfulConnection() {
for (BindCallback callback : mCallbacks) {
callback.onSuccess();
}
mCallbacks.clear();
}
/**
* Notifies all the outstanding callbacks that the service failed to bind. The list of
* outstanding callbacks is cleared afterwards.
*/
private void handleFailedConnection() {
for (BindCallback callback : mCallbacks) {
callback.onFailure();
}
mCallbacks.clear();
}
/**
* Handles a service disconnection.
*/
private void handleServiceDisconnected() {
setServiceInterface(null);
}
private void clearAbort() {
mIsBindingAborted = false;
}
/**
* Sets the service interface after the service is bound or unbound.
*
* @param binder The actual bound service implementation.
*/
protected abstract void setServiceInterface(IBinder binder);
}