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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
226
core/java/com/android/internal/infra/AndroidFuture.java
Normal file
226
core/java/com/android/internal/infra/AndroidFuture.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
699
core/java/com/android/internal/infra/ServiceConnector.java
Normal file
699
core/java/com/android/internal/infra/ServiceConnector.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user