ServiceConnector impl + migration of RemoteFillService

Original CL ag/5907902 has many merge conflicts so re-posting a trimmed
down version of it.
Initially migrating just the RemoteFillService, with the intention to
migrate others in separate CLs.

Fixes: 122480607
Test: atest CtsAutoFillServiceTestCases
Change-Id: Ibae8b0aa32a7c8283b0fb6eb3c288769b730149a
This commit is contained in:
Eugene Susla
2019-04-15 13:19:09 -07:00
parent 418edab1fa
commit a081250c30
6 changed files with 1175 additions and 442 deletions

View File

@@ -24,7 +24,11 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
/**
* <p>Various utilities for debugging and logging.</p>
@@ -270,4 +274,20 @@ public class DebugUtils {
private static String constNameWithoutPrefix(String prefix, Field field) {
return field.getName().substring(prefix.length());
}
/**
* Returns method names from current stack trace, where {@link StackTraceElement#getClass}
* starts with the given classes name
*
* @hide
*/
public static List<String> callersWithin(Class<?> cls, int offset) {
List<String> result = Arrays.stream(Thread.currentThread().getStackTrace())
.skip(offset + 3)
.filter(st -> st.getClassName().startsWith(cls.getName()))
.map(StackTraceElement::getMethodName)
.collect(Collectors.toList());
Collections.reverse(result);
return result;
}
}

View File

@@ -0,0 +1,226 @@
/*
* Copyright (C) 2019 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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Handler;
import android.os.Message;
import android.util.ExceptionUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* A customized {@link CompletableFuture} with focus on reducing the number of allocations involved
* in a typical future usage scenario for Android.
*
* In particular this involves allocations optimizations in:
* <ul>
* <li>{@link #thenCompose(Function)}</li>
* <li>{@link #orTimeout(long, TimeUnit)}</li>
* <li>{@link #whenComplete(BiConsumer)}</li>
* </ul>
*
* @param <T> see {@link CompletableFuture}
*/
public class AndroidFuture<T> extends CompletableFuture<T> {
private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
@GuardedBy("this")
private @Nullable BiConsumer<? super T, ? super Throwable> mListener;
private @NonNull Handler mTimeoutHandler = Handler.getMain();
@Override
public boolean complete(@Nullable T value) {
boolean changed = super.complete(value);
if (changed) {
onCompleted(value, null);
}
return changed;
}
@Override
public boolean completeExceptionally(@NonNull Throwable ex) {
boolean changed = super.completeExceptionally(ex);
if (changed) {
onCompleted(null, ex);
}
return super.completeExceptionally(ex);
}
private void onCompleted(@Nullable T res, @Nullable Throwable err) {
cancelTimeout();
BiConsumer<? super T, ? super Throwable> listener;
synchronized (this) {
listener = mListener;
mListener = null;
}
if (listener != null) {
callListener(listener, res, err);
}
}
@Override
public AndroidFuture<T> whenComplete(
@NonNull BiConsumer<? super T, ? super Throwable> action) {
Preconditions.checkNotNull(action);
synchronized (this) {
if (!isDone()) {
BiConsumer<? super T, ? super Throwable> oldListener = mListener;
mListener = oldListener == null
? action
: (res, err) -> {
callListener(oldListener, res, err);
callListener(action, res, err);
};
return this;
}
}
// isDone() == true at this point
T res = null;
Throwable err = null;
try {
res = get();
} catch (ExecutionException e) {
err = e.getCause();
} catch (Throwable e) {
err = e;
}
callListener(action, res, err);
return this;
}
/**
* Calls the provided listener, handling any exceptions that may arise.
*/
// package-private to avoid synthetic method when called from lambda
static <TT> void callListener(
@NonNull BiConsumer<? super TT, ? super Throwable> listener,
@Nullable TT res, @Nullable Throwable err) {
try {
try {
listener.accept(res, err);
} catch (Throwable t) {
if (err == null) {
// listener happy-case threw, but exception case might not throw, so report the
// same exception thrown by listener's happy-path to it again
listener.accept(null, t);
} else {
// listener exception-case threw
// give up on listener but preserve the original exception when throwing up
ExceptionUtils.getRootCause(t).initCause(err);
throw t;
}
}
} catch (Throwable t2) {
// give up on listener and log the result & exception to logcat
Log.e(LOG_TAG, "Failed to call whenComplete listener. res = " + res, t2);
}
}
/** @inheritDoc */
//@Override //TODO uncomment once java 9 APIs are exposed to frameworks
public AndroidFuture<T> orTimeout(long timeout, @NonNull TimeUnit unit) {
Message msg = PooledLambda.obtainMessage(AndroidFuture::triggerTimeout, this);
msg.obj = this;
mTimeoutHandler.sendMessageDelayed(msg, unit.toMillis(timeout));
return this;
}
void triggerTimeout() {
cancelTimeout();
if (!isDone()) {
completeExceptionally(new TimeoutException());
}
}
protected void cancelTimeout() {
mTimeoutHandler.removeCallbacksAndMessages(this);
}
/**
* Specifies the handler on which timeout is to be triggered
*/
public AndroidFuture<T> setTimeoutHandler(@NonNull Handler h) {
cancelTimeout();
mTimeoutHandler = Preconditions.checkNotNull(h);
return this;
}
@Override
public <U> AndroidFuture<U> thenCompose(
@NonNull Function<? super T, ? extends CompletionStage<U>> fn) {
return (AndroidFuture<U>) new ThenCompose<>(this, fn);
}
private static class ThenCompose<T, U> extends AndroidFuture<Object>
implements BiConsumer<Object, Throwable> {
private final AndroidFuture<T> mSource;
private Function<? super T, ? extends CompletionStage<U>> mFn;
ThenCompose(@NonNull AndroidFuture<T> source,
@NonNull Function<? super T, ? extends CompletionStage<U>> fn) {
mSource = source;
mFn = Preconditions.checkNotNull(fn);
// subscribe to first job completion
source.whenComplete(this);
}
@Override
public void accept(Object res, Throwable err) {
Function<? super T, ? extends CompletionStage<U>> fn;
synchronized (this) {
fn = mFn;
mFn = null;
}
if (fn != null) {
// first job completed
CompletionStage<U> secondJob;
try {
secondJob = Preconditions.checkNotNull(fn.apply((T) res));
} catch (Throwable t) {
completeExceptionally(t);
return;
}
// subscribe to second job completion
secondJob.whenComplete(this);
} else {
// second job completed
if (err != null) {
completeExceptionally(err);
} else {
complete(res);
}
}
}
}
}

View File

@@ -0,0 +1,699 @@
/*
* Copyright (C) 2019 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 android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
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.IInterface;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.DebugUtils;
import android.util.Log;
import com.android.internal.util.function.pooled.PooledLambda;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* Takes care of managing a {@link ServiceConnection} and auto-disconnecting from the service upon
* a certain timeout.
*
* <p>
* The requests are always processed in the order they are scheduled.
*
* <p>
* Use {@link ServiceConnector.Impl} to construct an instance.
*
* @param <I> the type of the {@link IInterface ipc interface} for the remote service
*/
public interface ServiceConnector<I extends IInterface> {
/**
* Schedules to run a given job when service is connected, without providing any means to track
* the job's completion.
*
* <p>
* This is slightly more efficient than {@link #post(VoidJob)} as it doesn't require an extra
* allocation of a {@link AndroidFuture} for progress tracking.
*
* @return whether a job was successfully scheduled
*/
boolean fireAndForget(@NonNull VoidJob<I> job);
/**
* Schedules to run a given job when service is connected.
*
* <p>
* You can choose to wait for the job synchronously using {@link AndroidFuture#get} or
* attach a listener to it using one of the options such as
* {@link AndroidFuture#whenComplete}
* You can also {@link AndroidFuture#cancel cancel} the pending job.
*
* @return a {@link AndroidFuture} tracking the job's completion
*
* @see #postForResult(Job) for a variant of this that also propagates an arbitrary result
* back to the caller
* @see CompletableFuture for more options on what you can do with a result of an asynchronous
* operation, including more advanced operations such as
* {@link CompletableFuture#thenApply transforming} its result,
* {@link CompletableFuture#thenCombine joining}
* results of multiple async operation into one,
* {@link CompletableFuture#thenCompose composing} results of
* multiple async operations that depend on one another, and more.
*/
@CheckResult(suggest = "#fireAndForget")
AndroidFuture<Void> post(@NonNull VoidJob<I> job);
/**
* Variant of {@link #post(VoidJob)} that also propagates an arbitrary result back to the
* caller asynchronously.
*
* @param <R> the type of the result this job produces
*
* @see #post(VoidJob)
*/
@CheckResult(suggest = "#fireAndForget")
<R> AndroidFuture<R> postForResult(@NonNull Job<I, R> job);
/**
* Schedules a job that is itself asynchronous, that is job returns a result in the form of a
* {@link CompletableFuture}
*
* <p>
* This takes care of "flattening" the nested futures that would have resulted from 2
* asynchronous operations performed in sequence.
*
* <p>
* Like with other options, {@link AndroidFuture#cancel cancelling} the resulting future
* will remove the job from the queue, preventing it from running if it hasn't yet started.
*
* @see #postForResult
* @see #post
*/
<R> AndroidFuture<R> postAsync(@NonNull Job<I, CompletableFuture<R>> job);
/**
* Requests to connect to the service without posting any meaningful job to run.
*
* <p>
* This returns a {@link AndroidFuture} tracking the progress of binding to the service,
* which can be used to schedule calls to the service once it's connected.
*
* <p>
* Avoid caching the resulting future as the instance may change due to service disconnecting
* and reconnecting.
*/
AndroidFuture<I> connect();
/**
* Request to unbind from the service as soon as possible.
*
* <p>
* If there are any pending jobs remaining they will be
* {@link AndroidFuture#cancel cancelled}.
*/
void unbind();
/**
* A request to be run when the service is
* {@link ServiceConnection#onServiceConnected connected}.
*
* @param <II> type of the {@link IInterface ipc interface} to be used
* @param <R> type of the return value
*
* @see VoidJob for a variant that doesn't produce any return value
*/
@FunctionalInterface
interface Job<II, R> {
/**
* Perform the remote call using the provided {@link IInterface ipc interface instance}.
*
* Avoid caching the provided {@code service} instance as it may become invalid when service
* disconnects.
*
* @return the result of this operation to be propagated to the original caller.
* If you do not need to provide a result you can implement {@link VoidJob} instead
*/
R run(@NonNull II service) throws RemoteException;
}
/**
* Variant of {@link Job} that doesn't return a result
*
* @param <II> see {@link Job}
*/
@FunctionalInterface
interface VoidJob<II> extends Job<II, Void> {
/** @see Job#run */
void runNoResult(II service) throws RemoteException;
@Override
default Void run(II service) throws RemoteException {
runNoResult(service);
return null;
}
}
/**
* Implementation of {@link ServiceConnector}
*
* <p>
* For allocation-efficiency reasons this implements a bunch of interfaces that are not meant to
* be a public API of {@link ServiceConnector}.
* For this reason prefer to use {@link ServiceConnector} instead of
* {@link ServiceConnector.Impl} as the field type when storing an instance.
*
* <p>
* In some rare cases you may want to extend this class, overriding certain methods for further
* flexibility.
* If you do, it would typically be one of the {@code protected} methods on this class.
*
* @param <I> see {@link ServiceConnector}
*/
class Impl<I extends IInterface> extends ArrayDeque<Job<I, ?>>
implements ServiceConnector<I>, ServiceConnection, IBinder.DeathRecipient, Runnable {
static final boolean DEBUG = false;
static final String LOG_TAG = "ServiceConnector.Impl";
private static final long DEFAULT_DISCONNECT_TIMEOUT_MS = 15_000;
private final @NonNull Queue<Job<I, ?>> mQueue = this;
private final @NonNull List<CompletionAwareJob<I, ?>> mUnfinishedJobs = new ArrayList<>();
private final @NonNull ServiceConnection mServiceConnection = this;
private final @NonNull Runnable mTimeoutDisconnect = this;
private final @NonNull Context mContext;
private final @NonNull Intent mIntent;
private final int mBindingFlags;
private final int mUserId;
private final @Nullable Function<IBinder, I> mBinderAsInterface;
private I mService = null;
private boolean mBinding = false;
private boolean mUnbinding = false;
private CompletionAwareJob<I, I> mServiceConnectionFutureCache = null;
/**
* Creates an instance of {@link ServiceConnector}
*
* See {@code protected} methods for optional parameters you can override.
*
* @param context to be used for {@link Context#bindServiceAsUser binding} and
* {@link Context#unbindService unbinding}
* @param intent to be used for {@link Context#bindServiceAsUser binding}
* @param bindingFlags to be used for {@link Context#bindServiceAsUser binding}
* @param userId to be used for {@link Context#bindServiceAsUser binding}
* @param binderAsInterface to be used for converting an {@link IBinder} provided in
* {@link ServiceConnection#onServiceConnected} into a specific
* {@link IInterface}.
* Typically this is {@code IMyInterface.Stub::asInterface}
*/
public Impl(@NonNull Context context, @NonNull Intent intent,
@Context.BindServiceFlags int bindingFlags, @UserIdInt int userId,
@Nullable Function<IBinder, I> binderAsInterface) {
mContext = context;
mIntent = intent;
mBindingFlags = bindingFlags;
mUserId = userId;
mBinderAsInterface = binderAsInterface;
}
/**
* {@link Handler} on which {@link Job}s will be called
*/
protected Handler getJobHandler() {
return Handler.getMain();
}
/**
* Gets the amount of time spent without any calls before the service is automatically
* {@link Context#unbindService unbound}
*
* @return amount of time in ms, or non-positive (<=0) value to disable automatic unbinding
*/
protected long getAutoDisconnectTimeoutMs() {
return DEFAULT_DISCONNECT_TIMEOUT_MS;
}
/**
* {@link Context#bindServiceAsUser Binds} to the service.
*
* <p>
* If overridden, implementation must use at least the provided {@link ServiceConnection}
*/
protected boolean bindService(
@NonNull ServiceConnection serviceConnection, @NonNull Handler handler) {
if (DEBUG) {
logTrace();
}
return mContext.bindServiceAsUser(mIntent, serviceConnection,
Context.BIND_AUTO_CREATE | mBindingFlags,
handler, UserHandle.of(mUserId));
}
/**
* Gets the binder interface.
* Typically {@code IMyInterface.Stub.asInterface(service)}.
*
* <p>
* Can be overridden instead of provided as a constructor parameter to save a singleton
* allocation
*/
protected I binderAsInterface(@NonNull IBinder service) {
return mBinderAsInterface.apply(service);
}
/**
* Called when service was {@link Context#unbindService unbound}
*
* <p>
* Can be overridden to perform some cleanup on service disconnect
*/
protected void onServiceUnbound() {
if (DEBUG) {
logTrace();
}
}
/**
* Called when the service just connected or is about to disconnect
*/
protected void onServiceConnectionStatusChanged(@NonNull I service, boolean isConnected) {}
@Override
public boolean fireAndForget(@NonNull VoidJob<I> job) {
if (DEBUG) {
Log.d(LOG_TAG, "Wrapping fireAndForget job to take advantage of its mDebugName");
return !post(job).isCompletedExceptionally();
}
return enqueue(job);
}
@Override
public AndroidFuture<Void> post(@NonNull VoidJob<I> job) {
return postForResult((Job) job);
}
@Override
public <R> CompletionAwareJob<I, R> postForResult(@NonNull Job<I, R> job) {
CompletionAwareJob<I, R> task = new CompletionAwareJob<>();
task.mDelegate = job;
enqueue(task);
return task;
}
@Override
public <R> AndroidFuture<R> postAsync(@NonNull Job<I, CompletableFuture<R>> job) {
CompletionAwareJob task = postForResult(job);
task.mAsync = true;
return task;
}
@Override
public synchronized AndroidFuture<I> connect() {
if (mServiceConnectionFutureCache == null) {
mServiceConnectionFutureCache = new CompletionAwareJob<I, I>();
mServiceConnectionFutureCache.mDelegate = s -> s;
I service = mService;
if (service != null) {
mServiceConnectionFutureCache.complete(service);
} else {
enqueue(mServiceConnectionFutureCache);
}
}
return mServiceConnectionFutureCache;
}
private void enqueue(@NonNull CompletionAwareJob<I, ?> task) {
if (!enqueue((Job<I, ?>) task)) {
task.completeExceptionally(new IllegalStateException(
"Failed to post a job to main handler. Likely main looper is exiting"));
}
}
private boolean enqueue(@NonNull Job<I, ?> job) {
cancelTimeout();
return getJobHandler().sendMessage(PooledLambda.obtainMessage(
ServiceConnector.Impl::enqueueJobThread, this, job));
}
void enqueueJobThread(@NonNull Job<I, ?> job) {
if (DEBUG) {
Log.i(LOG_TAG, "post(" + job + ", this = " + this + ")");
}
cancelTimeout();
if (mUnbinding) {
completeExceptionally(job,
new IllegalStateException("Service is unbinding. Ignoring " + job));
} else if (!mQueue.offer(job)) {
completeExceptionally(job,
new IllegalStateException("Failed to add to queue: " + job));
} else if (isBound()) {
processQueue();
} else if (!mBinding) {
if (bindService(mServiceConnection, getJobHandler())) {
mBinding = true;
} else {
completeExceptionally(job,
new IllegalStateException("Failed to bind to service " + mIntent));
}
}
}
private void cancelTimeout() {
if (DEBUG) {
logTrace();
}
Handler.getMain().removeCallbacks(mTimeoutDisconnect);
}
void completeExceptionally(@NonNull Job<?, ?> job, @NonNull Throwable ex) {
CompletionAwareJob task = castOrNull(job, CompletionAwareJob.class);
boolean taskChanged = false;
if (task != null) {
taskChanged = task.completeExceptionally(ex);
}
if (task == null || (DEBUG && taskChanged)) {
Log.e(LOG_TAG, "Job failed: " + job, ex);
}
}
static @Nullable <BASE, T extends BASE> T castOrNull(
@Nullable BASE instance, @NonNull Class<T> cls) {
return cls.isInstance(instance) ? (T) instance : null;
}
private void processQueue() {
if (DEBUG) {
logTrace();
}
Job<I, ?> job;
while ((job = mQueue.poll()) != null) {
CompletionAwareJob task = castOrNull(job, CompletionAwareJob.class);
try {
I service = mService;
if (service == null) {
return;
}
Object result = job.run(service);
if (DEBUG) {
Log.i(LOG_TAG, "complete(" + job + ", result = " + result + ")");
}
if (task != null) {
if (task.mAsync) {
mUnfinishedJobs.add(task);
((CompletionStage) result).whenComplete(task);
} else {
task.complete(result);
}
}
} catch (Throwable e) {
completeExceptionally(job, e);
}
}
maybeScheduleUnbindTimeout();
}
private void maybeScheduleUnbindTimeout() {
if (mUnfinishedJobs.isEmpty() && mQueue.isEmpty()) {
scheduleUnbindTimeout();
}
}
private void scheduleUnbindTimeout() {
if (DEBUG) {
logTrace();
}
long timeout = getAutoDisconnectTimeoutMs();
if (timeout > 0) {
Handler.getMain().postDelayed(mTimeoutDisconnect, timeout);
} else if (DEBUG) {
Log.i(LOG_TAG, "Not scheduling unbind for permanently bound " + this);
}
}
private boolean isBound() {
return mService != null;
}
@Override
public void unbind() {
if (DEBUG) {
logTrace();
}
mUnbinding = true;
getJobHandler().sendMessage(PooledLambda.obtainMessage(Impl::unbindJobThread, this));
}
void unbindJobThread() {
cancelTimeout();
boolean wasBound = isBound();
if (wasBound) {
onServiceConnectionStatusChanged(mService, false);
mContext.unbindService(mServiceConnection);
mService.asBinder().unlinkToDeath(this, 0);
mService = null;
}
mBinding = false;
mUnbinding = false;
synchronized (this) {
if (mServiceConnectionFutureCache != null) {
mServiceConnectionFutureCache.cancel(true);
mServiceConnectionFutureCache = null;
}
}
cancelPendingJobs();
if (wasBound) {
onServiceUnbound();
}
}
protected void cancelPendingJobs() {
Job<I, ?> job;
while ((job = mQueue.poll()) != null) {
if (DEBUG) {
Log.i(LOG_TAG, "cancel(" + job + ")");
}
CompletionAwareJob task = castOrNull(job, CompletionAwareJob.class);
if (task != null) {
task.cancel(/* mayInterruptWhileRunning= */ false);
}
}
}
@Override
public void onServiceConnected(@NonNull ComponentName name, @NonNull IBinder service) {
if (mUnbinding) {
Log.i(LOG_TAG, "Ignoring onServiceConnected due to ongoing unbinding: " + this);
return;
}
if (DEBUG) {
logTrace();
}
mService = binderAsInterface(service);
mBinding = false;
try {
service.linkToDeath(ServiceConnector.Impl.this, 0);
} catch (RemoteException e) {
Log.e(LOG_TAG, "onServiceConnected " + name + ": ", e);
}
processQueue();
onServiceConnectionStatusChanged(mService, true);
}
@Override
public void onServiceDisconnected(@NonNull ComponentName name) {
if (DEBUG) {
logTrace();
}
mBinding = true;
onServiceConnectionStatusChanged(mService, false);
mService = null;
}
@Override
public void onBindingDied(@NonNull ComponentName name) {
if (DEBUG) {
logTrace();
}
binderDied();
}
@Override
public void binderDied() {
if (DEBUG) {
logTrace();
}
mService = null;
unbind();
}
@Override
public void run() {
onTimeout();
}
private void onTimeout() {
if (DEBUG) {
logTrace();
}
unbind();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("ServiceConnector@")
.append(System.identityHashCode(this) % 1000).append("(")
.append(mIntent).append(", user: ").append(mUserId)
.append(")[").append(stateToString());
if (!mQueue.isEmpty()) {
sb.append(", ").append(mQueue.size()).append(" pending job(s)");
if (DEBUG) {
sb.append(": ").append(super.toString());
}
}
if (!mUnfinishedJobs.isEmpty()) {
sb.append(", ").append(mUnfinishedJobs.size()).append(" unfinished async job(s)");
}
return sb.append("]").toString();
}
public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
String tab = " ";
pw.append(prefix).append("ServiceConnector:").println();
pw.append(prefix).append(tab).append(String.valueOf(mIntent)).println();
pw.append(prefix).append(tab)
.append("userId: ").append(String.valueOf(mUserId)).println();
pw.append(prefix).append(tab)
.append("State: ").append(stateToString()).println();
pw.append(prefix).append(tab)
.append("Pending jobs: ").append(String.valueOf(mQueue.size())).println();
if (DEBUG) {
for (Job<I, ?> pendingJob : mQueue) {
pw.append(prefix).append(tab).append(tab)
.append(String.valueOf(pendingJob)).println();
}
}
pw.append(prefix).append(tab)
.append("Unfinished async jobs: ")
.append(String.valueOf(mUnfinishedJobs.size())).println();
}
private String stateToString() {
if (mBinding) {
return "Binding...";
} else if (mUnbinding) {
return "Unbinding...";
} else if (isBound()) {
return "Bound";
} else {
return "Unbound";
}
}
private void logTrace() {
Log.i(LOG_TAG,
TextUtils.join(" -> ",
DebugUtils.callersWithin(ServiceConnector.class, /* offset= */ 1))
+ "(" + this + ")");
}
/**
* {@link Job} + {@link AndroidFuture}
*/
class CompletionAwareJob<II, R> extends AndroidFuture<R>
implements Job<II, R>, BiConsumer<R, Throwable> {
Job<II, R> mDelegate;
boolean mAsync = false;
private String mDebugName;
{
if (DEBUG) {
mDebugName = Arrays.stream(Thread.currentThread().getStackTrace())
.skip(2)
.filter(st ->
!st.getClassName().contains(ServiceConnector.class.getName()))
.findFirst()
.get()
.getMethodName();
}
}
@Override
public R run(@NonNull II service) throws RemoteException {
return mDelegate.run(service);
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
if (mayInterruptIfRunning) {
Log.w(LOG_TAG, "mayInterruptIfRunning not supported - ignoring");
}
boolean wasRemoved = mQueue.remove(this);
return super.cancel(mayInterruptIfRunning) || wasRemoved;
}
@Override
public String toString() {
if (DEBUG) {
return mDebugName;
}
return mDelegate.toString() + " wrapped into " + super.toString();
}
@Override
public void accept(@Nullable R res, @Nullable Throwable err) {
if (mUnfinishedJobs.remove(this)) {
maybeScheduleUnbindTimeout();
}
if (err != null) {
completeExceptionally(err);
} else {
complete(res);
}
}
}
}
}

View File

@@ -1112,7 +1112,7 @@ final class AutofillManagerServiceImpl
final RemoteAugmentedAutofillService remoteService =
mRemoteAugmentedAutofillService;
if (remoteService != null) {
remoteService.destroy();
remoteService.unbind();
}
mRemoteAugmentedAutofillService = null;
}
@@ -1135,7 +1135,7 @@ final class AutofillManagerServiceImpl
Slog.v(TAG, "updateRemoteAugmentedAutofillService(): "
+ "destroying old remote service");
}
mRemoteAugmentedAutofillService.destroy();
mRemoteAugmentedAutofillService.unbind();
mRemoteAugmentedAutofillService = null;
mRemoteAugmentedAutofillServiceInfo = null;
resetAugmentedAutofillWhitelistLocked();

View File

@@ -25,9 +25,11 @@ import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.RemoteException;
@@ -42,30 +44,39 @@ import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.autofill.IAutoFillManagerClient;
import com.android.internal.infra.AbstractSinglePendingRequestRemoteService;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.ServiceConnector;
import com.android.internal.os.IResultReceiver;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
final class RemoteAugmentedAutofillService
extends AbstractSinglePendingRequestRemoteService<RemoteAugmentedAutofillService,
IAugmentedAutofillService> {
extends ServiceConnector.Impl<IAugmentedAutofillService> {
private static final String TAG = RemoteAugmentedAutofillService.class.getSimpleName();
private final int mIdleUnbindTimeoutMs;
private final int mRequestTimeoutMs;
private final ComponentName mComponentName;
RemoteAugmentedAutofillService(Context context, ComponentName serviceName,
int userId, RemoteAugmentedAutofillServiceCallbacks callbacks,
boolean bindInstantServiceAllowed, boolean verbose, int idleUnbindTimeoutMs,
int requestTimeoutMs) {
super(context, AugmentedAutofillService.SERVICE_INTERFACE, serviceName, userId, callbacks,
context.getMainThreadHandler(),
bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0, verbose);
super(context,
new Intent(AugmentedAutofillService.SERVICE_INTERFACE).setComponent(serviceName),
bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0,
userId, IAugmentedAutofillService.Stub::asInterface);
mIdleUnbindTimeoutMs = idleUnbindTimeoutMs;
mRequestTimeoutMs = requestTimeoutMs;
mComponentName = serviceName;
// Bind right away.
scheduleBind();
connect();
}
@Nullable
@@ -93,219 +104,126 @@ final class RemoteAugmentedAutofillService
return new Pair<>(serviceInfo, serviceComponent);
}
@Override // from RemoteService
protected void handleOnConnectedStateChanged(boolean state) {
if (state && getTimeoutIdleBindMillis() != PERMANENT_BOUND_TIMEOUT_MS) {
scheduleUnbind();
}
public ComponentName getComponentName() {
return mComponentName;
}
@Override // from ServiceConnector.Impl
protected void onServiceConnectionStatusChanged(
IAugmentedAutofillService service, boolean connected) {
try {
if (state) {
mService.onConnected(sDebug, sVerbose);
if (connected) {
service.onConnected(sDebug, sVerbose);
} else {
mService.onDisconnected();
service.onDisconnected();
}
} catch (Exception e) {
Slog.w(mTag, "Exception calling onConnectedStateChanged(" + state + "): " + e);
Slog.w(TAG,
"Exception calling onServiceConnectionStatusChanged(" + connected + "): ", e);
}
}
@Override // from AbstractRemoteService
protected IAugmentedAutofillService getServiceInterface(IBinder service) {
return IAugmentedAutofillService.Stub.asInterface(service);
}
@Override // from AbstractRemoteService
protected long getTimeoutIdleBindMillis() {
protected long getAutoDisconnectTimeoutMs() {
return mIdleUnbindTimeoutMs;
}
@Override // from AbstractRemoteService
protected long getRemoteRequestMillis() {
return mRequestTimeoutMs;
}
/**
* Called by {@link Session} to request augmented autofill.
*/
public void onRequestAutofillLocked(int sessionId, @NonNull IAutoFillManagerClient client,
int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
@Nullable AutofillValue focusedValue) {
scheduleRequest(new PendingAutofillRequest(this, sessionId, client, taskId,
activityComponent, focusedId, focusedValue));
long requestTime = SystemClock.elapsedRealtime();
AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>();
postAsync(service -> {
AndroidFuture<Void> requestAutofill = new AndroidFuture<>();
// TODO(b/122728762): set cancellation signal, timeout (from both client and service),
// cache IAugmentedAutofillManagerClient reference, etc...
client.getAugmentedAutofillClient(new IResultReceiver.Stub() {
@Override
public void send(int resultCode, Bundle resultData) throws RemoteException {
final IBinder realClient = resultData
.getBinder(AutofillManager.EXTRA_AUGMENTED_AUTOFILL_CLIENT);
service.onFillRequest(sessionId, realClient, taskId, activityComponent,
focusedId, focusedValue, requestTime, new IFillCallback.Stub() {
@Override
public void onSuccess() {
requestAutofill.complete(null);
}
@Override
public boolean isCompleted() {
return requestAutofill.isDone()
&& !requestAutofill.isCancelled();
}
@Override
public void onCancellable(ICancellationSignal cancellation) {
if (requestAutofill.isCancelled()) {
dispatchCancellation(cancellation);
} else {
cancellationRef.set(cancellation);
}
}
@Override
public void cancel() {
requestAutofill.cancel(true);
}
});
}
});
return requestAutofill;
}).orTimeout(mRequestTimeoutMs, TimeUnit.MILLISECONDS)
.whenComplete((res, err) -> {
if (err instanceof CancellationException) {
dispatchCancellation(cancellationRef.get());
} else if (err instanceof TimeoutException) {
// TODO(b/122858578): must update the logged AUTOFILL_AUGMENTED_REQUEST with
// the timeout
Slog.w(TAG, "PendingAutofillRequest timed out (" + mRequestTimeoutMs
+ "ms) for " + RemoteAugmentedAutofillService.this);
// NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks
dispatchCancellation(cancellationRef.get());
} else if (err != null) {
Slog.e(TAG, "exception handling getAugmentedAutofillClient() for "
+ sessionId + ": ", err);
} else {
// NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks
}
});
}
void dispatchCancellation(@Nullable ICancellationSignal cancellation) {
if (cancellation == null) {
return;
}
Handler.getMain().post(() -> {
try {
cancellation.cancel();
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting a cancellation", e);
}
});
}
@Override
public String toString() {
return "RemoteAugmentedAutofillService["
+ ComponentName.flattenToShortString(getComponentName()) + "]";
+ ComponentName.flattenToShortString(mComponentName) + "]";
}
/**
* Called by {@link Session} when it's time to destroy all augmented autofill requests.
*/
public void onDestroyAutofillWindowsRequest() {
scheduleAsyncRequest((s) -> s.onDestroyAllFillWindowsRequest());
}
private void dispatchOnFillTimeout(@NonNull ICancellationSignal cancellation) {
mHandler.post(() -> {
try {
cancellation.cancel();
} catch (RemoteException e) {
Slog.w(mTag, "Error calling cancellation signal: " + e);
}
});
}
// TODO(b/123100811): inline into PendingAutofillRequest if it doesn't have any other subclass
private abstract static class MyPendingRequest
extends PendingRequest<RemoteAugmentedAutofillService, IAugmentedAutofillService> {
protected final int mSessionId;
private MyPendingRequest(@NonNull RemoteAugmentedAutofillService service, int sessionId) {
super(service);
mSessionId = sessionId;
}
}
private static final class PendingAutofillRequest extends MyPendingRequest {
private final @NonNull AutofillId mFocusedId;
private final @Nullable AutofillValue mFocusedValue;
private final @NonNull IAutoFillManagerClient mClient;
private final @NonNull ComponentName mActivityComponent;
private final int mTaskId;
private final long mRequestTime = SystemClock.elapsedRealtime();
private final @NonNull IFillCallback mCallback;
private ICancellationSignal mCancellation;
protected PendingAutofillRequest(@NonNull RemoteAugmentedAutofillService service,
int sessionId, @NonNull IAutoFillManagerClient client, int taskId,
@NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
@Nullable AutofillValue focusedValue) {
super(service, sessionId);
mClient = client;
mTaskId = taskId;
mActivityComponent = activityComponent;
mFocusedId = focusedId;
mFocusedValue = focusedValue;
mCallback = new IFillCallback.Stub() {
@Override
public void onSuccess() {
if (!finish()) return;
// NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks
}
@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(mTag, "Error requesting a cancellation", e);
}
}
}
}
@Override
public boolean isCompleted() {
return isRequestCompleted();
}
@Override
public void cancel() {
synchronized (mLock) {
final boolean cancelled = isCancelledLocked();
final ICancellationSignal cancellation = mCancellation;
if (!cancelled) {
try {
cancellation.cancel();
} catch (RemoteException e) {
Slog.e(mTag, "Error requesting a cancellation", e);
}
}
}
}
};
}
@Override
public void run() {
synchronized (mLock) {
if (isCancelledLocked()) {
if (sDebug) Slog.d(mTag, "run() called after canceled");
return;
}
}
final RemoteAugmentedAutofillService remoteService = getService();
if (remoteService == null) return;
final IResultReceiver receiver = new IResultReceiver.Stub() {
@Override
public void send(int resultCode, Bundle resultData) throws RemoteException {
final IBinder realClient = resultData
.getBinder(AutofillManager.EXTRA_AUGMENTED_AUTOFILL_CLIENT);
remoteService.mService.onFillRequest(mSessionId, realClient, mTaskId,
mActivityComponent, mFocusedId, mFocusedValue, mRequestTime, mCallback);
}
};
// TODO(b/122728762): set cancellation signal, timeout (from both mClient and service),
// cache IAugmentedAutofillManagerClient reference, etc...
try {
mClient.getAugmentedAutofillClient(receiver);
} catch (RemoteException e) {
Slog.e(TAG, "exception handling getAugmentedAutofillClient() for "
+ mSessionId + ": " + e);
finish();
}
}
@Override
protected void onTimeout(RemoteAugmentedAutofillService remoteService) {
// TODO(b/122858578): must update the logged AUTOFILL_AUGMENTED_REQUEST with the
// timeout
Slog.w(TAG, "PendingAutofillRequest timed out (" + remoteService.mRequestTimeoutMs
+ "ms) for " + remoteService);
// NOTE: so far we don't need notify RemoteAugmentedAutofillServiceCallbacks
final ICancellationSignal cancellation;
synchronized (mLock) {
cancellation = mCancellation;
}
if (cancellation != null) {
remoteService.dispatchOnFillTimeout(cancellation);
}
finish();
}
@Override
public boolean cancel() {
if (!super.cancel()) return false;
final ICancellationSignal cancellation;
synchronized (mLock) {
cancellation = mCancellation;
}
if (cancellation != null) {
try {
cancellation.cancel();
} catch (RemoteException e) {
Slog.e(mTag, "Error cancelling a fill request", e);
}
}
return true;
}
fireAndForget((s) -> s.onDestroyAllFillWindowsRequest());
}
public interface RemoteAugmentedAutofillServiceCallbacks
extends VultureCallback<RemoteAugmentedAutofillService> {
extends AbstractRemoteService.VultureCallback<RemoteAugmentedAutofillService> {
// NOTE: so far we don't need to notify the callback implementation (an inner class on
// AutofillManagerServiceImpl) of the request results (success, timeouts, etc..), so this
// callback interface is empty.

View File

@@ -18,15 +18,15 @@ 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.IntentSender;
import android.os.IBinder;
import android.os.Handler;
import android.os.ICancellationSignal;
import android.os.RemoteException;
import android.service.autofill.AutofillService;
@@ -39,17 +39,30 @@ import android.service.autofill.SaveRequest;
import android.text.format.DateUtils;
import android.util.Slog;
import com.android.internal.infra.AbstractSinglePendingRequestRemoteService;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.ServiceConnector;
final class RemoteFillService
extends AbstractSinglePendingRequestRemoteService<RemoteFillService, IAutoFillService> {
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
final class RemoteFillService extends ServiceConnector.Impl<IAutoFillService> {
private static final String TAG = "RemoteFillService";
private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
private final FillServiceCallbacks mCallbacks;
private final Object mLock = new Object();
private CompletableFuture<FillResponse> mPendingFillRequest;
private int mPendingFillRequestId = INVALID_REQUEST_ID;
private final ComponentName mComponentName;
public interface FillServiceCallbacks extends VultureCallback<RemoteFillService> {
public interface FillServiceCallbacks
extends AbstractRemoteService.VultureCallback<RemoteFillService> {
void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
@NonNull String servicePackageName, int requestFlags);
void onFillRequestFailure(int requestId, @Nullable CharSequence message);
@@ -63,38 +76,51 @@ final class RemoteFillService
RemoteFillService(Context context, ComponentName componentName, int userId,
FillServiceCallbacks callbacks, boolean bindInstantServiceAllowed) {
super(context, AutofillService.SERVICE_INTERFACE, componentName, userId, callbacks,
context.getMainThreadHandler(), Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
| (bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0), sVerbose);
super(context, new Intent(AutofillService.SERVICE_INTERFACE).setComponent(componentName),
Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
| (bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0),
userId, IAutoFillService.Stub::asInterface);
mCallbacks = callbacks;
mComponentName = componentName;
}
@Override // from AbstractRemoteService
protected void handleOnConnectedStateChanged(boolean state) {
if (mService == null) {
Slog.w(mTag, "onConnectedStateChanged(): null service");
@Override // from ServiceConnector.Impl
protected void onServiceConnectionStatusChanged(IAutoFillService service, boolean connected) {
try {
service.onConnectedStateChanged(connected);
} catch (Exception e) {
Slog.w(TAG, "Exception calling onConnectedStateChanged(" + connected + "): " + e);
}
if (!connected) {
try {
mCallbacks.onServiceDied(this);
} catch (Exception e) {
Slog.w(TAG, "Exception calling onServiceDied(): " + e);
}
}
}
private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
if (signal == null) {
return;
}
try {
mService.onConnectedStateChanged(state);
} catch (Exception e) {
Slog.w(mTag, "Exception calling onConnectedStateChanged(" + state + "): " + e);
signal.cancel();
} catch (RemoteException e) {
Slog.e(TAG, "Error requesting a cancellation", e);
}
}
@Override // from AbstractRemoteService
protected IAutoFillService getServiceInterface(IBinder service) {
return IAutoFillService.Stub.asInterface(service);
}
@Override // from AbstractRemoteService
protected long getTimeoutIdleBindMillis() {
@Override // from ServiceConnector.Impl
protected long getAutoDisconnectTimeoutMs() {
return TIMEOUT_IDLE_BIND_MILLIS;
}
@Override // from AbstractRemoteService
protected long getRemoteRequestMillis() {
return TIMEOUT_REMOTE_REQUEST_MILLIS;
@Override // from ServiceConnector.Impl
public void addLast(Job<IAutoFillService, ?> iAutoFillServiceJob) {
// Only maintain single request at a time
cancelPendingJobs();
super.addLast(iAutoFillServiceJob);
}
/**
@@ -104,265 +130,109 @@ final class RemoteFillService
* will soon be queued.
*
* @return the id of the canceled request, or {@link FillRequest#INVALID_REQUEST_ID} if no
* {@link PendingFillRequest} was canceled.
* {@link FillRequest} was canceled.
*/
// TODO(b/117779333): move this logic to super class (and make mPendingRequest private)
public int cancelCurrentRequest() {
if (isDestroyed()) {
return INVALID_REQUEST_ID;
synchronized (mLock) {
return mPendingFillRequest != null && mPendingFillRequest.cancel(false)
? mPendingFillRequestId
: 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) {
scheduleRequest(new PendingFillRequest(request, this));
}
AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
AtomicReference<CompletableFuture<FillResponse>> futureRef = new AtomicReference<>();
public void onSaveRequest(@NonNull SaveRequest request) {
scheduleRequest(new PendingSaveRequest(request, this));
}
private boolean handleResponseCallbackCommon(
@NonNull PendingRequest<RemoteFillService, IAutoFillService> pendingRequest) {
if (isDestroyed()) return false;
if (mPendingRequest == pendingRequest) {
mPendingRequest = null;
}
return true;
}
private void dispatchOnFillRequestSuccess(@NonNull PendingFillRequest pendingRequest,
@Nullable FillResponse response, int requestFlags) {
mHandler.post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onFillRequestSuccess(pendingRequest.mRequest.getId(), response,
mComponentName.getPackageName(), requestFlags);
CompletableFuture<FillResponse> connectThenFillRequest = postAsync(remoteService -> {
if (sVerbose) {
Slog.v(TAG, "calling onFillRequest() for id=" + request.getId());
}
});
}
private void dispatchOnFillRequestFailure(@NonNull PendingFillRequest pendingRequest,
@Nullable CharSequence message) {
mHandler.post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onFillRequestFailure(pendingRequest.mRequest.getId(), message);
}
});
}
private void dispatchOnFillRequestTimeout(@NonNull PendingFillRequest pendingRequest) {
mHandler.post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onFillRequestTimeout(pendingRequest.mRequest.getId());
}
});
}
private void dispatchOnFillTimeout(@NonNull ICancellationSignal cancellationSignal) {
mHandler.post(() -> {
try {
cancellationSignal.cancel();
} catch (RemoteException e) {
Slog.w(mTag, "Error calling cancellation signal: " + e);
}
});
}
private void dispatchOnSaveRequestSuccess(PendingSaveRequest pendingRequest,
IntentSender intentSender) {
mHandler.post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), intentSender);
}
});
}
private void dispatchOnSaveRequestFailure(PendingSaveRequest pendingRequest,
@Nullable CharSequence message) {
mHandler.post(() -> {
if (handleResponseCallbackCommon(pendingRequest)) {
mCallbacks.onSaveRequestFailure(message, mComponentName.getPackageName());
}
});
}
private static final class PendingFillRequest
extends PendingRequest<RemoteFillService, IAutoFillService> {
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() {
CompletableFuture<FillResponse> fillRequest = new CompletableFuture<>();
remoteService.onFillRequest(request, 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(mTag, "Error requesting a cancellation", e);
}
}
CompletableFuture<FillResponse> future = futureRef.get();
if (future != null && future.isCancelled()) {
dispatchCancellationSignal(cancellation);
} else {
cancellationSink.set(cancellation);
}
}
@Override
public void onSuccess(FillResponse response) {
if (!finish()) return;
final RemoteFillService remoteService = getService();
if (remoteService != null) {
remoteService.dispatchOnFillRequestSuccess(PendingFillRequest.this,
response, request.getFlags());
}
fillRequest.complete(response);
}
@Override
public void onFailure(int requestId, CharSequence message) {
if (!finish()) return;
fillRequest.completeExceptionally(
new RuntimeException(String.valueOf(message)));
}
});
return fillRequest;
}).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
futureRef.set(connectThenFillRequest);
final RemoteFillService remoteService = getService();
if (remoteService != null) {
remoteService.dispatchOnFillRequestFailure(PendingFillRequest.this,
message);
synchronized (mLock) {
mPendingFillRequest = connectThenFillRequest;
mPendingFillRequestId = request.getId();
}
connectThenFillRequest.whenComplete((res, err) -> Handler.getMain().post(() -> {
synchronized (mLock) {
mPendingFillRequest = null;
mPendingFillRequestId = INVALID_REQUEST_ID;
}
if (err == null) {
mCallbacks.onFillRequestSuccess(request.getId(), res,
mComponentName.getPackageName(), request.getFlags());
} else {
Slog.e(TAG, "Error calling on fill request", err);
if (err instanceof TimeoutException) {
dispatchCancellationSignal(cancellationSink.get());
mCallbacks.onFillRequestTimeout(request.getId());
} else {
if (err instanceof CancellationException) {
dispatchCancellationSignal(cancellationSink.get());
}
}
};
}
@Override
protected void onTimeout(RemoteFillService remoteService) {
// NOTE: Must make these 2 calls asynchronously, because the cancellation signal is
// handled by the service, which could block.
final ICancellationSignal cancellation;
synchronized (mLock) {
cancellation = mCancellation;
}
if (cancellation != null) {
remoteService.dispatchOnFillTimeout(cancellation);
}
remoteService.dispatchOnFillRequestTimeout(PendingFillRequest.this);
}
@Override
public void run() {
synchronized (mLock) {
if (isCancelledLocked()) {
if (sDebug) Slog.d(mTag, "run() called after canceled: " + mRequest);
return;
mCallbacks.onFillRequestFailure(request.getId(), err.getMessage());
}
}
final RemoteFillService remoteService = getService();
if (remoteService != null) {
if (sVerbose) Slog.v(mTag, "calling onFillRequest() for id=" + mRequest.getId());
try {
remoteService.mService.onFillRequest(mRequest, mCallback);
} catch (RemoteException e) {
Slog.e(mTag, "Error calling on fill request", e);
remoteService.dispatchOnFillRequestFailure(PendingFillRequest.this, null);
}
}
}
@Override
public boolean cancel() {
if (!super.cancel()) return false;
final ICancellationSignal cancellation;
synchronized (mLock) {
cancellation = mCancellation;
}
if (cancellation != null) {
try {
cancellation.cancel();
} catch (RemoteException e) {
Slog.e(mTag, "Error cancelling a fill request", e);
}
}
return true;
}
}));
}
private static final class PendingSaveRequest
extends PendingRequest<RemoteFillService, IAutoFillService> {
private final SaveRequest mRequest;
private final ISaveCallback mCallback;
public void onSaveRequest(@NonNull SaveRequest request) {
postAsync(service -> {
if (sVerbose) Slog.v(TAG, "calling onSaveRequest()");
public PendingSaveRequest(@NonNull SaveRequest request,
@NonNull RemoteFillService service) {
super(service);
mRequest = request;
mCallback = new ISaveCallback.Stub() {
CompletableFuture<IntentSender> save = new CompletableFuture<>();
service.onSaveRequest(request, new ISaveCallback.Stub() {
@Override
public void onSuccess(IntentSender intentSender) {
if (!finish()) return;
final RemoteFillService remoteService = getService();
if (remoteService != null) {
remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this,
intentSender);
}
save.complete(intentSender);
}
@Override
public void onFailure(CharSequence message) {
if (!finish()) return;
final RemoteFillService remoteService = getService();
if (remoteService != null) {
remoteService.dispatchOnSaveRequestFailure(PendingSaveRequest.this,
message);
save.completeExceptionally(new RuntimeException(String.valueOf(message)));
}
});
return save;
}).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS)
.whenComplete((res, err) -> Handler.getMain().post(() -> {
if (err == null) {
mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), res);
} else {
mCallbacks.onSaveRequestFailure(
mComponentName.getPackageName(), err.getMessage());
}
}
};
}
}));
}
@Override
protected void onTimeout(RemoteFillService remoteService) {
remoteService.dispatchOnSaveRequestFailure(PendingSaveRequest.this, null);
}
@Override
public void run() {
final RemoteFillService remoteService = getService();
if (remoteService != null) {
if (sVerbose) Slog.v(mTag, "calling onSaveRequest()");
try {
remoteService.mService.onSaveRequest(mRequest, mCallback);
} catch (RemoteException e) {
Slog.e(mTag, "Error calling on save request", e);
remoteService.dispatchOnSaveRequestFailure(PendingSaveRequest.this, null);
}
}
}
@Override
public boolean isFinal() {
return true;
}
public void destroy() {
unbind();
}
}