blob: 181d7fde1fb8b4bc2e2925ee740f6972728d9d63 [file] [log] [blame]
/*
* Copyright (C) 2018 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;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.IInterface;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
/**
* Base class representing a remote service.
*
* <p>It abstracts away the binding and unbinding from the remote implementation, so clients can
* call its methods without worrying about when and how to bind/unbind/timeout.
*
* <p>All state of this class is modified on a handler thread.
*
* <p>See {@code com.android.server.autofill.RemoteFillService} for a concrete
* (no pun intended) example of how to use it.
*
* @hide
*/
//TODO(b/117779333): improve javadoc above instead of using Autofill as an example
public abstract class AbstractRemoteService implements DeathRecipient {
private static final int MSG_UNBIND = 1;
protected static final int LAST_PRIVATE_MSG = MSG_UNBIND;
// TODO(b/117779333): convert all booleans into an integer / flags
public final boolean mVerbose;
protected final String mTag = getClass().getSimpleName();
protected final Handler mHandler;
protected final ComponentName mComponentName;
protected PendingRequest<? extends AbstractRemoteService> mPendingRequest;
private final Context mContext;
private final Intent mIntent;
private final VultureCallback mVultureCallback;
private final int mUserId;
private final ServiceConnection mServiceConnection = new RemoteServiceConnection();
private final boolean mBindInstantServiceAllowed;
private IInterface mServiceInterface;
private boolean mBinding;
private boolean mDestroyed;
private boolean mServiceDied;
private boolean mCompleted;
/**
* Callback called when the service dies.
*/
public interface VultureCallback {
/**
* Called when the service dies.
*
* @param service service that died!
*/
void onServiceDied(AbstractRemoteService service);
}
public AbstractRemoteService(@NonNull Context context, @NonNull String serviceInterface,
@NonNull ComponentName componentName, int userId, @NonNull VultureCallback callback,
boolean bindInstantServiceAllowed, boolean verbose) {
mContext = context;
mVultureCallback = callback;
mVerbose = verbose;
mComponentName = componentName;
mIntent = new Intent(serviceInterface).setComponent(mComponentName);
mUserId = userId;
mHandler = new Handler(FgThread.getHandler().getLooper());
mBindInstantServiceAllowed = bindInstantServiceAllowed;
}
/**
* Destroys this service.
*/
public final void destroy() {
mHandler.sendMessage(obtainMessage(AbstractRemoteService::handleDestroy, this));
}
/**
* Checks whether this service is destroyed.
*/
public final boolean isDestroyed() {
return mDestroyed;
}
/**
* Callback called when the system connected / disconnected to the service.
*
* @param state {@code true} when connected, {@code false} when disconnected.
*/
protected void onConnectedStateChanged(boolean state) {
}
/**
* Gets the base Binder interface from the service.
*/
@NonNull
protected abstract IInterface getServiceInterface(@NonNull IBinder service);
/**
* Defines How long after the last interaction with the service we would unbind.
*/
protected abstract long getTimeoutIdleBindMillis();
/**
* Defines how long after we make a remote request to a fill service we timeout.
*/
protected abstract long getRemoteRequestMillis();
private void handleDestroy() {
if (checkIfDestroyed()) return;
if (mPendingRequest != null) {
mPendingRequest.cancel();
mPendingRequest = null;
}
ensureUnbound();
mDestroyed = true;
}
@Override // from DeathRecipient
public void binderDied() {
mHandler.sendMessage(obtainMessage(AbstractRemoteService::handleBinderDied, this));
}
private void handleBinderDied() {
if (checkIfDestroyed()) return;
if (mServiceInterface != null) {
mServiceInterface.asBinder().unlinkToDeath(this, 0);
}
mServiceInterface = null;
mServiceDied = true;
mVultureCallback.onServiceDied(this);
}
// Note: we are dumping without a lock held so this is a bit racy but
// adding a lock to a class that offloads to a handler thread would
// mean adding a lock adding overhead to normal runtime operation.
/**
* Dump it!
*/
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
String tab = " ";
pw.append(prefix).append("service:").println();
pw.append(prefix).append(tab).append("userId=")
.append(String.valueOf(mUserId)).println();
pw.append(prefix).append(tab).append("componentName=")
.append(mComponentName.flattenToString()).println();
pw.append(prefix).append(tab).append("destroyed=")
.append(String.valueOf(mDestroyed)).println();
pw.append(prefix).append(tab).append("bound=")
.append(String.valueOf(isBound())).println();
pw.append(prefix).append(tab).append("hasPendingRequest=")
.append(String.valueOf(mPendingRequest != null)).println();
pw.append(prefix).append("mBindInstantServiceAllowed=").println(mBindInstantServiceAllowed);
pw.append(prefix).append("idleTimeout=")
.append(Long.toString(getTimeoutIdleBindMillis() / 1000)).append("s").println();
pw.append(prefix).append("requestTimeout=")
.append(Long.toString(getRemoteRequestMillis() / 1000)).append("s").println();
pw.println();
}
protected void scheduleRequest(PendingRequest<? extends AbstractRemoteService> pendingRequest) {
mHandler.sendMessage(obtainMessage(
AbstractRemoteService::handlePendingRequest, this, pendingRequest));
}
protected void cancelScheduledUnbind() {
mHandler.removeMessages(MSG_UNBIND);
}
protected void scheduleUnbind() {
cancelScheduledUnbind();
mHandler.sendMessageDelayed(obtainMessage(AbstractRemoteService::handleUnbind, this)
.setWhat(MSG_UNBIND), getTimeoutIdleBindMillis());
}
private void handleUnbind() {
if (checkIfDestroyed()) return;
ensureUnbound();
}
private void handlePendingRequest(
PendingRequest<? extends AbstractRemoteService> pendingRequest) {
if (checkIfDestroyed() || mCompleted) return;
if (!isBound()) {
if (mPendingRequest != null) {
mPendingRequest.cancel();
}
mPendingRequest = pendingRequest;
ensureBound();
} else {
if (mVerbose) Slog.v(mTag, "handlePendingRequest(): " + pendingRequest);
pendingRequest.run();
if (pendingRequest.isFinal()) {
mCompleted = true;
}
}
}
private boolean isBound() {
return mServiceInterface != null;
}
private void ensureBound() {
if (isBound() || mBinding) return;
if (mVerbose) Slog.v(mTag, "ensureBound()");
mBinding = true;
int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
if (mBindInstantServiceAllowed) {
flags |= Context.BIND_ALLOW_INSTANT;
}
final boolean willBind = mContext.bindServiceAsUser(mIntent, mServiceConnection, flags,
new UserHandle(mUserId));
if (!willBind) {
Slog.w(mTag, "could not bind to " + mIntent + " using flags " + flags);
mBinding = false;
if (!mServiceDied) {
handleBinderDied();
}
}
}
private void ensureUnbound() {
if (!isBound() && !mBinding) return;
if (mVerbose) Slog.v(mTag, "ensureUnbound()");
mBinding = false;
if (isBound()) {
onConnectedStateChanged(false);
if (mServiceInterface != null) {
mServiceInterface.asBinder().unlinkToDeath(this, 0);
mServiceInterface = null;
}
}
mContext.unbindService(mServiceConnection);
}
private class RemoteServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mDestroyed || !mBinding) {
// This is abnormal. Unbinding the connection has been requested already.
Slog.wtf(mTag, "onServiceConnected() was dispatched after unbindService.");
return;
}
mBinding = false;
mServiceInterface = getServiceInterface(service);
try {
service.linkToDeath(AbstractRemoteService.this, 0);
} catch (RemoteException re) {
handleBinderDied();
return;
}
onConnectedStateChanged(true);
if (mPendingRequest != null) {
final PendingRequest<? extends AbstractRemoteService> pendingRequest =
mPendingRequest;
mPendingRequest = null;
handlePendingRequest(pendingRequest);
}
mServiceDied = false;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBinding = true;
mServiceInterface = null;
}
}
private boolean checkIfDestroyed() {
if (mDestroyed) {
if (mVerbose) {
Slog.v(mTag, "Not handling operation as service for " + mComponentName
+ " is already destroyed");
}
}
return mDestroyed;
}
protected boolean handleResponseCallbackCommon(
PendingRequest<? extends AbstractRemoteService> pendingRequest) {
if (isDestroyed()) return false;
if (mPendingRequest == pendingRequest) {
mPendingRequest = null;
}
if (mPendingRequest == null) {
scheduleUnbind();
}
return true;
}
/**
* Base class for the requests serviced by the remote service.
*
* @param <S> the remote service class
*/
public abstract static class PendingRequest<S extends AbstractRemoteService>
implements Runnable {
protected final String mTag = getClass().getSimpleName();
protected final Object mLock = new Object();
private final WeakReference<S> mWeakService;
private final Runnable mTimeoutTrigger;
private final Handler mServiceHandler;
@GuardedBy("mLock")
private boolean mCancelled;
@GuardedBy("mLock")
private boolean mCompleted;
protected PendingRequest(S service) {
mWeakService = new WeakReference<>(service);
mServiceHandler = service.mHandler;
mTimeoutTrigger = () -> {
synchronized (mLock) {
if (mCancelled) {
return;
}
mCompleted = true;
}
final S remoteService = mWeakService.get();
if (remoteService != null) {
Slog.w(mTag, "timed out after " + service.getRemoteRequestMillis() + " ms");
onTimeout(remoteService);
} else {
Slog.w(mTag, "timed out (no service)");
}
};
mServiceHandler.postAtTime(mTimeoutTrigger,
SystemClock.uptimeMillis() + service.getRemoteRequestMillis());
}
/**
* Gets a reference to the remote service.
*/
protected final S getService() {
return mWeakService.get();
}
/**
* Subclasses must call this method when the remote service finishes, i.e., when the service
* finishes processing a request.
*
* @return {@code false} in the service is already finished, {@code true} otherwise.
*/
protected final boolean finish() {
synchronized (mLock) {
if (mCompleted || mCancelled) {
return false;
}
mCompleted = true;
}
mServiceHandler.removeCallbacks(mTimeoutTrigger);
return true;
}
/**
* Checks whether this request was cancelled.
*/
@GuardedBy("mLock")
protected final boolean isCancelledLocked() {
return mCancelled;
}
/**
* Cancels the service.
*
* @return {@code false} if service is already canceled, {@code true} otherwise.
*/
public boolean cancel() {
synchronized (mLock) {
if (mCancelled || mCompleted) {
return false;
}
mCancelled = true;
}
mServiceHandler.removeCallbacks(mTimeoutTrigger);
return true;
}
/**
* Called by the self-destruct timeout when the remote service didn't reply to the
* request on time.
*/
protected abstract void onTimeout(S remoteService);
/**
* Checks whether this request leads to a final state where no other requests can be made.
*/
protected boolean isFinal() {
return false;
}
}
}