blob: aebe92e1687ddcf4b10141ddf396e8bfe0e33796 [file] [log] [blame]
/*
* Copyright (C) 2017 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.autofill;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sVerbose;
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.ICancellationSignal;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.service.autofill.AutofillService;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
import android.service.autofill.IAutoFillService;
import android.service.autofill.IFillCallback;
import android.service.autofill.ISaveCallback;
import android.service.autofill.SaveRequest;
import android.text.format.DateUtils;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.HandlerCaller;
import com.android.server.FgThread;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
/**
* This class represents a remote fill service. It abstracts away the binding
* and unbinding from the remote implementation.
*
* <p>Clients can call methods of this class without worrying about when and
* how to bind/unbind/timeout. All state of this class is modified on a handler
* thread.
*/
final class RemoteFillService implements DeathRecipient {
private static final String LOG_TAG = "RemoteFillService";
// How long after the last interaction with the service we would unbind
private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
// How long after we make a remote request to a fill service we timeout
private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
private final Context mContext;
private final ComponentName mComponentName;
private final Intent mIntent;
private final FillServiceCallbacks mCallbacks;
private final int mUserId;
private final ServiceConnection mServiceConnection = new RemoteServiceConnection();
private final HandlerCaller mHandler;
private IAutoFillService mAutoFillService;
private boolean mBinding;
private boolean mDestroyed;
private boolean mServiceDied;
private boolean mCompleted;
private PendingRequest mPendingRequest;
public interface FillServiceCallbacks {
void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response, int serviceUid,
@NonNull String servicePackageName);
void onFillRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName);
void onSaveRequestSuccess(@NonNull String servicePackageName);
void onSaveRequestFailure(@Nullable CharSequence message,
@NonNull String servicePackageName);
void onServiceDied(RemoteFillService service);
}
public RemoteFillService(Context context, ComponentName componentName,
int userId, FillServiceCallbacks callbacks) {
mContext = context;
mCallbacks = callbacks;
mComponentName = componentName;
mIntent = new Intent(AutofillService.SERVICE_INTERFACE).setComponent(mComponentName);
mUserId = userId;
mHandler = new MyHandler(context);
}
public void destroy() {
mHandler.obtainMessage(MyHandler.MSG_DESTROY).sendToTarget();
}
private void handleDestroy() {
if (mPendingRequest != null) {
mPendingRequest.cancel();
mPendingRequest = null;
}
ensureUnbound();
mDestroyed = true;
}
@Override
public void binderDied() {
mHandler.obtainMessage(MyHandler.MSG_BINDER_DIED).sendToTarget();
}
private void handleBinderDied() {
if (mAutoFillService != null) {
mAutoFillService.asBinder().unlinkToDeath(this, 0);
}
mAutoFillService = null;
mServiceDied = true;
mCallbacks.onServiceDied(this);
}
/**
* Cancel the currently pending request.
*
* <p>This can be used when the request is unnecessary or will be superceeded by a request that
* will soon be queued.
*
* @return the id of the canceled request, or {@link FillRequest#INVALID_REQUEST_ID} if no
* {@link PendingFillRequest} was canceled.
*/
public int cancelCurrentRequest() {
if (mDestroyed) {
return INVALID_REQUEST_ID;
}
int requestId = INVALID_REQUEST_ID;
if (mPendingRequest != null) {
if (mPendingRequest instanceof PendingFillRequest) {
requestId = ((PendingFillRequest) mPendingRequest).mRequest.getId();
}
mPendingRequest.cancel();
mPendingRequest = null;
}
return requestId;
}
public void onFillRequest(@NonNull FillRequest request) {
cancelScheduledUnbind();
final PendingFillRequest pendingRequest = new PendingFillRequest(request, this);
mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, pendingRequest).sendToTarget();
}
public void onSaveRequest(@NonNull SaveRequest request) {
cancelScheduledUnbind();
final PendingSaveRequest pendingRequest = new PendingSaveRequest(request, this);
mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, pendingRequest).sendToTarget();
}
// 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.
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.println();
}
private void cancelScheduledUnbind() {
mHandler.removeMessages(MyHandler.MSG_UNBIND);
}
private void scheduleUnbind() {
cancelScheduledUnbind();
Message message = mHandler.obtainMessage(MyHandler.MSG_UNBIND);
mHandler.sendMessageDelayed(message, TIMEOUT_IDLE_BIND_MILLIS);
}
private void handleUnbind() {
ensureUnbound();
}
private void handlePendingRequest(PendingRequest pendingRequest) {
if (mDestroyed || mCompleted) {
return;
}
if (!isBound()) {
if (mPendingRequest != null) {
mPendingRequest.cancel();
}
mPendingRequest = pendingRequest;
ensureBound();
} else {
if (sVerbose) Slog.v(LOG_TAG, "[user: " + mUserId + "] handlePendingRequest()");
pendingRequest.run();
if (pendingRequest.isFinal()) {
mCompleted = true;
}
}
}
private boolean isBound() {
return mAutoFillService != null;
}
private void ensureBound() {
if (isBound() || mBinding) {
return;
}
if (sVerbose) Slog.v(LOG_TAG, "[user: " + mUserId + "] ensureBound()");
mBinding = true;
boolean willBind = mContext.bindServiceAsUser(mIntent, mServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
new UserHandle(mUserId));
if (!willBind) {
if (sDebug) Slog.d(LOG_TAG, "[user: " + mUserId + "] could not bind to " + mIntent);
mBinding = false;
if (!mServiceDied) {
handleBinderDied();
}
}
}
private void ensureUnbound() {
if (!isBound() && !mBinding) {
return;
}
if (sVerbose) Slog.v(LOG_TAG, "[user: " + mUserId + "] ensureUnbound()");
mBinding = false;
if (isBound()) {
try {
mAutoFillService.onConnectedStateChanged(false);
} catch (Exception e) {
Slog.w(LOG_TAG, "Exception calling onDisconnected(): " + e);
}
if (mAutoFillService != null) {
mAutoFillService.asBinder().unlinkToDeath(this, 0);
mAutoFillService = null;
}
}
mContext.unbindService(mServiceConnection);
}
private void dispatchOnFillRequestSuccess(PendingRequest pendingRequest,
int callingUid, int requestFlags, FillResponse response) {
mHandler.getHandler().post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onFillRequestSuccess(requestFlags, response, callingUid,
mComponentName.getPackageName());
}
});
}
private void dispatchOnFillRequestFailure(PendingRequest pendingRequest,
CharSequence message) {
mHandler.getHandler().post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onFillRequestFailure(message, mComponentName.getPackageName());
}
});
}
private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest) {
mHandler.getHandler().post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName());
}
});
}
private void dispatchOnSaveRequestFailure(PendingRequest pendingRequest,
CharSequence message) {
mHandler.getHandler().post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onSaveRequestFailure(message, mComponentName.getPackageName());
}
});
}
private boolean handleResponseCallbackCommon(PendingRequest pendingRequest) {
if (mDestroyed) {
return false;
}
if (mPendingRequest == pendingRequest) {
mPendingRequest = null;
}
if (mPendingRequest == null) {
scheduleUnbind();
}
return true;
}
private class RemoteServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (mDestroyed || !mBinding) {
mContext.unbindService(mServiceConnection);
return;
}
mBinding = false;
mAutoFillService = IAutoFillService.Stub.asInterface(service);
try {
service.linkToDeath(RemoteFillService.this, 0);
} catch (RemoteException re) {
handleBinderDied();
return;
}
try {
mAutoFillService.onConnectedStateChanged(true);
} catch (RemoteException e) {
Slog.w(LOG_TAG, "Exception calling onConnected(): " + e);
}
if (mPendingRequest != null) {
PendingRequest pendingRequest = mPendingRequest;
mPendingRequest = null;
handlePendingRequest(pendingRequest);
}
mServiceDied = false;
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBinding = true;
mAutoFillService = null;
}
}
private final class MyHandler extends HandlerCaller {
public static final int MSG_DESTROY = 1;
public static final int MSG_BINDER_DIED = 2;
public static final int MSG_UNBIND = 3;
public static final int MSG_ON_PENDING_REQUEST = 4;
public MyHandler(Context context) {
// Cannot use lambda - doesn't compile
super(context, FgThread.getHandler().getLooper(), new Callback() {
@Override
public void executeMessage(Message message) {
if (mDestroyed) {
Slog.w(LOG_TAG, "Not handling " + message + " as service for "
+ mComponentName + " is already destroyed");
return;
}
switch (message.what) {
case MSG_DESTROY: {
handleDestroy();
} break;
case MSG_BINDER_DIED: {
handleBinderDied();
} break;
case MSG_UNBIND: {
handleUnbind();
} break;
case MSG_ON_PENDING_REQUEST: {
handlePendingRequest((PendingRequest) message.obj);
} break;
}
}
}, false);
}
}
private static abstract class PendingRequest implements Runnable {
protected final Object mLock = new Object();
private final WeakReference<RemoteFillService> mWeakService;
private final Runnable mTimeoutTrigger;
private final Handler mServiceHandler;
@GuardedBy("mLock")
private boolean mCancelled;
@GuardedBy("mLock")
private boolean mCompleted;
PendingRequest(RemoteFillService service) {
mWeakService = new WeakReference<>(service);
mServiceHandler = service.mHandler.getHandler();
mTimeoutTrigger = () -> {
synchronized (mLock) {
if (mCancelled) {
return;
}
mCompleted = true;
}
Slog.w(LOG_TAG, getClass().getSimpleName() + " timed out");
final RemoteFillService remoteService = mWeakService.get();
if (remoteService != null) {
fail(remoteService);
}
};
mServiceHandler.postAtTime(mTimeoutTrigger,
SystemClock.uptimeMillis() + TIMEOUT_REMOTE_REQUEST_MILLIS);
}
protected RemoteFillService getService() {
return mWeakService.get();
}
/**
* Sub-classes must call this method when the remote service finishes, i.e., when it
* called {@code onFill...} or {@code onSave...}.
*
* @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;
}
protected boolean isCancelledLocked() {
return mCancelled;
}
/**
* Cancels the service.
*
* @return {@code false} if service is already canceled, {@code true} otherwise.
*/
boolean cancel() {
synchronized (mLock) {
if (mCancelled || mCompleted) {
return false;
}
mCancelled = true;
}
mServiceHandler.removeCallbacks(mTimeoutTrigger);
return true;
}
/**
* Called by the self-destructure timeout when the AutofilllService didn't reply to the
* request on time.
*/
abstract void fail(RemoteFillService remoteService);
/**
* @return whether this request leads to a final state where no
* other requests can be made.
*/
boolean isFinal() {
return false;
}
}
private static final class PendingFillRequest extends PendingRequest {
private final FillRequest mRequest;
private final IFillCallback mCallback;
private ICancellationSignal mCancellation;
public PendingFillRequest(FillRequest request, RemoteFillService service) {
super(service);
mRequest = request;
mCallback = new IFillCallback.Stub() {
@Override
public void onCancellable(ICancellationSignal cancellation) {
synchronized (mLock) {
final boolean cancelled;
synchronized (mLock) {
mCancellation = cancellation;
cancelled = isCancelledLocked();
}
if (cancelled) {
try {
cancellation.cancel();
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Error requesting a cancellation", e);
}
}
}
}
@Override
public void onSuccess(FillResponse response) {
if (!finish()) return;
final RemoteFillService remoteService = getService();
if (remoteService != null) {
remoteService.dispatchOnFillRequestSuccess(PendingFillRequest.this,
getCallingUid(), request.getFlags(), response);
}
}
@Override
public void onFailure(CharSequence message) {
if (!finish()) return;
final RemoteFillService remoteService = getService();
if (remoteService != null) {
remoteService.dispatchOnFillRequestFailure(
PendingFillRequest.this, message);
}
}
};
}
@Override
void fail(RemoteFillService remoteService) {
remoteService.dispatchOnFillRequestFailure(PendingFillRequest.this, null);
}
@Override
public void run() {
final RemoteFillService remoteService = getService();
if (remoteService != null) {
try {
remoteService.mAutoFillService.onFillRequest(mRequest, mCallback);
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Error calling on fill request", e);
remoteService.dispatchOnFillRequestFailure(PendingFillRequest.this, null);
}
}
}
@Override
public boolean cancel() {
if (!super.cancel()) return false;
final ICancellationSignal cancellation = mCancellation;
if (cancellation != null) {
try {
cancellation.cancel();
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Error cancelling a fill request", e);
}
}
return true;
}
}
private static final class PendingSaveRequest extends PendingRequest {
private final SaveRequest mRequest;
private final ISaveCallback mCallback;
public PendingSaveRequest(@NonNull SaveRequest request,
@NonNull RemoteFillService service) {
super(service);
mRequest = request;
mCallback = new ISaveCallback.Stub() {
@Override
public void onSuccess() {
if (!finish()) return;
final RemoteFillService remoteService = getService();
if (remoteService != null) {
remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this);
}
}
@Override
public void onFailure(CharSequence message) {
if (!finish()) return;
final RemoteFillService remoteService = getService();
if (remoteService != null) {
remoteService.dispatchOnSaveRequestFailure(PendingSaveRequest.this,
message);
}
}
};
}
@Override
void fail(RemoteFillService remoteService) {
remoteService.dispatchOnSaveRequestFailure(PendingSaveRequest.this, null);
}
@Override
public void run() {
final RemoteFillService remoteService = getService();
if (remoteService != null) {
try {
remoteService.mAutoFillService.onSaveRequest(mRequest, mCallback);
} catch (RemoteException e) {
Slog.e(LOG_TAG, "Error calling on save request", e);
remoteService.dispatchOnFillRequestFailure(PendingSaveRequest.this, null);
}
}
}
@Override
public boolean isFinal() {
return true;
}
}
}