IMF: Factor out OnceResultReceiver to fix TextBoundsInfoResultReceiver

Fixes an issue with TextBoundsInfoResultReceiver, where any Executor
that does not run the Runnable directly crashes, because the mConsumer
is cleared before it is run. Also avoids calling into the executor
with internal locks held.

Bug: 265457058
Test: atest InputConnectionEndToEndTest
Change-Id: I4b229494fb2451135ae1075272f04d865a9b509e
This commit is contained in:
Adrian Roos
2023-01-20 10:23:16 +01:00
parent 687a05d319
commit 953d9873f4

View File

@@ -66,18 +66,13 @@ final class IRemoteInputConnectionInvoker {
mSessionId = sessionId;
}
/**
* Subclass of {@link ResultReceiver} used by
* {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)} for providing
* callback.
*/
private static final class IntResultReceiver extends ResultReceiver {
private abstract static class OnceResultReceiver<C> extends ResultReceiver {
@Nullable
private IntConsumer mConsumer;
private C mConsumer;
@Nullable
private Executor mExecutor;
IntResultReceiver(@NonNull Executor executor, @NonNull IntConsumer consumer) {
protected OnceResultReceiver(@NonNull Executor executor, @NonNull C consumer) {
super(null);
Objects.requireNonNull(executor);
Objects.requireNonNull(consumer);
@@ -86,9 +81,9 @@ final class IRemoteInputConnectionInvoker {
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
protected final void onReceiveResult(int resultCode, Bundle resultData) {
final Executor executor;
final IntConsumer consumer;
final C consumer;
synchronized (this) {
executor = mExecutor;
consumer = mConsumer;
@@ -96,46 +91,49 @@ final class IRemoteInputConnectionInvoker {
mConsumer = null;
}
if (executor != null && consumer != null) {
executor.execute(() -> consumer.accept(resultCode));
dispatch(executor, consumer, resultCode, resultData);
}
}
};
protected abstract void dispatch(@NonNull Executor executor, @NonNull C consumer, int code,
Bundle data);
}
/**
* Subclass of {@link ResultReceiver} used by
* {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)} for providing
* callback.
*/
private static final class IntResultReceiver extends OnceResultReceiver<IntConsumer> {
IntResultReceiver(@NonNull Executor executor, @NonNull IntConsumer consumer) {
super(executor, consumer);
}
@Override
protected void dispatch(@NonNull Executor executor, @NonNull IntConsumer consumer, int code,
Bundle data) {
executor.execute(() -> consumer.accept(code));
}
}
/**
* Subclass of {@link ResultReceiver} used by
* {@link #requestTextBoundsInfo(RectF, Executor, Consumer)} for providing
* callback.
*/
private static final class TextBoundsInfoResultReceiver extends ResultReceiver {
@Nullable
private Consumer<TextBoundsInfoResult> mConsumer;
@Nullable
private Executor mExecutor;
private static final class TextBoundsInfoResultReceiver extends
OnceResultReceiver<Consumer<TextBoundsInfoResult>> {
TextBoundsInfoResultReceiver(@NonNull Executor executor,
@NonNull Consumer<TextBoundsInfoResult> consumer) {
super(null);
mExecutor = executor;
mConsumer = consumer;
super(executor, consumer);
}
@Override
protected void onReceiveResult(@TextBoundsInfoResult.ResultCode int resultCode,
@Nullable Bundle resultData) {
synchronized (this) {
if (mExecutor != null && mConsumer != null) {
final TextBoundsInfoResult textBoundsInfoResult = new TextBoundsInfoResult(
resultCode, TextBoundsInfo.createFromBundle(resultData));
mExecutor.execute(() -> mConsumer.accept(textBoundsInfoResult));
// provide callback only once.
clear();
}
}
}
private void clear() {
mExecutor = null;
mConsumer = null;
protected void dispatch(@NonNull Executor executor,
@NonNull Consumer<TextBoundsInfoResult> consumer, int code, Bundle data) {
final TextBoundsInfoResult textBoundsInfoResult = new TextBoundsInfoResult(
code, TextBoundsInfo.createFromBundle(data));
executor.execute(() -> consumer.accept(textBoundsInfoResult));
}
}