blob: 9292b956fe2fad4007bbaa2be594e7318b7bf2fc [file] [log] [blame]
/*
* Copyright (C) 2021 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 androidx.health.services.client.impl.ipc;
import android.os.IBinder;
import android.os.IInterface;
import android.os.RemoteException;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.RestrictTo.Scope;
import androidx.health.services.client.impl.ipc.internal.BaseQueueOperation;
import androidx.health.services.client.impl.ipc.internal.ConnectionConfiguration;
import androidx.health.services.client.impl.ipc.internal.ConnectionManager;
import androidx.health.services.client.impl.ipc.internal.ExecutionTracker;
import androidx.health.services.client.impl.ipc.internal.ListenerKey;
import androidx.health.services.client.impl.ipc.internal.QueueOperation;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
/**
* Client for establishing connection to a cross process service.
*
* <p>Extend this class to create a new client. Each client should represent one connection to AIDL
* interface. For user instruction see: go/wear-dd-wcs-sdk
*
* @param <S> type of the service interface
* @hide
*/
@RestrictTo(Scope.LIBRARY)
public abstract class Client<S extends IInterface> {
private static final int UNKNOWN_VERSION = -1;
private final ConnectionConfiguration mConnectionConfiguration;
private final ConnectionManager mConnectionManager;
private final ServiceGetter<S> mServiceGetter;
private final RemoteOperation<S, Integer> mRemoteVersionGetter;
private volatile int mCurrentVersion = UNKNOWN_VERSION;
public Client(
ClientConfiguration clientConfiguration,
ConnectionManager connectionManager,
ServiceGetter<S> serviceGetter,
RemoteOperation<S, Integer> remoteVersionGetter) {
QueueOperation versionOperation =
new QueueOperation() {
@Override
public void execute(IBinder binder) throws RemoteException {
mCurrentVersion =
remoteVersionGetter.execute(serviceGetter.getService(binder));
}
@Override
public void setException(Throwable exception) {}
@Override
public QueueOperation trackExecution(ExecutionTracker tracker) {
return this;
}
@Override
public ConnectionConfiguration getConnectionConfiguration() {
return mConnectionConfiguration;
}
};
this.mConnectionConfiguration =
new ConnectionConfiguration(
clientConfiguration.getServicePackageName(),
clientConfiguration.getApiClientName(),
clientConfiguration.getBindAction(),
versionOperation);
this.mConnectionManager = connectionManager;
this.mServiceGetter = serviceGetter;
this.mRemoteVersionGetter = remoteVersionGetter;
}
/**
* Executes given {@code operation} against the service.
*
* @see #execute(RemoteFutureOperation)
*/
protected <R> ListenableFuture<R> execute(RemoteOperation<S, R> operation) {
return execute((service, resultFuture) -> resultFuture.set(operation.execute(service)));
}
/**
* Executes given {@code operation} against the service and the result future.
*
* @param operation operation that will be executed against the service and the result future
* @param <R> type of the result value returned in the future
* @return {@link ListenableFuture} with the result of the operation or an exception if the
* execution fails.
*/
protected <R> ListenableFuture<R> execute(RemoteFutureOperation<S, R> operation) {
SettableFuture<R> settableFuture = SettableFuture.create();
mConnectionManager.scheduleForExecution(createQueueOperation(operation, settableFuture));
return settableFuture;
}
/**
* Executes given {@code operation} against the service and the result future with version
* check.
*
* @param operation operation that will be executed against the service and the result future
* @param <R> type of the result value returned in the future
* @return {@link ListenableFuture} with the result of the operation or an exception if the
* execution fails or if the remote service version is lower than {@code minApiVersion}
*/
protected <R> ListenableFuture<R> executeWithVersionCheck(
RemoteFutureOperation<S, R> operation, int minApiVersion) {
if (mCurrentVersion == UNKNOWN_VERSION) {
SettableFuture<R> settableFuture = SettableFuture.create();
ListenableFuture<Integer> versionFuture = execute(mRemoteVersionGetter);
Futures.addCallback(
versionFuture,
new FutureCallback<Integer>() {
@Override
public void onSuccess(@Nullable Integer remoteVersion) {
mCurrentVersion =
remoteVersion == null ? UNKNOWN_VERSION : remoteVersion;
if (mCurrentVersion < minApiVersion) {
settableFuture.setException(
getApiVersionCheckFailureException(
mCurrentVersion, minApiVersion));
} else {
mConnectionManager.scheduleForExecution(
createQueueOperation(operation, settableFuture));
}
}
@Override
public void onFailure(Throwable throwable) {
settableFuture.setException(throwable);
}
},
MoreExecutors.directExecutor());
return settableFuture;
} else if (mCurrentVersion >= minApiVersion) {
return execute(operation);
} else {
// This empty operation is executed just to connect to the service. If we didn't connect
// it
// could happen that we won't detect change in the API version.
mConnectionManager.scheduleForExecution(
new BaseQueueOperation(mConnectionConfiguration));
return Futures.immediateFailedFuture(
getApiVersionCheckFailureException(mCurrentVersion, minApiVersion));
}
}
/**
* Registers a listener by executing the provided {@link RemoteOperation
* registerListenerOperation}.
*
* <p>The provided {@code registerListenerOperation} will be stored for every unique {@code
* listenerKey} and re-executed when connection is lost.
*
* @param listenerKey key based on which listeners will be distinguished
* @param registerListenerOperation {@link RemoteOperation} to register the listener
* @param <R> return type of {@code registerListenerOperation}
* @return {@link ListenableFuture} with the result of the operation or an exception if the
* execution fails
*/
protected <R> ListenableFuture<R> registerListener(
ListenerKey listenerKey, RemoteOperation<S, R> registerListenerOperation) {
return registerListener(
listenerKey,
(service, resultFuture) ->
resultFuture.set(registerListenerOperation.execute(service)));
}
/**
* Registers a listener by executing the provided {@link RemoteFutureOperation
* registerListenerOperation}.
*
* <p>The provided {@code registerListenerOperation} will be stored for every unique {@code
* listenerKey} and re-executed when connection is lost.
*
* @param listenerKey key based on which listeners will be distinguished
* @param registerListenerOperation {@link RemoteFutureOperation} to register the listener
* @param <R> return type of {@code registerListenerOperation}
* @return {@link ListenableFuture} with the result of the operation or an exception if the
* execution fails
*/
protected <R> ListenableFuture<R> registerListener(
ListenerKey listenerKey, RemoteFutureOperation<S, R> registerListenerOperation) {
SettableFuture<R> settableFuture = SettableFuture.create();
mConnectionManager.registerListener(
listenerKey, createQueueOperation(registerListenerOperation, settableFuture));
return settableFuture;
}
/**
* Unregisters a listener by executing the provided {@link RemoteOperation
* unregisterListenerOperation}.
*
* @param listenerKey key based on which listeners will be distinguished
* @param unregisterListenerOperation {@link RemoteOperation} to unregister the listener
* @param <R> return type of {@code unregisterListenerOperation}
* @return {@link ListenableFuture} with the result of the operation or an exception if the
* execution fails
*/
protected <R> ListenableFuture<R> unregisterListener(
ListenerKey listenerKey, RemoteOperation<S, R> unregisterListenerOperation) {
return unregisterListener(
listenerKey,
(service, resultFuture) ->
resultFuture.set(unregisterListenerOperation.execute(service)));
}
/**
* Unregisters a listener by executing the provided {@link RemoteFutureOperation
* unregisterListenerOperation}.
*
* @param listenerKey key based on which listeners will be distinguished
* @param unregisterListenerOperation {@link RemoteFutureOperation} to unregister the listener
* @param <R> return type of {@code unregisterListenerOperation}
* @return {@link ListenableFuture} with the result of the operation or an exception if the
* execution fails
*/
protected <R> ListenableFuture<R> unregisterListener(
ListenerKey listenerKey, RemoteFutureOperation<S, R> unregisterListenerOperation) {
SettableFuture<R> settableFuture = SettableFuture.create();
mConnectionManager.unregisterListener(
listenerKey, createQueueOperation(unregisterListenerOperation, settableFuture));
return settableFuture;
}
protected Exception getApiVersionCheckFailureException(int currentVersion, int minApiVersion) {
return new ApiVersionException(currentVersion, minApiVersion);
}
ConnectionConfiguration getConnectionConfiguration() {
return mConnectionConfiguration;
}
ConnectionManager getConnectionManager() {
return mConnectionManager;
}
private <R> QueueOperation createQueueOperation(
RemoteFutureOperation<S, R> operation, SettableFuture<R> settableFuture) {
return new BaseQueueOperation(mConnectionConfiguration) {
@Override
public void execute(IBinder binder) throws RemoteException {
operation.execute(getService(binder), settableFuture);
}
@Override
public void setException(Throwable exception) {
settableFuture.setException(exception);
}
@Override
public QueueOperation trackExecution(ExecutionTracker tracker) {
tracker.track(settableFuture);
return this;
}
};
}
private S getService(IBinder binder) {
return mServiceGetter.getService(binder);
}
/** Interface for obtaining the service instance from the binder. */
protected interface ServiceGetter<S> {
S getService(IBinder binder);
}
}