blob: 6c13a4a731a93b5f10885552738c0321ed762ae2 [file] [log] [blame]
/*
* Copyright 2019 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.browser.trusted;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.support.customtabs.trusted.ITrustedWebActivityService;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
/**
* Holds a connection to a TrustedWebActivityService.
* It should only be used on the UI Thread.
*/
class ConnectionHolder implements ServiceConnection {
private static final int STATE_AWAITING_CONNECTION = 0;
private static final int STATE_CONNECTED = 1;
private static final int STATE_DISCONNECTED = 2;
private static final int STATE_CANCELLED = 3;
@NonNull private final Runnable mCloseRunnable;
@NonNull private final WrapperFactory mWrapperFactory;
private int mState = STATE_AWAITING_CONNECTION;
@Nullable private TrustedWebActivityServiceConnection mService;
@NonNull private List<Completer<TrustedWebActivityServiceConnection>> mCompleters =
new ArrayList<>();
@Nullable private Exception mCancellationException;
/** A class that creates the TrustedWebActivityServiceConnection. Allows mocking in tests. */
static class WrapperFactory {
@NonNull
TrustedWebActivityServiceConnection create(ComponentName name, IBinder iBinder) {
return new TrustedWebActivityServiceConnection(
ITrustedWebActivityService.Stub.asInterface(iBinder), name);
}
}
/**
* Constructor for production use. Takes in a {@link Runnable} that will be called either when
* the Service disconnects or {@link #cancel} is called.
*/
@MainThread
ConnectionHolder(@NonNull Runnable closeRunnable) {
this(closeRunnable, new WrapperFactory());
}
/** Constructor for testing use. */
@MainThread
ConnectionHolder(@NonNull Runnable closeRunnable, @NonNull WrapperFactory factory) {
mCloseRunnable = closeRunnable;
mWrapperFactory = factory;
}
/** This method will be called on the UI Thread by the Android Framework. */
@Override
@MainThread
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mService = mWrapperFactory.create(componentName, iBinder);
for (Completer<TrustedWebActivityServiceConnection> completer : mCompleters) {
completer.set(mService);
}
mCompleters.clear();
mState = STATE_CONNECTED;
}
/** This method will be called on the UI Thread by the Android Framework. */
@Override
@MainThread
public void onServiceDisconnected(ComponentName componentName) {
mService = null;
mCloseRunnable.run();
mState = STATE_DISCONNECTED;
}
/**
* Called to signal that the connection attempt failed and that neither
* {@link #onServiceConnected(ComponentName, IBinder)} or
* {@link #onServiceDisconnected(ComponentName)} will be called.
*/
@MainThread
public void cancel(@NonNull Exception exception) {
for (Completer<TrustedWebActivityServiceConnection> completer : mCompleters) {
completer.setException(exception);
}
mCompleters.clear();
mCloseRunnable.run();
mState = STATE_CANCELLED;
mCancellationException = exception;
}
/**
* Returns a future that will:
* - be unset if a connection is still pending and set once open.
* - be set to a {@link TrustedWebActivityServiceConnection} if a connection is open.
* - be set to an exception if the connection failed or has been closed.
*/
@MainThread
@NonNull
public ListenableFuture<TrustedWebActivityServiceConnection> getServiceWrapper() {
// Using CallbackToFutureAdapter and storing the completers gives us some additional safety
// checks over using Futures ourselves (such as failing the Future if the completer is
// garbage collected).
return CallbackToFutureAdapter.getFuture(completer -> {
switch (mState) {
case STATE_AWAITING_CONNECTION:
mCompleters.add(completer);
break;
case STATE_CONNECTED:
if (mService == null) {
throw new IllegalStateException("ConnectionHolder state is incorrect.");
}
completer.set(mService);
break;
case STATE_DISCONNECTED:
throw new IllegalStateException("Service has been disconnected.");
case STATE_CANCELLED:
throw mCancellationException;
default:
throw new IllegalStateException("Connection state is invalid");
}
return "ConnectionHolder, state = " + mState;
});
}
}