| /* |
| * 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 com.android.server.autofill.Helper.DEBUG; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.assist.AssistStructure; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.ServiceConnection; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.IBinder.DeathRecipient; |
| import android.os.ICancellationSignal; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.os.UserHandle; |
| import android.service.autofill.AutofillService; |
| import android.service.autofill.FillResponse; |
| import android.service.autofill.IAutoFillService; |
| import android.service.autofill.IFillCallback; |
| import android.service.autofill.ISaveCallback; |
| import android.text.format.DateUtils; |
| import android.util.Slog; |
| |
| 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; |
| |
| 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(@Nullable FillResponse response, |
| @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); |
| } |
| |
| public void onFillRequest(@NonNull AssistStructure structure, @Nullable Bundle extras, |
| int flags) { |
| cancelScheduledUnbind(); |
| final PendingFillRequest request = new PendingFillRequest(structure, extras, this, flags); |
| mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, request).sendToTarget(); |
| } |
| |
| public void onSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle extras) { |
| cancelScheduledUnbind(); |
| final PendingSaveRequest request = new PendingSaveRequest(structure, extras, this); |
| mHandler.obtainMessageO(MyHandler.MSG_ON_PENDING_REQUEST, request).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 (DEBUG) { |
| Slog.d(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 (DEBUG) { |
| Slog.d(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 (DEBUG) { |
| Slog.d(LOG_TAG, "[user: " + mUserId + "] could not bind to " + mIntent); |
| } |
| mBinding = false; |
| |
| if (!mServiceDied) { |
| handleBinderDied(); |
| } |
| } |
| } |
| |
| private void ensureUnbound() { |
| if (!isBound() && !mBinding) { |
| return; |
| } |
| if (DEBUG) { |
| Slog.d(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, |
| FillResponse response) { |
| mHandler.getHandler().post(() -> { |
| if (handleResponseCallbackCommon(pendingRequest)) { |
| mCallbacks.onFillRequestSuccess(response, 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 { |
| void cancel() { |
| |
| } |
| |
| /** |
| * @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 Object mLock = new Object(); |
| private final WeakReference<RemoteFillService> mWeakService; |
| private final AssistStructure mStructure; |
| private final Bundle mExtras; |
| private final IFillCallback mCallback; |
| private ICancellationSignal mCancellation; |
| private boolean mCancelled; |
| private int mFlags; |
| |
| public PendingFillRequest(AssistStructure structure, |
| Bundle extras, RemoteFillService service, int flags) { |
| mStructure = structure; |
| mExtras = extras; |
| mFlags = flags; |
| mWeakService = new WeakReference<>(service); |
| mCallback = new IFillCallback.Stub() { |
| @Override |
| public void onCancellable(ICancellationSignal cancellation) { |
| synchronized (mLock) { |
| final boolean cancelled; |
| synchronized (mLock) { |
| mCancellation = cancellation; |
| cancelled = mCancelled; |
| } |
| if (cancelled) { |
| try { |
| cancellation.cancel(); |
| } catch (RemoteException e) { |
| Slog.e(LOG_TAG, "Error requesting a cancellation", e); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onSuccess(FillResponse response) { |
| RemoteFillService remoteService = mWeakService.get(); |
| if (remoteService != null) { |
| remoteService.dispatchOnFillRequestSuccess( |
| PendingFillRequest.this, response); |
| } |
| } |
| |
| @Override |
| public void onFailure(CharSequence message) { |
| RemoteFillService remoteService = mWeakService.get(); |
| if (remoteService != null) { |
| remoteService.dispatchOnFillRequestFailure( |
| PendingFillRequest.this, message); |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public void run() { |
| RemoteFillService remoteService = mWeakService.get(); |
| if (remoteService != null) { |
| try { |
| remoteService.mAutoFillService.onFillRequest(mStructure, |
| mExtras, mCallback, mFlags); |
| } catch (RemoteException e) { |
| Slog.e(LOG_TAG, "Error calling on fill request", e); |
| cancel(); |
| } |
| } |
| } |
| |
| @Override |
| public void cancel() { |
| final ICancellationSignal cancellation; |
| synchronized (mLock) { |
| if (mCancelled) { |
| return; |
| } |
| mCancelled = true; |
| cancellation = mCancellation; |
| } |
| if (cancellation == null) { |
| return; |
| } |
| try { |
| cancellation.cancel(); |
| } catch (RemoteException e) { |
| Slog.e(LOG_TAG, "Error cancelling a fill request", e); |
| } |
| } |
| } |
| |
| private static final class PendingSaveRequest extends PendingRequest { |
| private final WeakReference<RemoteFillService> mWeakService; |
| private final AssistStructure mStructure; |
| private final Bundle mExtras; |
| private final ISaveCallback mCallback; |
| |
| public PendingSaveRequest(@NonNull AssistStructure structure, @Nullable Bundle extras, |
| @NonNull RemoteFillService service) { |
| mStructure = structure; |
| mExtras = extras; |
| mWeakService = new WeakReference<>(service); |
| mCallback = new ISaveCallback.Stub() { |
| @Override |
| public void onSuccess() { |
| RemoteFillService service = mWeakService.get(); |
| if (service != null) { |
| service.dispatchOnSaveRequestSuccess( |
| PendingSaveRequest.this); |
| } |
| } |
| |
| @Override |
| public void onFailure(CharSequence message) { |
| RemoteFillService service = mWeakService.get(); |
| if (service != null) { |
| service.dispatchOnSaveRequestFailure( |
| PendingSaveRequest.this, message); |
| } |
| } |
| }; |
| } |
| |
| @Override |
| public void run() { |
| final RemoteFillService service = mWeakService.get(); |
| if (service != null) { |
| try { |
| service.mAutoFillService.onSaveRequest(mStructure, mExtras, mCallback); |
| } catch (RemoteException e) { |
| Slog.e(LOG_TAG, "Error calling on save request", e); |
| } |
| } |
| } |
| |
| @Override |
| public boolean isFinal() { |
| return true; |
| } |
| } |
| } |