All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.internal.infra.AbstractRemoteService Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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.internal.infra;

import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;

import android.annotation.NonNull;
import android.annotation.Nullable;
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 android.util.TimeUtils;

import com.android.internal.annotations.GuardedBy;

import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;

/**
 * Base class representing a remote service.
 *
 * 

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. * *

All state of this class is modified on a handler thread. * *

NOTE: this class should not be extended directly, you should extend either * {@link AbstractSinglePendingRequestRemoteService} or * {@link AbstractMultiplePendingRequestsRemoteService}. * *

See {@code com.android.server.autofill.RemoteFillService} for a concrete * (no pun intended) example of how to use it. * * @param the concrete remote service class * @param the interface of the binder service * * @deprecated Use {@link ServiceConnector} to manage remote service connections * * @hide */ //TODO(b/117779333): improve javadoc above instead of using Autofill as an example @Deprecated public abstract class AbstractRemoteService, I extends IInterface> implements DeathRecipient { private static final int MSG_BIND = 1; private static final int MSG_UNBIND = 2; public static final long PERMANENT_BOUND_TIMEOUT_MS = 0; 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; private final Context mContext; private final Intent mIntent; private final VultureCallback mVultureCallback; private final int mUserId; private final ServiceConnection mServiceConnection = new RemoteServiceConnection(); private final int mBindingFlags; protected I mService; private boolean mBound; private boolean mConnecting; private boolean mDestroyed; private boolean mServiceDied; private boolean mCompleted; // Used just for debugging purposes (on dump) private long mNextUnbind; /** Requests that have been scheduled, but that are not finished yet */ private final ArrayList> mUnfinishedRequests = new ArrayList<>(); /** * Callback called when the service dies. * * @param service class */ public interface VultureCallback { /** * Called when the service dies. * * @param service service that died! */ void onServiceDied(T service); } // NOTE: must be package-protected so this class is not extended outside AbstractRemoteService(@NonNull Context context, @NonNull String serviceInterface, @NonNull ComponentName componentName, int userId, @NonNull VultureCallback callback, @NonNull Handler handler, int bindingFlags, boolean verbose) { mContext = context; mVultureCallback = callback; mVerbose = verbose; mComponentName = componentName; mIntent = new Intent(serviceInterface).setComponent(mComponentName); mUserId = userId; mHandler = new Handler(handler.getLooper()); mBindingFlags = bindingFlags; } /** * 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; } /** * Gets the name of the service. */ @NonNull public final ComponentName getComponentName() { return mComponentName; } private void handleOnConnectedStateChangedInternal(boolean connected) { handleOnConnectedStateChanged(connected); if (connected) { handlePendingRequests(); } } /** * Handles the pending requests when the connection it bounds to the remote service. */ abstract void handlePendingRequests(); /** * Callback called when the system connected / disconnected to the service and the pending * requests have been handled. * * @param state {@code true} when connected, {@code false} when disconnected. */ protected void handleOnConnectedStateChanged(boolean state) { } /** * Gets the base Binder interface from the service. */ @NonNull protected abstract I getServiceInterface(@NonNull IBinder service); /** * Defines how long after the last interaction with the service we would unbind. * * @return time to unbind (in millis), or {@link #PERMANENT_BOUND_TIMEOUT_MS} to not unbind. */ protected abstract long getTimeoutIdleBindMillis(); /** * Defines how long after we make a remote request to a fill service we timeout. * *

Just need to be overridden by subclasses that uses sync {@link PendingRequest}s. * * @throws UnsupportedOperationException if called when not overridden. * */ protected long getRemoteRequestMillis() { throw new UnsupportedOperationException("not implemented by " + getClass()); } /** * Gets the currently registered service interface or {@code null} if the service is not * connected. */ @Nullable public final I getServiceInterface() { return mService; } private void handleDestroy() { if (checkIfDestroyed()) return; handleOnDestroy(); handleEnsureUnbound(); mDestroyed = true; } /** * Clears the state when this object is destroyed. * *

Typically used to cancel the pending requests. */ protected abstract void handleOnDestroy(); @Override // from DeathRecipient public void binderDied() { mHandler.sendMessage(obtainMessage(AbstractRemoteService::handleBinderDied, this)); } private void handleBinderDied() { if (checkIfDestroyed()) return; if (mService != null) { mService.asBinder().unlinkToDeath(this, 0); } mConnecting = true; mService = null; mServiceDied = true; cancelScheduledUnbind(); @SuppressWarnings("unchecked") // TODO(b/117779333): fix this warning final S castService = (S) this; mVultureCallback.onServiceDied(castService); handleBindFailure(); } // 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("numUnfinishedRequests=") .append(String.valueOf(mUnfinishedRequests.size())).println(); pw.append(prefix).append(tab).append("bound=") .append(String.valueOf(mBound)); final boolean bound = handleIsBound(); pw.append(prefix).append(tab).append("connected=") .append(String.valueOf(bound)); final long idleTimeout = getTimeoutIdleBindMillis(); if (bound) { if (idleTimeout > 0) { pw.append(" (unbind in : "); TimeUtils.formatDuration(mNextUnbind - SystemClock.elapsedRealtime(), pw); pw.append(")"); } else { pw.append(" (permanently bound)"); } } pw.println(); pw.append(prefix).append("mBindingFlags=").println(mBindingFlags); pw.append(prefix).append("idleTimeout=") .append(Long.toString(idleTimeout / 1000)).append("s\n"); pw.append(prefix).append("requestTimeout="); try { pw.append(Long.toString(getRemoteRequestMillis() / 1000)).append("s\n"); } catch (UnsupportedOperationException e) { pw.append("not supported\n"); } pw.println(); } /** * Schedules a "sync" request. * *

This request must be responded by the service somehow (typically using a callback), * othewise it will trigger a {@link PendingRequest#onTimeout(AbstractRemoteService)} if the * service doesn't respond. */ protected void scheduleRequest(@NonNull BasePendingRequest pendingRequest) { mHandler.sendMessage(obtainMessage( AbstractRemoteService::handlePendingRequest, this, pendingRequest)); } /** * Marks a pendingRequest as finished. * * @param finshedRequest The request that is finished */ void finishRequest(@NonNull BasePendingRequest finshedRequest) { mHandler.sendMessage( obtainMessage(AbstractRemoteService::handleFinishRequest, this, finshedRequest)); } private void handleFinishRequest(@NonNull BasePendingRequest finshedRequest) { mUnfinishedRequests.remove(finshedRequest); if (mUnfinishedRequests.isEmpty()) { scheduleUnbind(); } } /** * Schedules an async request. * *

This request is not expecting a callback from the service, hence it's represented by * a simple {@link Runnable}. */ protected void scheduleAsyncRequest(@NonNull AsyncRequest request) { // TODO(b/117779333): fix generics below @SuppressWarnings({"unchecked", "rawtypes"}) final MyAsyncPendingRequest asyncRequest = new MyAsyncPendingRequest(this, request); mHandler.sendMessage( obtainMessage(AbstractRemoteService::handlePendingRequest, this, asyncRequest)); } /** * Executes an async request immediately instead of sending it to Handler queue as what * {@link scheduleAsyncRequest} does. * *

This request is not expecting a callback from the service, hence it's represented by * a simple {@link Runnable}. */ protected void executeAsyncRequest(@NonNull AsyncRequest request) { // TODO(b/117779333): fix generics below @SuppressWarnings({"unchecked", "rawtypes"}) final MyAsyncPendingRequest asyncRequest = new MyAsyncPendingRequest(this, request); handlePendingRequest(asyncRequest); } private void cancelScheduledUnbind() { mHandler.removeMessages(MSG_UNBIND); } /** * Schedules a request to bind to the remote service. * *

Typically used on constructor for implementations that need a permanent connection to * the remote service. */ protected void scheduleBind() { if (mHandler.hasMessages(MSG_BIND)) { if (mVerbose) Slog.v(mTag, "scheduleBind(): already scheduled"); return; } mHandler.sendMessage(obtainMessage(AbstractRemoteService::handleEnsureBound, this) .setWhat(MSG_BIND)); } /** * Schedules a request to automatically unbind from the service after the * {@link #getTimeoutIdleBindMillis() idle timeout} expires. */ protected void scheduleUnbind() { scheduleUnbind(true); } private void scheduleUnbind(boolean delay) { long unbindDelay = getTimeoutIdleBindMillis(); if (unbindDelay <= PERMANENT_BOUND_TIMEOUT_MS) { if (mVerbose) Slog.v(mTag, "not scheduling unbind when value is " + unbindDelay); return; } if (!delay) { unbindDelay = 0; } cancelScheduledUnbind(); // TODO(b/117779333): make sure it's unbound if the service settings changing (right now // it's not) mNextUnbind = SystemClock.elapsedRealtime() + unbindDelay; if (mVerbose) Slog.v(mTag, "unbinding in " + unbindDelay + "ms: " + mNextUnbind); mHandler.sendMessageDelayed(obtainMessage(AbstractRemoteService::handleUnbind, this) .setWhat(MSG_UNBIND), unbindDelay); } private void handleUnbind() { if (checkIfDestroyed()) return; handleEnsureUnbound(); } /** * Handles a request, either processing it right now when bound, or saving it to be handled when * bound. */ protected final void handlePendingRequest(@NonNull BasePendingRequest pendingRequest) { if (checkIfDestroyed() || mCompleted) return; if (!handleIsBound()) { if (mVerbose) Slog.v(mTag, "handlePendingRequest(): queuing " + pendingRequest); handlePendingRequestWhileUnBound(pendingRequest); handleEnsureBound(); } else { if (mVerbose) Slog.v(mTag, "handlePendingRequest(): " + pendingRequest); mUnfinishedRequests.add(pendingRequest); cancelScheduledUnbind(); pendingRequest.run(); if (pendingRequest.isFinal()) { mCompleted = true; } } } /** * Defines what to do with a request that arrives while not bound to the service. */ abstract void handlePendingRequestWhileUnBound( @NonNull BasePendingRequest pendingRequest); /** * Called if {@link Context#bindServiceAsUser} returns {@code false}, or * if {@link DeathRecipient#binderDied()} is called. */ abstract void handleBindFailure(); // This is actually checking isConnected. TODO: rename this and other related methods (or just // stop using this class..) private boolean handleIsBound() { return mService != null; } private void handleEnsureBound() { if (handleIsBound() || mConnecting) return; if (mVerbose) Slog.v(mTag, "ensureBound()"); mConnecting = true; final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | Context.BIND_INCLUDE_CAPABILITIES | mBindingFlags; final boolean willBind = mContext.bindServiceAsUser(mIntent, mServiceConnection, flags, mHandler, new UserHandle(mUserId)); mBound = true; if (!willBind) { Slog.w(mTag, "could not bind to " + mIntent + " using flags " + flags); mConnecting = false; if (!mServiceDied) { handleBinderDied(); } } } private void handleEnsureUnbound() { if (!handleIsBound() && !mConnecting) return; if (mVerbose) Slog.v(mTag, "ensureUnbound()"); mConnecting = false; if (handleIsBound()) { handleOnConnectedStateChangedInternal(false); if (mService != null) { mService.asBinder().unlinkToDeath(this, 0); mService = null; } } mNextUnbind = 0; if (mBound) { mContext.unbindService(mServiceConnection); mBound = false; } } private class RemoteServiceConnection implements ServiceConnection { @Override public void onServiceConnected(ComponentName name, IBinder service) { if (mVerbose) Slog.v(mTag, "onServiceConnected()"); if (mDestroyed || !mConnecting) { // This is abnormal. Unbinding the connection has been requested already. Slog.wtf(mTag, "onServiceConnected() was dispatched after unbindService."); return; } mConnecting = false; try { service.linkToDeath(AbstractRemoteService.this, 0); } catch (RemoteException re) { handleBinderDied(); return; } mService = getServiceInterface(service); handleOnConnectedStateChangedInternal(true); mServiceDied = false; } @Override public void onServiceDisconnected(ComponentName name) { if (mVerbose) Slog.v(mTag, "onServiceDisconnected()"); mConnecting = true; mService = null; } @Override public void onBindingDied(ComponentName name) { if (mVerbose) Slog.v(mTag, "onBindingDied()"); scheduleUnbind(false); } } private boolean checkIfDestroyed() { if (mDestroyed) { if (mVerbose) { Slog.v(mTag, "Not handling operation as service for " + mComponentName + " is already destroyed"); } } return mDestroyed; } @Override public String toString() { return getClass().getSimpleName() + "[" + mComponentName + " " + System.identityHashCode(this) + (mService != null ? " (bound)" : " (unbound)") + (mDestroyed ? " (destroyed)" : "") + "]"; } /** * Base class for the requests serviced by the remote service. * *

NOTE: this class is not used directly, you should either override * {@link com.android.internal.infra.AbstractRemoteService.PendingRequest} for sync requests, or * use {@link AbstractRemoteService#scheduleAsyncRequest(AsyncRequest)} for async requests. * * @param the remote service class * @param the interface of the binder service */ public abstract static class BasePendingRequest, I extends IInterface> implements Runnable { protected final String mTag = getClass().getSimpleName(); protected final Object mLock = new Object(); final WeakReference mWeakService; @GuardedBy("mLock") boolean mCancelled; @GuardedBy("mLock") boolean mCompleted; BasePendingRequest(@NonNull S service) { mWeakService = new WeakReference<>(service); } /** * 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; } S service = mWeakService.get(); if (service != null) { service.finishRequest(this); } onFinished(); return true; } void onFinished() { } /** * Called when request fails due to reasons internal to {@link AbstractRemoteService}, * e.g. failure to bind to service. */ protected void onFailed() { } /** * 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; } onCancel(); return true; } void onCancel() {} /** * Checks whether this request leads to a final state where no other requests can be made. */ protected boolean isFinal() { return false; } protected boolean isRequestCompleted() { synchronized (mLock) { return mCompleted; } } } /** * Base class for the requests serviced by the remote service. * *

NOTE: this class is typically used when the service needs to use a callback to * communicate back with the system server. For cases where that's not needed, you should use * {@link AbstractRemoteService#scheduleAsyncRequest(AsyncRequest)} instead. * *

NOTE: you must override {@link AbstractRemoteService#getRemoteRequestMillis()}, * otherwise the constructor will throw an {@link UnsupportedOperationException}. * * @param the remote service class * @param the interface of the binder service */ public abstract static class PendingRequest, I extends IInterface> extends BasePendingRequest { private final Runnable mTimeoutTrigger; private final Handler mServiceHandler; protected PendingRequest(S service) { super(service); mServiceHandler = service.mHandler; mTimeoutTrigger = () -> { synchronized (mLock) { if (mCancelled) { return; } mCompleted = true; } final S remoteService = mWeakService.get(); if (remoteService != null) { // TODO(b/117779333): we should probably ignore it if service is destroyed. Slog.w(mTag, "timed out after " + service.getRemoteRequestMillis() + " ms"); remoteService.finishRequest(this); onTimeout(remoteService); } else { Slog.w(mTag, "timed out (no service)"); } }; mServiceHandler.postAtTime(mTimeoutTrigger, SystemClock.uptimeMillis() + service.getRemoteRequestMillis()); } @Override final void onFinished() { mServiceHandler.removeCallbacks(mTimeoutTrigger); } @Override final void onCancel() { mServiceHandler.removeCallbacks(mTimeoutTrigger); } /** * Called by the self-destruct timeout when the remote service didn't reply to the * request on time. */ protected abstract void onTimeout(S remoteService); } /** * Represents a request that does not expect a callback from the remote service. * * @param the interface of the binder service */ public interface AsyncRequest { /** * Run Forrest, run! */ void run(@NonNull I binder) throws RemoteException; } private static final class MyAsyncPendingRequest, I extends IInterface> extends BasePendingRequest { private static final String TAG = MyAsyncPendingRequest.class.getSimpleName(); private final AsyncRequest mRequest; protected MyAsyncPendingRequest(@NonNull S service, @NonNull AsyncRequest request) { super(service); mRequest = request; } @Override public void run() { final S remoteService = getService(); if (remoteService == null) return; try { mRequest.run(remoteService.mService); } catch (RemoteException e) { Slog.w(TAG, "exception handling async request (" + this + "): " + e); } finally { finish(); } } } }