blob: 56367180ec15ecca4da4260c17d3892085bea2b5 [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 com.android.server.servicewatcher;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import com.android.server.FgThread;
import java.io.PrintWriter;
import java.util.Objects;
/**
* A ServiceWatcher is responsible for continuously maintaining an active binding to a service
* selected by it's {@link ServiceSupplier}. The {@link ServiceSupplier} may change the service it
* selects over time, and the currently bound service may crash, restart, have a user change, have
* changes made to its package, and so on and so forth. The ServiceWatcher is responsible for
* maintaining the binding across all these changes.
*
* <p>Clients may invoke {@link BinderOperation}s on the ServiceWatcher, and it will make a best
* effort to run these on the currently bound service, but individual operations may fail (if there
* is no service currently bound for instance). In order to help clients maintain the correct state,
* clients may supply a {@link ServiceListener}, which is informed when the ServiceWatcher connects
* and disconnects from a service. This allows clients to bring a bound service back into a known
* state on connection, and then run binder operations from there. In order to help clients
* accomplish this, ServiceWatcher guarantees that {@link BinderOperation}s and the
* {@link ServiceListener} will always be run on the same thread, so that strong ordering guarantees
* can be established between them.
*
* There is never any guarantee of whether a ServiceWatcher is currently connected to a service, and
* whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely
* on this, and instead use {@link ServiceListener} notifications as necessary to recover from
* failures.
*/
public interface ServiceWatcher {
/**
* Operation to run on a binder interface. All operations will be run on the thread used by the
* ServiceWatcher this is run with.
*/
interface BinderOperation {
/** Invoked to run the operation. Run on the ServiceWatcher thread. */
void run(IBinder binder) throws RemoteException;
/**
* Invoked if {@link #run(IBinder)} could not be invoked because there was no current
* binding, or if {@link #run(IBinder)} threw an exception ({@link RemoteException} or
* {@link RuntimeException}). This callback is only intended for resource deallocation and
* cleanup in response to a single binder operation, it should not be used to propagate
* errors further. Run on the ServiceWatcher thread.
*/
default void onError(Throwable t) {}
}
/**
* Listener for bind and unbind events. All operations will be run on the thread used by the
* ServiceWatcher this is run with.
*
* @param <TBoundServiceInfo> type of bound service
*/
interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> {
/** Invoked when a service is bound. Run on the ServiceWatcher thread. */
void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException;
/** Invoked when a service is unbound. Run on the ServiceWatcher thread. */
void onUnbind();
}
/**
* A listener for when a {@link ServiceSupplier} decides that the current service has changed.
*/
interface ServiceChangedListener {
/**
* Should be invoked when the current service may have changed.
*/
void onServiceChanged();
}
/**
* This supplier encapsulates the logic of deciding what service a {@link ServiceWatcher} should
* be bound to at any given moment.
*
* @param <TBoundServiceInfo> type of bound service
*/
interface ServiceSupplier<TBoundServiceInfo extends BoundServiceInfo> {
/**
* Should return true if there exists at least one service capable of meeting the criteria
* of this supplier. This does not imply that {@link #getServiceInfo()} will always return a
* non-null result, as any service may be disqualified for various reasons at any point in
* time. May be invoked at any time from any thread and thus should generally not have any
* dependency on the other methods in this interface.
*/
boolean hasMatchingService();
/**
* Invoked when the supplier should start monitoring for any changes that could result in a
* different service selection, and should invoke
* {@link ServiceChangedListener#onServiceChanged()} in that case. {@link #getServiceInfo()}
* may be invoked after this method is called.
*/
void register(ServiceChangedListener listener);
/**
* Invoked when the supplier should stop monitoring for any changes that could result in a
* different service selection, should no longer invoke
* {@link ServiceChangedListener#onServiceChanged()}. {@link #getServiceInfo()} will not be
* invoked after this method is called.
*/
void unregister();
/**
* Must be implemented to return the current service selected by this supplier. May return
* null if no service currently meets the criteria. Only invoked while registered.
*/
@Nullable TBoundServiceInfo getServiceInfo();
}
/**
* Information on the service selected as the best option for binding.
*/
class BoundServiceInfo {
protected final @Nullable String mAction;
protected final int mUid;
protected final ComponentName mComponentName;
protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
this(action, resolveInfo.serviceInfo.applicationInfo.uid,
resolveInfo.serviceInfo.getComponentName());
}
protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
mAction = action;
mUid = uid;
mComponentName = Objects.requireNonNull(componentName);
}
/** Returns the action associated with this bound service. */
public @Nullable String getAction() {
return mAction;
}
/** Returns the component of this bound service. */
public ComponentName getComponentName() {
return mComponentName;
}
/** Returns the user id for this bound service. */
public @UserIdInt int getUserId() {
return UserHandle.getUserId(mUid);
}
@Override
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BoundServiceInfo)) {
return false;
}
BoundServiceInfo that = (BoundServiceInfo) o;
return mUid == that.mUid
&& Objects.equals(mAction, that.mAction)
&& mComponentName.equals(that.mComponentName);
}
@Override
public final int hashCode() {
return Objects.hash(mAction, mUid, mComponentName);
}
@Override
public String toString() {
if (mComponentName == null) {
return "none";
} else {
return mUid + "/" + mComponentName.flattenToShortString();
}
}
}
/**
* Creates a new ServiceWatcher instance.
*/
static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
Context context,
String tag,
ServiceSupplier<TBoundServiceInfo> serviceSupplier,
@Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
return create(context, FgThread.getHandler(), tag, serviceSupplier, serviceListener);
}
/**
* Creates a new ServiceWatcher instance that runs on the given handler.
*/
static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
Context context,
Handler handler,
String tag,
ServiceSupplier<TBoundServiceInfo> serviceSupplier,
@Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
return new ServiceWatcherImpl<>(context, handler, tag, serviceSupplier, serviceListener);
}
/**
* Returns true if there is at least one service that the ServiceWatcher could hypothetically
* bind to, as selected by the {@link ServiceSupplier}.
*/
boolean checkServiceResolves();
/**
* Registers the ServiceWatcher, so that it will begin maintaining an active binding to the
* service selected by {@link ServiceSupplier}, until {@link #unregister()} is called.
*/
void register();
/**
* Unregisters the ServiceWatcher, so that it will release any active bindings. If the
* ServiceWatcher is currently bound, this will result in one final
* {@link ServiceListener#onUnbind()} invocation, which may happen after this method completes
* (but which is guaranteed to occur before any further
* {@link ServiceListener#onBind(IBinder, BoundServiceInfo)} invocation in response to a later
* call to {@link #register()}).
*/
void unregister();
/**
* Runs the given binder operation on the currently bound service (if available). The operation
* will always fail if the ServiceWatcher is not currently registered.
*/
void runOnBinder(BinderOperation operation);
/**
* Dumps ServiceWatcher information.
*/
void dump(PrintWriter pw);
}