diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index ec5bcf1f1199b..464b421d970d5 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -34,7 +34,7 @@ import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.SurroundingText;
import com.android.internal.inputmethod.CancellationGroup;
-import com.android.internal.inputmethod.Completable;
+import com.android.internal.inputmethod.CompletableFutureUtil;
import com.android.internal.inputmethod.IInputContextInvoker;
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.InputConnectionProtoDumper;
@@ -42,6 +42,7 @@ import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import java.lang.ref.WeakReference;
+import java.util.concurrent.CompletableFuture;
/**
* Takes care of remote method invocations of {@link InputConnection} in the IME side.
@@ -96,8 +97,8 @@ final class RemoteInputConnection implements InputConnection {
return null;
}
- final Completable.CharSequence value = mInvoker.getTextAfterCursor(length, flags);
- final CharSequence result = Completable.getResultOrNull(
+ final CompletableFuture value = mInvoker.getTextAfterCursor(length, flags);
+ final CharSequence result = CompletableFutureUtil.getResultOrNull(
value, TAG, "getTextAfterCursor()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
final InputMethodServiceInternal inputMethodService = mInputMethodService.get();
@@ -120,8 +121,8 @@ final class RemoteInputConnection implements InputConnection {
return null;
}
- final Completable.CharSequence value = mInvoker.getTextBeforeCursor(length, flags);
- final CharSequence result = Completable.getResultOrNull(
+ final CompletableFuture value = mInvoker.getTextBeforeCursor(length, flags);
+ final CharSequence result = CompletableFutureUtil.getResultOrNull(
value, TAG, "getTextBeforeCursor()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
final InputMethodServiceInternal inputMethodService = mInputMethodService.get();
@@ -144,8 +145,8 @@ final class RemoteInputConnection implements InputConnection {
// This method is not implemented.
return null;
}
- final Completable.CharSequence value = mInvoker.getSelectedText(flags);
- final CharSequence result = Completable.getResultOrNull(
+ final CompletableFuture value = mInvoker.getSelectedText(flags);
+ final CharSequence result = CompletableFutureUtil.getResultOrNull(
value, TAG, "getSelectedText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
final InputMethodServiceInternal inputMethodService = mInputMethodService.get();
@@ -181,9 +182,9 @@ final class RemoteInputConnection implements InputConnection {
// This method is not implemented.
return null;
}
- final Completable.SurroundingText value = mInvoker.getSurroundingText(beforeLength,
+ final CompletableFuture value = mInvoker.getSurroundingText(beforeLength,
afterLength, flags);
- final SurroundingText result = Completable.getResultOrNull(
+ final SurroundingText result = CompletableFutureUtil.getResultOrNull(
value, TAG, "getSurroundingText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
final InputMethodServiceInternal inputMethodService = mInputMethodService.get();
@@ -202,8 +203,8 @@ final class RemoteInputConnection implements InputConnection {
return 0;
}
- final Completable.Int value = mInvoker.getCursorCapsMode(reqModes);
- final int result = Completable.getResultOrZero(
+ final CompletableFuture value = mInvoker.getCursorCapsMode(reqModes);
+ final int result = CompletableFutureUtil.getResultOrZero(
value, TAG, "getCursorCapsMode()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
final InputMethodServiceInternal inputMethodService = mInputMethodService.get();
@@ -222,8 +223,8 @@ final class RemoteInputConnection implements InputConnection {
return null;
}
- final Completable.ExtractedText value = mInvoker.getExtractedText(request, flags);
- final ExtractedText result = Completable.getResultOrNull(
+ final CompletableFuture value = mInvoker.getExtractedText(request, flags);
+ final ExtractedText result = CompletableFutureUtil.getResultOrNull(
value, TAG, "getExtractedText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
final InputMethodServiceInternal inputMethodService = mInputMethodService.get();
@@ -371,8 +372,8 @@ final class RemoteInputConnection implements InputConnection {
// This method is not implemented.
return false;
}
- final Completable.Boolean value = mInvoker.requestCursorUpdates(cursorUpdateMode);
- return Completable.getResultOrFalse(value, TAG, "requestCursorUpdates()",
+ final CompletableFuture value = mInvoker.requestCursorUpdates(cursorUpdateMode);
+ return CompletableFutureUtil.getResultOrFalse(value, TAG, "requestCursorUpdates()",
mCancellationGroup, MAX_WAIT_TIME_MILLIS);
}
@@ -407,8 +408,9 @@ final class RemoteInputConnection implements InputConnection {
inputMethodService.exposeContent(inputContentInfo, this);
}
- final Completable.Boolean value = mInvoker.commitContent(inputContentInfo, flags, opts);
- return Completable.getResultOrFalse(
+ final CompletableFuture value =
+ mInvoker.commitContent(inputContentInfo, flags, opts);
+ return CompletableFutureUtil.getResultOrFalse(
value, TAG, "commitContent()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
}
diff --git a/core/java/com/android/internal/inputmethod/CancellationGroup.java b/core/java/com/android/internal/inputmethod/CancellationGroup.java
index aef9e3b9a0fc1..3b2e1cd9b556a 100644
--- a/core/java/com/android/internal/inputmethod/CancellationGroup.java
+++ b/core/java/com/android/internal/inputmethod/CancellationGroup.java
@@ -23,49 +23,66 @@ import android.annotation.Nullable;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CompletableFuture;
/**
* A utility class, which works as both a factory class of a cancellation signal to cancel
* all the completable objects.
+ *
+ * TODO: Make this lock-free.
*/
public final class CancellationGroup {
private final Object mLock = new Object();
/**
- * List of {@link CountDownLatch}, which can be used to propagate {@link #cancelAll()} to
+ * List of {@link CompletableFuture}, which can be used to propagate {@link #cancelAll()} to
* completable objects.
*
* This will be lazily instantiated to avoid unnecessary object allocations.
*/
@Nullable
@GuardedBy("mLock")
- private ArrayList mLatchList = null;
+ private ArrayList> mFutureList = null;
@GuardedBy("mLock")
private boolean mCanceled = false;
+ /**
+ * Tries to register the given {@link CompletableFuture} into the callback list if this
+ * {@link CancellationGroup} is not yet cancelled.
+ *
+ * If this {@link CancellationGroup} is already cancelled, then this method will immediately
+ * call {@link CompletableFuture#cancel(boolean)} then return {@code false}.
+ *
+ * When this method returns {@code true}, call {@link #unregisterFuture(CompletableFuture)}
+ * to remove the unnecessary object reference.
+ *
+ * @param future {@link CompletableFuture} to be added to the cancellation callback list.
+ * @return {@code true} if the given {@code future} is added to the callback list.
+ * {@code false} otherwise.
+ */
@AnyThread
- boolean registerLatch(@NonNull CountDownLatch latch) {
+ boolean tryRegisterFutureOrCancelImmediately(@NonNull CompletableFuture> future) {
synchronized (mLock) {
if (mCanceled) {
+ future.cancel(false);
return false;
}
- if (mLatchList == null) {
+ if (mFutureList == null) {
// Set the initial capacity to 1 with an assumption that usually there is up to 1
// on-going operation.
- mLatchList = new ArrayList<>(1);
+ mFutureList = new ArrayList<>(1);
}
- mLatchList.add(latch);
+ mFutureList.add(future);
return true;
}
}
@AnyThread
- void unregisterLatch(@NonNull CountDownLatch latch) {
+ void unregisterFuture(@NonNull CompletableFuture> future) {
synchronized (mLock) {
- if (mLatchList != null) {
- mLatchList.remove(latch);
+ if (mFutureList != null) {
+ mFutureList.remove(future);
}
}
}
@@ -80,10 +97,10 @@ public final class CancellationGroup {
synchronized (mLock) {
if (!mCanceled) {
mCanceled = true;
- if (mLatchList != null) {
- mLatchList.forEach(CountDownLatch::countDown);
- mLatchList.clear();
- mLatchList = null;
+ if (mFutureList != null) {
+ mFutureList.forEach(future -> future.cancel(false));
+ mFutureList.clear();
+ mFutureList = null;
}
}
}
diff --git a/core/java/com/android/internal/inputmethod/Completable.java b/core/java/com/android/internal/inputmethod/Completable.java
deleted file mode 100644
index 132272c68ca86..0000000000000
--- a/core/java/com/android/internal/inputmethod/Completable.java
+++ /dev/null
@@ -1,558 +0,0 @@
-/*
- * Copyright (C) 2020 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.inputmethod;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.AnyThread;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.lang.annotation.Retention;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * An class to consolidate completable object types supported by
- * {@link CancellationGroup}.
- */
-public final class Completable {
-
- /**
- * Not intended to be instantiated.
- */
- private Completable() {
- }
-
- /**
- * Base class of all the completable types supported by {@link CancellationGroup}.
- */
- protected static class ValueBase {
- /**
- * {@link CountDownLatch} to be signaled to unblock
- * {@link #await(int, TimeUnit, CancellationGroup)}.
- */
- private final CountDownLatch mLatch = new CountDownLatch(1);
-
- /**
- * Lock {@link Object} to guard complete operations within this class.
- */
- protected final Object mStateLock = new Object();
-
- /**
- * Indicates the completion state of this object.
- */
- @GuardedBy("mStateLock")
- @CompletionState
- protected int mState = CompletionState.NOT_COMPLETED;
-
- /**
- * {@link Throwable} message passed to {@link #onError(ThrowableHolder)}.
- *
- * This is not {@code null} only when {@link #mState} is
- * {@link CompletionState#COMPLETED_WITH_ERROR}.
- */
- @GuardedBy("mStateLock")
- @Nullable
- protected String mMessage = null;
-
- @Retention(SOURCE)
- @IntDef({
- CompletionState.NOT_COMPLETED,
- CompletionState.COMPLETED_WITH_VALUE,
- CompletionState.COMPLETED_WITH_ERROR})
- protected @interface CompletionState {
- /**
- * This object is not completed yet.
- */
- int NOT_COMPLETED = 0;
- /**
- * This object is already completed with a value.
- */
- int COMPLETED_WITH_VALUE = 1;
- /**
- * This object is already completed with an error.
- */
- int COMPLETED_WITH_ERROR = 2;
- }
-
- /**
- * Converts the given {@link CompletionState} into a human-readable string.
- *
- * @param state {@link CompletionState} to be converted.
- * @return a human-readable {@link String} for the given {@code state}.
- */
- @AnyThread
- protected static String stateToString(@CompletionState int state) {
- switch (state) {
- case CompletionState.NOT_COMPLETED:
- return "NOT_COMPLETED";
- case CompletionState.COMPLETED_WITH_VALUE:
- return "COMPLETED_WITH_VALUE";
- case CompletionState.COMPLETED_WITH_ERROR:
- return "COMPLETED_WITH_ERROR";
- default:
- return "Unknown(value=" + state + ")";
- }
- }
-
- /**
- * @return {@code true} if {@link #onComplete()} gets called and {@link #mState} is
- * {@link CompletionState#COMPLETED_WITH_VALUE}.
- */
- @AnyThread
- public boolean hasValue() {
- synchronized (mStateLock) {
- return mState == CompletionState.COMPLETED_WITH_VALUE;
- }
- }
-
- /**
- * Provides the base implementation of {@code getValue()} for derived classes.
- *
- * Must be called after acquiring {@link #mStateLock}.
- *
- * @throws RuntimeException when {@link #mState} is
- * {@link CompletionState#COMPLETED_WITH_ERROR}.
- * @throws UnsupportedOperationException when {@link #mState} is not
- * {@link CompletionState#COMPLETED_WITH_VALUE} and
- * {@link CompletionState#COMPLETED_WITH_ERROR}.
- */
- @GuardedBy("mStateLock")
- protected void enforceGetValueLocked() {
- switch (mState) {
- case CompletionState.NOT_COMPLETED:
- throw new UnsupportedOperationException(
- "getValue() is allowed only if hasValue() returns true");
- case CompletionState.COMPLETED_WITH_VALUE:
- return;
- case CompletionState.COMPLETED_WITH_ERROR:
- throw new RuntimeException(mMessage);
- default:
- throw new UnsupportedOperationException(
- "getValue() is not allowed on state=" + stateToString(mState));
- }
- }
-
- /**
- * Called by subclasses to signale {@link #mLatch}.
- */
- @AnyThread
- protected void onComplete() {
- mLatch.countDown();
- }
-
- /**
- * Notify when exception happened.
- *
- * @param throwableHolder contains the {@link Throwable} object when exception happened.
- */
- @AnyThread
- protected void onError(ThrowableHolder throwableHolder) {
- synchronized (mStateLock) {
- switch (mState) {
- case CompletionState.NOT_COMPLETED:
- mMessage = throwableHolder.getMessage();
- mState = CompletionState.COMPLETED_WITH_ERROR;
- break;
- default:
- throw new UnsupportedOperationException(
- "onError() is not allowed on state=" + stateToString(mState));
- }
- }
- onComplete();
- }
-
- /**
- * Blocks the calling thread until at least one of the following conditions is met.
- *
- *
- *
- * - This object becomes ready to return the value.
- * - {@link CancellationGroup#cancelAll()} gets called.
- * - The given timeout period has passed.
- *
- *
- *
- * The caller can distinguish the case 1 and case 2 by calling {@link #hasValue()}.
- * Note that the return value of {@link #hasValue()} can change from {@code false} to
- * {@code true} at any time, even after this methods finishes with returning
- * {@code true}.
- *
- * @param timeout length of the timeout.
- * @param timeUnit unit of {@code timeout}.
- * @param cancellationGroup {@link CancellationGroup} to cancel completable objects.
- * @return {@code false} if and only if the given timeout period has passed. Otherwise
- * {@code true}.
- */
- @AnyThread
- public boolean await(int timeout, @NonNull TimeUnit timeUnit,
- @Nullable CancellationGroup cancellationGroup) {
- if (cancellationGroup == null) {
- return awaitInner(timeout, timeUnit);
- }
-
- if (!cancellationGroup.registerLatch(mLatch)) {
- // Already canceled when this method gets called.
- return false;
- }
- try {
- return awaitInner(timeout, timeUnit);
- } finally {
- cancellationGroup.unregisterLatch(mLatch);
- }
- }
-
- private boolean awaitInner(int timeout, @NonNull TimeUnit timeUnit) {
- try {
- return mLatch.await(timeout, timeUnit);
- } catch (InterruptedException e) {
- return true;
- }
- }
-
- /**
- * Blocks the calling thread until this object becomes ready to return the value, even if
- * {@link InterruptedException} is thrown.
- */
- @AnyThread
- public void await() {
- boolean interrupted = false;
- while (true) {
- try {
- mLatch.await();
- break;
- } catch (InterruptedException ignored) {
- interrupted = true;
- }
- }
-
- if (interrupted) {
- // Try to preserve the interrupt bit on this thread.
- Thread.currentThread().interrupt();
- }
- }
- }
-
- /**
- * Completable object of integer primitive.
- */
- public static final class Int extends ValueBase {
- @GuardedBy("mStateLock")
- private int mValue = 0;
-
- /**
- * Notify when a value is set to this completable object.
- *
- * @param value value to be set.
- */
- @AnyThread
- void onComplete(int value) {
- synchronized (mStateLock) {
- switch (mState) {
- case CompletionState.NOT_COMPLETED:
- mValue = value;
- mState = CompletionState.COMPLETED_WITH_VALUE;
- break;
- default:
- throw new UnsupportedOperationException(
- "onComplete() is not allowed on state=" + stateToString(mState));
- }
- }
- onComplete();
- }
-
- /**
- * @return value associated with this object.
- * @throws RuntimeException when called while {@link #onError} happened.
- * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
- * {@code false}.
- */
- @AnyThread
- public int getValue() {
- synchronized (mStateLock) {
- enforceGetValueLocked();
- return mValue;
- }
- }
- }
-
- /**
- * Completable object of {@link java.lang.Void}.
- */
- public static final class Void extends ValueBase {
- /**
- * Notify when this completable object callback.
- */
- @AnyThread
- @Override
- protected void onComplete() {
- synchronized (mStateLock) {
- switch (mState) {
- case CompletionState.NOT_COMPLETED:
- mState = CompletionState.COMPLETED_WITH_VALUE;
- break;
- default:
- throw new UnsupportedOperationException(
- "onComplete() is not allowed on state=" + stateToString(mState));
- }
- }
- super.onComplete();
- }
-
- /**
- * @throws RuntimeException when called while {@link #onError} happened.
- * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
- * {@code false}.
- */
- @AnyThread
- public void getValue() {
- synchronized (mStateLock) {
- enforceGetValueLocked();
- }
- }
- }
-
- /**
- * Base class of completable object types.
- *
- * @param type associated with this completable object.
- */
- public static class Values extends ValueBase {
- @GuardedBy("mStateLock")
- @Nullable
- private T mValue = null;
-
- /**
- * Notify when a value is set to this completable value object.
- *
- * @param value value to be set.
- */
- @AnyThread
- void onComplete(@Nullable T value) {
- synchronized (mStateLock) {
- switch (mState) {
- case CompletionState.NOT_COMPLETED:
- mValue = value;
- mState = CompletionState.COMPLETED_WITH_VALUE;
- break;
- default:
- throw new UnsupportedOperationException(
- "onComplete() is not allowed on state=" + stateToString(mState));
- }
- }
- onComplete();
- }
-
- /**
- * @return value associated with this object.
- * @throws RuntimeException when called while {@link #onError} happened
- * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
- * {@code false}.
- */
- @AnyThread
- @Nullable
- public T getValue() {
- synchronized (mStateLock) {
- enforceGetValueLocked();
- return mValue;
- }
- }
- }
-
- /**
- * @return an instance of {@link Completable.Int}.
- */
- public static Completable.Int createInt() {
- return new Completable.Int();
- }
-
- /**
- * @return an instance of {@link Completable.Boolean}.
- */
- public static Completable.Boolean createBoolean() {
- return new Completable.Boolean();
- }
-
- /**
- * @return an instance of {@link Completable.CharSequence}.
- */
- public static Completable.CharSequence createCharSequence() {
- return new Completable.CharSequence();
- }
-
- /**
- * @return an instance of {@link Completable.ExtractedText}.
- */
- public static Completable.ExtractedText createExtractedText() {
- return new Completable.ExtractedText();
- }
-
- /**
- * @return an instance of {@link Completable.SurroundingText}.
- */
- public static Completable.SurroundingText createSurroundingText() {
- return new Completable.SurroundingText();
- }
-
- /**
- * @return an instance of {@link Completable.IInputContentUriToken}.
- */
- public static Completable.IInputContentUriToken createIInputContentUriToken() {
- return new Completable.IInputContentUriToken();
- }
-
- /**
- * @return an instance of {@link Completable.Void}.
- */
- public static Completable.Void createVoid() {
- return new Completable.Void();
- }
-
- /**
- * Completable object of {@link java.lang.Boolean}.
- */
- public static final class Boolean extends Values { }
-
- /**
- * Completable object of {@link java.lang.CharSequence}.
- */
- public static final class CharSequence extends Values { }
-
- /**
- * Completable object of {@link android.view.inputmethod.ExtractedText}.
- */
- public static final class ExtractedText
- extends Values { }
-
- /**
- * Completable object of {@link android.view.inputmethod.SurroundingText}.
- */
- public static final class SurroundingText
- extends Values { }
-
- /**
- * Completable object of {@link IInputContentUriToken>}.
- */
- public static final class IInputContentUriToken
- extends Values { }
-
- /**
- * Await the result by the {@link Completable.Values}.
- *
- * @return the result once {@link ValueBase#onComplete()}.
- */
- @AnyThread
- @Nullable
- public static T getResult(@NonNull Completable.Values value) {
- value.await();
- return value.getValue();
- }
-
- /**
- * Await the int result by the {@link Completable.Int}.
- *
- * @return the result once {@link ValueBase#onComplete()}.
- */
- @AnyThread
- public static int getIntResult(@NonNull Completable.Int value) {
- value.await();
- return value.getValue();
- }
-
- /**
- * Await the result by the {@link Completable.Void}.
- *
- * Check the result once {@link ValueBase#onComplete()}
- */
- @AnyThread
- public static void getResult(@NonNull Completable.Void value) {
- value.await();
- value.getValue();
- }
-
- /**
- * Await the result by the {@link Completable.Boolean}, and log it if there is no result after
- * given timeout.
- *
- * @return the result once {@link ValueBase#onComplete()}
- */
- @AnyThread
- public static boolean getResultOrFalse(@NonNull Completable.Boolean value, String tag,
- @NonNull String methodName, @Nullable CancellationGroup cancellationGroup,
- int maxWaitTime) {
- final boolean timedOut = value.await(maxWaitTime, TimeUnit.MILLISECONDS, cancellationGroup);
- if (value.hasValue()) {
- return value.getValue();
- }
- logInternal(tag, methodName, timedOut, maxWaitTime, 0);
- return false;
- }
-
- /**
- * Await the result by the {@link Completable.Int}, and log it if there is no result after
- * given timeout.
- *
- * @return the result once {@link ValueBase#onComplete()}
- */
- @AnyThread
- public static int getResultOrZero(@NonNull Completable.Int value, String tag,
- @NonNull String methodName, @Nullable CancellationGroup cancellationGroup,
- int maxWaitTime) {
- final boolean timedOut = value.await(maxWaitTime, TimeUnit.MILLISECONDS, cancellationGroup);
- if (value.hasValue()) {
- return value.getValue();
- }
- logInternal(tag, methodName, timedOut, maxWaitTime, 0);
- return 0;
- }
-
- /**
- * Await the result by the {@link Completable.Values}, and log it if there is no result after
- * given timeout.
- *
- * @return the result once {@link ValueBase#onComplete()}
- */
- @AnyThread
- @Nullable
- public static T getResultOrNull(@NonNull Completable.Values value, String tag,
- @NonNull String methodName, @Nullable CancellationGroup cancellationGroup,
- int maxWaitTime) {
- final boolean timedOut = value.await(maxWaitTime, TimeUnit.MILLISECONDS, cancellationGroup);
- if (value.hasValue()) {
- return value.getValue();
- }
- logInternal(tag, methodName, timedOut, maxWaitTime, null);
- return null;
- }
-
- @AnyThread
- private static void logInternal(String tag, @Nullable String methodName, boolean timedOut,
- int maxWaitTime, @Nullable Object defaultValue) {
- if (timedOut) {
- Log.w(tag, methodName + " didn't respond in " + maxWaitTime + " msec."
- + " Returning default: " + defaultValue);
- } else {
- Log.w(tag, methodName + " was canceled before complete. Returning default: "
- + defaultValue);
- }
- }
-}
diff --git a/core/java/com/android/internal/inputmethod/CompletableFutureUtil.java b/core/java/com/android/internal/inputmethod/CompletableFutureUtil.java
new file mode 100644
index 0000000000000..ec10e014384a4
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/CompletableFutureUtil.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2021 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.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A set of helper methods to retrieve result values from {@link CompletableFuture}.
+ */
+public final class CompletableFutureUtil {
+ /**
+ * Not intended to be instantiated.
+ */
+ private CompletableFutureUtil() {
+ }
+
+ @AnyThread
+ @Nullable
+ private static T getValueOrRethrowErrorInternal(@NonNull CompletableFuture future) {
+ boolean interrupted = false;
+ try {
+ while (true) {
+ try {
+ return future.get();
+ } catch (ExecutionException e) {
+ final Throwable cause = e.getCause();
+ throw new RuntimeException(cause.getMessage(), cause.getCause());
+ } catch (InterruptedException e) {
+ interrupted = true;
+ }
+ }
+ } finally {
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ @AnyThread
+ @Nullable
+ private static T getValueOrNullInternal(@NonNull CompletableFuture future,
+ @Nullable String tag, @Nullable String methodName,
+ @DurationMillisLong long timeoutMillis, @Nullable CancellationGroup cancellationGroup) {
+ // We intentionally do not use CompletableFuture.anyOf() to avoid additional object
+ // allocations.
+ final boolean needsToUnregister = cancellationGroup != null
+ && cancellationGroup.tryRegisterFutureOrCancelImmediately(future);
+ boolean interrupted = false;
+ try {
+ while (true) {
+ try {
+ return future.get(timeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (CompletionException e) {
+ if (e.getCause() instanceof CancellationException) {
+ logCancellationInternal(tag, methodName);
+ return null;
+ }
+ logErrorInternal(tag, methodName, e.getMessage());
+ return null;
+ } catch (CancellationException e) {
+ logCancellationInternal(tag, methodName);
+ return null;
+ } catch (InterruptedException e) {
+ interrupted = true;
+ } catch (TimeoutException e) {
+ logTimeoutInternal(tag, methodName, timeoutMillis);
+ return null;
+ } catch (Throwable e) {
+ logErrorInternal(tag, methodName, e.getMessage());
+ return null;
+ }
+ }
+ } finally {
+ if (needsToUnregister) {
+ cancellationGroup.unregisterFuture(future);
+ }
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
+ @AnyThread
+ private static void logTimeoutInternal(@Nullable String tag, @Nullable String methodName,
+ @DurationMillisLong long timeout) {
+ if (tag == null || methodName == null) {
+ return;
+ }
+ Log.w(tag, methodName + " didn't respond in " + timeout + " msec.");
+ }
+
+ @AnyThread
+ private static void logErrorInternal(@Nullable String tag, @Nullable String methodName,
+ @Nullable String errorString) {
+ if (tag == null || methodName == null) {
+ return;
+ }
+ Log.w(tag, methodName + " was failed with an exception=" + errorString);
+ }
+
+ @AnyThread
+ private static void logCancellationInternal(@Nullable String tag, @Nullable String methodName) {
+ if (tag == null || methodName == null) {
+ return;
+ }
+ Log.w(tag, methodName + " was cancelled.");
+ }
+
+ /**
+ * Return the result of the given {@link CompletableFuture}.
+ *
+ * This method may throw exception is the task is completed with an error.
+ *
+ * @param future the object to extract the result from.
+ * @param type of the result.
+ * @return the result.
+ */
+ @AnyThread
+ @Nullable
+ public static T getResult(@NonNull CompletableFuture future) {
+ return getValueOrRethrowErrorInternal(future);
+ }
+
+ /**
+ * Return the result of the given {@link CompletableFuture}.
+ *
+ * This method may throw exception is the task is completed with an error.
+ *
+ * @param future the object to extract the result from.
+ * @return the result.
+ */
+ @AnyThread
+ public static boolean getBooleanResult(@NonNull CompletableFuture future) {
+ return getValueOrRethrowErrorInternal(future);
+ }
+
+ /**
+ * Return the result of the given {@link CompletableFuture}.
+ *
+ * This method may throw exception is the task is completed with an error.
+ *
+ * @param future the object to extract the result from.
+ * @return the result.
+ */
+ @AnyThread
+ public static int getIntegerResult(@NonNull CompletableFuture future) {
+ return getValueOrRethrowErrorInternal(future);
+ }
+
+ /**
+ * Return the result of the given {@link CompletableFuture}.
+ *
+ * This method is agnostic to {@link Thread#interrupt()}.
+ *
+ * CAVEAT: when {@code cancellationGroup} is specified and it is signalled, {@code future}
+ * will be cancelled permanently. You have to duplicate the {@link CompletableFuture} if you
+ * want to avoid this side-effect.
+ *
+ * @param future the object to extract the result from.
+ * @param tag tag name for logging. Pass {@code null} to disable logging.
+ * @param methodName method name for logging. Pass {@code null} to disable logging.
+ * @param cancellationGroup an optional {@link CancellationGroup} to cancel {@code future}
+ * object. Can be {@code null}.
+ * @param timeoutMillis length of the timeout in millisecond.
+ * @return the result if it is completed within the given timeout. {@code false} otherwise.
+ */
+ @AnyThread
+ public static boolean getResultOrFalse(@NonNull CompletableFuture future,
+ @Nullable String tag, @Nullable String methodName,
+ @Nullable CancellationGroup cancellationGroup,
+ @DurationMillisLong long timeoutMillis) {
+ final Boolean obj = getValueOrNullInternal(future, tag, methodName, timeoutMillis,
+ cancellationGroup);
+ return obj != null ? obj : false;
+ }
+
+ /**
+ * Return the result of the given {@link CompletableFuture}.
+ *
+ * This method is agnostic to {@link Thread#interrupt()}.
+ *
+ * CAVEAT: when {@code cancellationGroup} is specified and it is signalled, {@code future}
+ * will be cancelled permanently. You have to duplicate the {@link CompletableFuture} if you
+ * want to avoid this side-effect.
+ *
+ * @param future the object to extract the result from.
+ * @param tag tag name for logging. Pass {@code null} to disable logging.
+ * @param methodName method name for logging. Pass {@code null} to disable logging.
+ * @param cancellationGroup an optional {@link CancellationGroup} to cancel {@code future}
+ * object. Can be {@code null}.
+ * @param timeoutMillis length of the timeout in millisecond.
+ * @return the result if it is completed within the given timeout. {@code 0} otherwise.
+ */
+ @AnyThread
+ public static int getResultOrZero(@NonNull CompletableFuture future,
+ @Nullable String tag, @Nullable String methodName,
+ @Nullable CancellationGroup cancellationGroup, @DurationMillisLong long timeoutMillis) {
+ final Integer obj = getValueOrNullInternal(future, tag, methodName, timeoutMillis,
+ cancellationGroup);
+ return obj != null ? obj : 0;
+ }
+
+ /**
+ * Return the result of the given {@link CompletableFuture}.
+ *
+ * This method is agnostic to {@link Thread#interrupt()}.
+ *
+ * CAVEAT: when {@code cancellationGroup} is specified and it is signalled, {@code future}
+ * will be cancelled permanently. You have to duplicate the {@link CompletableFuture} if you
+ * want to avoid this side-effect.
+ *
+ * @param future the object to extract the result from.
+ * @param tag tag name for logging. Pass {@code null} to disable logging.
+ * @param methodName method name for logging. Pass {@code null} to disable logging.
+ * @param cancellationGroup an optional {@link CancellationGroup} to cancel {@code future}
+ * object. Can be {@code null}.
+ * @param timeoutMillis length of the timeout in millisecond.
+ * @param Type of the result.
+ * @return the result if it is completed within the given timeout. {@code null} otherwise.
+ */
+ @AnyThread
+ @Nullable
+ public static T getResultOrNull(@NonNull CompletableFuture future, @Nullable String tag,
+ @Nullable String methodName, @Nullable CancellationGroup cancellationGroup,
+ @DurationMillisLong long timeoutMillis) {
+ return getValueOrNullInternal(future, tag, methodName, timeoutMillis, cancellationGroup);
+ }
+}
diff --git a/core/java/com/android/internal/inputmethod/IInputContextInvoker.java b/core/java/com/android/internal/inputmethod/IInputContextInvoker.java
index 977f9a5a51105..783d54d077b6a 100644
--- a/core/java/com/android/internal/inputmethod/IInputContextInvoker.java
+++ b/core/java/com/android/internal/inputmethod/IInputContextInvoker.java
@@ -23,16 +23,19 @@ import android.os.RemoteException;
import android.view.KeyEvent;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.SurroundingText;
import com.android.internal.view.IInputContext;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
/**
* A stateless wrapper of {@link com.android.internal.view.IInputContext} to encapsulate boilerplate
- * code around {@link Completable} and {@link RemoteException}.
+ * code around {@link CompletableFuture} and {@link RemoteException}.
*/
public final class IInputContextInvoker {
@@ -60,17 +63,17 @@ public final class IInputContextInvoker {
*
* @param length {@code length} parameter to be passed.
* @param flags {@code flags} parameter to be passed.
- * @return {@link Completable.CharSequence} that can be used to retrieve the invocation result.
- * {@link RemoteException} will be treated as an error.
+ * @return {@link CompletableFuture} that can be used to retrieve the invocation
+ * result. {@link RemoteException} will be treated as an error.
*/
@AnyThread
@NonNull
- public Completable.CharSequence getTextAfterCursor(int length, int flags) {
- final Completable.CharSequence value = Completable.createCharSequence();
+ public CompletableFuture getTextAfterCursor(int length, int flags) {
+ final CompletableFuture value = new CompletableFuture<>();
try {
- mIInputContext.getTextAfterCursor(length, flags, ResultCallbacks.of(value));
+ mIInputContext.getTextAfterCursor(length, flags, ResultCallbacks.ofCharSequence(value));
} catch (RemoteException e) {
- value.onError(ThrowableHolder.of(e));
+ value.completeExceptionally(e);
}
return value;
}
@@ -80,17 +83,18 @@ public final class IInputContextInvoker {
*
* @param length {@code length} parameter to be passed.
* @param flags {@code flags} parameter to be passed.
- * @return {@link Completable.CharSequence} that can be used to retrieve the invocation result.
- * {@link RemoteException} will be treated as an error.
+ * @return {@link CompletableFuture} that can be used to retrieve the invocation
+ * result. {@link RemoteException} will be treated as an error.
*/
@AnyThread
@NonNull
- public Completable.CharSequence getTextBeforeCursor(int length, int flags) {
- final Completable.CharSequence value = Completable.createCharSequence();
+ public CompletableFuture getTextBeforeCursor(int length, int flags) {
+ final CompletableFuture value = new CompletableFuture<>();
try {
- mIInputContext.getTextBeforeCursor(length, flags, ResultCallbacks.of(value));
+ mIInputContext.getTextBeforeCursor(length, flags,
+ ResultCallbacks.ofCharSequence(value));
} catch (RemoteException e) {
- value.onError(ThrowableHolder.of(e));
+ value.completeExceptionally(e);
}
return value;
}
@@ -99,17 +103,17 @@ public final class IInputContextInvoker {
* Invokes {@link IInputContext#getSelectedText(int, ICharSequenceResultCallback)}.
*
* @param flags {@code flags} parameter to be passed.
- * @return {@link Completable.CharSequence} that can be used to retrieve the invocation result.
- * {@link RemoteException} will be treated as an error.
+ * @return {@link CompletableFuture} that can be used to retrieve the invocation
+ * result. {@link RemoteException} will be treated as an error.
*/
@AnyThread
@NonNull
- public Completable.CharSequence getSelectedText(int flags) {
- final Completable.CharSequence value = Completable.createCharSequence();
+ public CompletableFuture getSelectedText(int flags) {
+ final CompletableFuture value = new CompletableFuture<>();
try {
- mIInputContext.getSelectedText(flags, ResultCallbacks.of(value));
+ mIInputContext.getSelectedText(flags, ResultCallbacks.ofCharSequence(value));
} catch (RemoteException e) {
- value.onError(ThrowableHolder.of(e));
+ value.completeExceptionally(e);
}
return value;
}
@@ -121,19 +125,19 @@ public final class IInputContextInvoker {
* @param beforeLength {@code beforeLength} parameter to be passed.
* @param afterLength {@code afterLength} parameter to be passed.
* @param flags {@code flags} parameter to be passed.
- * @return {@link Completable.SurroundingText} that can be used to retrieve the invocation
- * result. {@link RemoteException} will be treated as an error.
+ * @return {@link CompletableFuture} that can be used to retrieve the
+ * invocation result. {@link RemoteException} will be treated as an error.
*/
@AnyThread
@NonNull
- public Completable.SurroundingText getSurroundingText(int beforeLength, int afterLength,
+ public CompletableFuture getSurroundingText(int beforeLength, int afterLength,
int flags) {
- final Completable.SurroundingText value = Completable.createSurroundingText();
+ final CompletableFuture value = new CompletableFuture<>();
try {
mIInputContext.getSurroundingText(beforeLength, afterLength, flags,
- ResultCallbacks.of(value));
+ ResultCallbacks.ofSurroundingText(value));
} catch (RemoteException e) {
- value.onError(ThrowableHolder.of(e));
+ value.completeExceptionally(e);
}
return value;
}
@@ -142,17 +146,17 @@ public final class IInputContextInvoker {
* Invokes {@link IInputContext#getCursorCapsMode(int, IIntResultCallback)}.
*
* @param reqModes {@code reqModes} parameter to be passed.
- * @return {@link Completable.Int} that can be used to retrieve the invocation result.
- * {@link RemoteException} will be treated as an error.
+ * @return {@link CompletableFuture} that can be used to retrieve the invocation
+ * result. {@link RemoteException} will be treated as an error.
*/
@AnyThread
@NonNull
- public Completable.Int getCursorCapsMode(int reqModes) {
- final Completable.Int value = Completable.createInt();
+ public CompletableFuture getCursorCapsMode(int reqModes) {
+ final CompletableFuture value = new CompletableFuture<>();
try {
- mIInputContext.getCursorCapsMode(reqModes, ResultCallbacks.of(value));
+ mIInputContext.getCursorCapsMode(reqModes, ResultCallbacks.ofInteger(value));
} catch (RemoteException e) {
- value.onError(ThrowableHolder.of(e));
+ value.completeExceptionally(e);
}
return value;
}
@@ -163,17 +167,18 @@ public final class IInputContextInvoker {
*
* @param request {@code request} parameter to be passed.
* @param flags {@code flags} parameter to be passed.
- * @return {@link Completable.ExtractedText} that can be used to retrieve the invocation result.
- * {@link RemoteException} will be treated as an error.
+ * @return {@link CompletableFuture} that can be used to retrieve the invocation
+ * result. {@link RemoteException} will be treated as an error.
*/
@AnyThread
@NonNull
- public Completable.ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
- final Completable.ExtractedText value = Completable.createExtractedText();
+ public CompletableFuture getExtractedText(ExtractedTextRequest request,
+ int flags) {
+ final CompletableFuture value = new CompletableFuture<>();
try {
- mIInputContext.getExtractedText(request, flags, ResultCallbacks.of(value));
+ mIInputContext.getExtractedText(request, flags, ResultCallbacks.ofExtractedText(value));
} catch (RemoteException e) {
- value.onError(ThrowableHolder.of(e));
+ value.completeExceptionally(e);
}
return value;
}
@@ -474,17 +479,17 @@ public final class IInputContextInvoker {
* Invokes {@link IInputContext#requestCursorUpdates(int, IIntResultCallback)}.
*
* @param cursorUpdateMode {@code cursorUpdateMode} parameter to be passed.
- * @return {@link Completable.Boolean} that can be used to retrieve the invocation result.
- * {@link RemoteException} will be treated as an error.
+ * @return {@link CompletableFuture} that can be used to retrieve the invocation
+ * result. {@link RemoteException} will be treated as an error.
*/
@AnyThread
@NonNull
- public Completable.Boolean requestCursorUpdates(int cursorUpdateMode) {
- final Completable.Boolean value = Completable.createBoolean();
+ public CompletableFuture requestCursorUpdates(int cursorUpdateMode) {
+ final CompletableFuture value = new CompletableFuture<>();
try {
- mIInputContext.requestCursorUpdates(cursorUpdateMode, ResultCallbacks.of(value));
+ mIInputContext.requestCursorUpdates(cursorUpdateMode, ResultCallbacks.ofBoolean(value));
} catch (RemoteException e) {
- value.onError(ThrowableHolder.of(e));
+ value.completeExceptionally(e);
}
return value;
}
@@ -496,18 +501,19 @@ public final class IInputContextInvoker {
* @param inputContentInfo {@code inputContentInfo} parameter to be passed.
* @param flags {@code flags} parameter to be passed.
* @param opts {@code opts} parameter to be passed.
- * @return {@link Completable.Boolean} that can be used to retrieve the invocation result.
- * {@link RemoteException} will be treated as an error.
+ * @return {@link CompletableFuture} that can be used to retrieve the invocation
+ * result. {@link RemoteException} will be treated as an error.
*/
@AnyThread
@NonNull
- public Completable.Boolean commitContent(InputContentInfo inputContentInfo, int flags,
+ public CompletableFuture commitContent(InputContentInfo inputContentInfo, int flags,
Bundle opts) {
- final Completable.Boolean value = Completable.createBoolean();
+ final CompletableFuture value = new CompletableFuture<>();
try {
- mIInputContext.commitContent(inputContentInfo, flags, opts, ResultCallbacks.of(value));
+ mIInputContext.commitContent(inputContentInfo, flags, opts,
+ ResultCallbacks.ofBoolean(value));
} catch (RemoteException e) {
- value.onError(ThrowableHolder.of(e));
+ value.completeExceptionally(e);
}
return value;
}
diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
index 9fb0bb52e0011..c0785abdc7fb6 100644
--- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
+++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java
@@ -30,6 +30,7 @@ import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.GuardedBy;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
/**
* A utility class to take care of boilerplate code around IPCs.
@@ -156,10 +157,10 @@ public final class InputMethodPrivilegedOperations {
return null;
}
try {
- final Completable.IInputContentUriToken value =
- Completable.createIInputContentUriToken();
- ops.createInputContentUriToken(contentUri, packageName, ResultCallbacks.of(value));
- return Completable.getResult(value);
+ final CompletableFuture value = new CompletableFuture<>();
+ ops.createInputContentUriToken(contentUri, packageName,
+ ResultCallbacks.ofIInputContentUriToken(value));
+ return CompletableFutureUtil.getResult(value);
} catch (RemoteException e) {
// For historical reasons, this error was silently ignored.
// Note that the caller already logs error so we do not need additional Log.e() here.
@@ -218,9 +219,9 @@ public final class InputMethodPrivilegedOperations {
return;
}
try {
- final Completable.Void value = Completable.createVoid();
- ops.setInputMethod(id, ResultCallbacks.of(value));
- Completable.getResult(value);
+ final CompletableFuture value = new CompletableFuture<>();
+ ops.setInputMethod(id, ResultCallbacks.ofVoid(value));
+ CompletableFutureUtil.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -241,9 +242,9 @@ public final class InputMethodPrivilegedOperations {
return;
}
try {
- final Completable.Void value = Completable.createVoid();
- ops.setInputMethodAndSubtype(id, subtype, ResultCallbacks.of(value));
- Completable.getResult(value);
+ final CompletableFuture value = new CompletableFuture<>();
+ ops.setInputMethodAndSubtype(id, subtype, ResultCallbacks.ofVoid(value));
+ CompletableFutureUtil.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -263,9 +264,9 @@ public final class InputMethodPrivilegedOperations {
return;
}
try {
- final Completable.Void value = Completable.createVoid();
- ops.hideMySoftInput(flags, ResultCallbacks.of(value));
- Completable.getResult(value);
+ final CompletableFuture value = new CompletableFuture<>();
+ ops.hideMySoftInput(flags, ResultCallbacks.ofVoid(value));
+ CompletableFutureUtil.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -285,9 +286,9 @@ public final class InputMethodPrivilegedOperations {
return;
}
try {
- final Completable.Void value = Completable.createVoid();
- ops.showMySoftInput(flags, ResultCallbacks.of(value));
- Completable.getResult(value);
+ final CompletableFuture value = new CompletableFuture<>();
+ ops.showMySoftInput(flags, ResultCallbacks.ofVoid(value));
+ CompletableFutureUtil.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -306,9 +307,9 @@ public final class InputMethodPrivilegedOperations {
return false;
}
try {
- final Completable.Boolean value = Completable.createBoolean();
- ops.switchToPreviousInputMethod(ResultCallbacks.of(value));
- return Completable.getResult(value);
+ final CompletableFuture value = new CompletableFuture<>();
+ ops.switchToPreviousInputMethod(ResultCallbacks.ofBoolean(value));
+ return CompletableFutureUtil.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -329,9 +330,9 @@ public final class InputMethodPrivilegedOperations {
return false;
}
try {
- final Completable.Boolean value = Completable.createBoolean();
- ops.switchToNextInputMethod(onlyCurrentIme, ResultCallbacks.of(value));
- return Completable.getResult(value);
+ final CompletableFuture value = new CompletableFuture<>();
+ ops.switchToNextInputMethod(onlyCurrentIme, ResultCallbacks.ofBoolean(value));
+ return CompletableFutureUtil.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -350,9 +351,9 @@ public final class InputMethodPrivilegedOperations {
return false;
}
try {
- final Completable.Boolean value = Completable.createBoolean();
- ops.shouldOfferSwitchingToNextInputMethod(ResultCallbacks.of(value));
- return Completable.getResult(value);
+ final CompletableFuture value = new CompletableFuture<>();
+ ops.shouldOfferSwitchingToNextInputMethod(ResultCallbacks.ofBoolean(value));
+ return CompletableFutureUtil.getResult(value);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
index 343a6e697c7c7..8aef54b3b1d38 100644
--- a/core/java/com/android/internal/inputmethod/ResultCallbacks.java
+++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
@@ -20,12 +20,15 @@ import android.annotation.AnyThread;
import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.SurroundingText;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
/**
* Defines a set of factory methods to create {@link android.os.IBinder}-based callbacks that are
- * associated with completable objects defined in {@link Completable}.
+ * associated with completable objects defined in {@link CompletableFuture}.
*/
public final class ResultCallbacks {
@@ -35,6 +38,13 @@ public final class ResultCallbacks {
private ResultCallbacks() {
}
+ private static final class LightweightThrowable extends RuntimeException {
+ LightweightThrowable(@Nullable ThrowableHolder throwableHolder) {
+ super(throwableHolder != null ? throwableHolder.getMessage() : null,
+ null, false, false);
+ }
+ }
+
@AnyThread
@Nullable
private static T unwrap(@NonNull AtomicReference atomicRef) {
@@ -43,218 +53,222 @@ public final class ResultCallbacks {
}
/**
- * Creates {@link IIntResultCallback.Stub} that is to set {@link Completable.Int} when receiving
- * the result.
+ * Creates {@link IIntResultCallback.Stub} that is to set {@link CompletableFuture}
+ * when receiving the result.
*
- * @param value {@link Completable.Int} to be set when receiving the result.
+ * @param value {@link CompletableFuture} to be set when receiving the result.
* @return {@link IIntResultCallback.Stub} that can be passed as a binder IPC parameter.
*/
@AnyThread
- public static IIntResultCallback.Stub of(@NonNull Completable.Int value) {
- final AtomicReference atomicRef = new AtomicReference<>(value);
+ public static IIntResultCallback.Stub ofInteger(@NonNull CompletableFuture value) {
+ final AtomicReference> atomicRef = new AtomicReference<>(value);
return new IIntResultCallback.Stub() {
@BinderThread
@Override
public void onResult(int result) {
- final Completable.Int value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onComplete(result);
+ value.complete(result);
}
@BinderThread
@Override
public void onError(ThrowableHolder throwableHolder) {
- final Completable.Int value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onError(throwableHolder);
+ value.completeExceptionally(new LightweightThrowable(throwableHolder));
}
};
}
/**
* Creates {@link ICharSequenceResultCallback.Stub} that is to set
- * {@link Completable.CharSequence} when receiving the result.
+ * {@link CompletableFuture} when receiving the result.
*
- * @param value {@link Completable.CharSequence} to be set when receiving the result.
+ * @param value {@link CompletableFuture} to be set when receiving the result.
* @return {@link ICharSequenceResultCallback.Stub} that can be passed as a binder IPC
* parameter.
*/
@AnyThread
- public static ICharSequenceResultCallback.Stub of(
- @NonNull Completable.CharSequence value) {
- final AtomicReference atomicRef = new AtomicReference<>(value);
+ public static ICharSequenceResultCallback.Stub ofCharSequence(
+ @NonNull CompletableFuture value) {
+ final AtomicReference> atomicRef =
+ new AtomicReference<>(value);
return new ICharSequenceResultCallback.Stub() {
@BinderThread
@Override
public void onResult(CharSequence result) {
- final Completable.CharSequence value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onComplete(result);
+ value.complete(result);
}
};
}
/**
* Creates {@link IExtractedTextResultCallback.Stub} that is to set
- * {@link Completable.ExtractedText} when receiving the result.
+ * {@link CompletableFuture} when receiving the result.
*
- * @param value {@link Completable.ExtractedText} to be set when receiving the result.
+ * @param value {@link CompletableFuture} to be set when receiving the result.
* @return {@link IExtractedTextResultCallback.Stub} that can be passed as a binder IPC
* parameter.
*/
@AnyThread
- public static IExtractedTextResultCallback.Stub of(
- @NonNull Completable.ExtractedText value) {
- final AtomicReference atomicRef = new AtomicReference<>(value);
+ public static IExtractedTextResultCallback.Stub ofExtractedText(
+ @NonNull CompletableFuture value) {
+ final AtomicReference> atomicRef =
+ new AtomicReference<>(value);
return new IExtractedTextResultCallback.Stub() {
@BinderThread
@Override
public void onResult(android.view.inputmethod.ExtractedText result) {
- final Completable.ExtractedText value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onComplete(result);
+ value.complete(result);
}
};
}
/**
* Creates {@link ISurroundingTextResultCallback.Stub} that is to set
- * {@link Completable.SurroundingText} when receiving the result.
+ * {@link CompletableFuture} when receiving the result.
*
- * @param value {@link Completable.SurroundingText} to be set when receiving the result.
+ * @param value {@link CompletableFuture} to be set when receiving the result.
* @return {@link ISurroundingTextResultCallback.Stub} that can be passed as a binder IPC
* parameter.
*/
@AnyThread
- public static ISurroundingTextResultCallback.Stub of(
- @NonNull Completable.SurroundingText value) {
- final AtomicReference atomicRef = new AtomicReference<>(value);
+ public static ISurroundingTextResultCallback.Stub ofSurroundingText(
+ @NonNull CompletableFuture value) {
+ final AtomicReference> atomicRef =
+ new AtomicReference<>(value);
return new ISurroundingTextResultCallback.Stub() {
@BinderThread
@Override
public void onResult(android.view.inputmethod.SurroundingText result) {
- final Completable.SurroundingText value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onComplete(result);
+ value.complete(result);
}
};
}
/**
- * Creates {@link IBooleanResultCallback.Stub} that is to set {@link Completable.Boolean} when
- * receiving the result.
+ * Creates {@link IBooleanResultCallback.Stub} that is to set {@link CompletableFuture}
+ * when receiving the result.
*
- * @param value {@link Completable.Boolean} to be set when receiving the result.
+ * @param value {@link CompletableFuture} to be set when receiving the result.
* @return {@link IBooleanResultCallback.Stub} that can be passed as a binder IPC parameter.
*/
@AnyThread
- public static IBooleanResultCallback.Stub of(@NonNull Completable.Boolean value) {
- final AtomicReference atomicRef = new AtomicReference<>(value);
+ public static IBooleanResultCallback.Stub ofBoolean(@NonNull CompletableFuture value) {
+ final AtomicReference> atomicRef = new AtomicReference<>(value);
return new IBooleanResultCallback.Stub() {
@BinderThread
@Override
public void onResult(boolean result) {
- final Completable.Boolean value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onComplete(result);
+ value.complete(result);
}
@BinderThread
@Override
public void onError(ThrowableHolder throwableHolder) {
- final Completable.Boolean value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onError(throwableHolder);
+ value.completeExceptionally(new LightweightThrowable(throwableHolder));
}
};
}
/**
- * Creates {@link IVoidResultCallback.Stub} that is to set {@link Completable.Void} when
+ * Creates {@link IVoidResultCallback.Stub} that is to set {@link CompletableFuture} when
* receiving the result.
*
- * @param value {@link Completable.Void} to be set when receiving the result.
+ * @param value {@link CompletableFuture} to be set when receiving the result.
* @return {@link IVoidResultCallback.Stub} that can be passed as a binder IPC parameter.
*/
@AnyThread
- public static IVoidResultCallback.Stub of(@NonNull Completable.Void value) {
- final AtomicReference atomicRef = new AtomicReference<>(value);
+ public static IVoidResultCallback.Stub ofVoid(@NonNull CompletableFuture value) {
+ final AtomicReference> atomicRef = new AtomicReference<>(value);
return new IVoidResultCallback.Stub() {
@BinderThread
@Override
public void onResult() {
- final Completable.Void value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onComplete();
+ value.complete(null);
}
@BinderThread
@Override
public void onError(ThrowableHolder throwableHolder) {
- final Completable.Void value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onError(throwableHolder);
+ value.completeExceptionally(new LightweightThrowable(throwableHolder));
}
};
}
/**
* Creates {@link IInputContentUriTokenResultCallback.Stub} that is to set
- * {@link Completable.IInputContentUriToken} when receiving the result.
+ * {@link CompletableFuture} when receiving the result.
*
- * @param value {@link Completable.IInputContentUriToken} to be set when receiving the result.
+ * @param value {@link CompletableFuture} to be set when receiving the
+ * result.
* @return {@link IInputContentUriTokenResultCallback.Stub} that can be passed as a binder IPC
* parameter.
*/
@AnyThread
- public static IInputContentUriTokenResultCallback.Stub of(
- @NonNull Completable.IInputContentUriToken value) {
- final AtomicReference
+ public static IInputContentUriTokenResultCallback.Stub ofIInputContentUriToken(
+ @NonNull CompletableFuture value) {
+ final AtomicReference>
atomicRef = new AtomicReference<>(value);
return new IInputContentUriTokenResultCallback.Stub() {
@BinderThread
@Override
public void onResult(IInputContentUriToken result) {
- final Completable.IInputContentUriToken value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onComplete(result);
+ value.complete(result);
}
@BinderThread
@Override
public void onError(ThrowableHolder throwableHolder) {
- final Completable.IInputContentUriToken value = unwrap(atomicRef);
+ final CompletableFuture value = unwrap(atomicRef);
if (value == null) {
return;
}
- value.onError(throwableHolder);
+ value.completeExceptionally(new LightweightThrowable(throwableHolder));
}
};
}
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt b/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
new file mode 100644
index 0000000000000..8355daa94e911
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/CompletableFutureUtilTest.kt
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2021 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.inputmethod
+
+import android.annotation.DurationMillisLong
+import android.os.Handler
+import android.os.SystemClock
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicLong
+import java.util.concurrent.atomic.AtomicReference
+
+@DurationMillisLong
+const val SHORT_PERIOD_MILLI = 50L
+const val SHORT_PERIOD_NANO = SHORT_PERIOD_MILLI * 1_000_000L
+
+@DurationMillisLong
+const val TIMEOUT_MILLI = 10_000L
+const val TIMEOUT_NANO = TIMEOUT_MILLI * 1_000_000L
+
+const val ERROR_MESSAGE = "Test Error Message!"
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class CompletableFutureUtilTest {
+
+ private inline fun assertRuntimeException(expectedMessage: String, block: () -> Unit) {
+ try {
+ block()
+ fail()
+ } catch (exception: RuntimeException) {
+ assertThat(exception.message).isEqualTo(expectedMessage)
+ // Expected
+ } catch (exception: Throwable) {
+ fail("RuntimeException is expected but got $exception")
+ }
+ }
+
+ private inline fun runOnMainDelayed(delay: Long, crossinline block: () -> Unit) {
+ val handler = Handler.createAsync(
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getMainLooper())
+ handler.postDelayed({
+ block()
+ }, delay)
+ }
+
+ @Test
+ fun testCharSequenceTimedOut() {
+ val completable = CompletableFuture()
+
+ assertThat(completable.isDone).isFalse()
+
+ val beginNanos = SystemClock.elapsedRealtimeNanos()
+ val result = CompletableFutureUtil.getResultOrNull(
+ completable, null, null, null, SHORT_PERIOD_MILLI)
+ val elapsed = SystemClock.elapsedRealtimeNanos() - beginNanos
+
+ assertThat(completable.isDone).isFalse()
+ assertThat(result).isNull()
+ assertThat(elapsed).isGreaterThan(SHORT_PERIOD_NANO)
+ }
+
+ @Test
+ fun testCharSequenceTimedOutWithInterruption() {
+ val completable = CompletableFuture()
+
+ val beginNanosRef = AtomicLong()
+ val endNanosRef = AtomicLong()
+ val isInterruptedRef = AtomicBoolean()
+ val resultRef = AtomicReference()
+
+ // Verifies that calling getResultOrNull() on an interrupted thread still times out with
+ // preserving the interrupted state.
+ val thread = Thread {
+ val currentThread = Thread.currentThread()
+ currentThread.interrupt()
+ beginNanosRef.set(SystemClock.elapsedRealtimeNanos())
+ resultRef.set(CompletableFutureUtil.getResultOrNull(
+ completable, null, null, null, SHORT_PERIOD_MILLI))
+ endNanosRef.set(SystemClock.elapsedRealtimeNanos())
+ isInterruptedRef.set(currentThread.isInterrupted())
+ }
+
+ thread.run()
+ thread.join(TIMEOUT_MILLI)
+ assertThat(thread.isAlive).isFalse()
+
+ val elapsedTime = endNanosRef.get() - beginNanosRef.get()
+ assertThat(elapsedTime).isGreaterThan(SHORT_PERIOD_NANO)
+ assertThat(resultRef.get()).isNull()
+ assertThat(isInterruptedRef.get()).isTrue()
+ }
+
+ @Test
+ fun testCharSequenceAfterCompletion() {
+ val expectedValue = "Expected Value"
+ val completable = CompletableFuture()
+
+ assertThat(completable.isDone).isFalse()
+ completable.complete(expectedValue)
+ assertThat(completable.isDone).isTrue()
+
+ val beginNanos = SystemClock.elapsedRealtimeNanos()
+ val result = CompletableFutureUtil.getResultOrNull(
+ completable, null, null, null,
+ TIMEOUT_MILLI)
+ val elapsed = SystemClock.elapsedRealtimeNanos() - beginNanos
+
+ assertThat(result).isEqualTo(expectedValue)
+ assertThat(elapsed).isLessThan(SHORT_PERIOD_NANO)
+ }
+
+ @Test
+ fun testCharSequenceAfterError() {
+ val completable = CompletableFuture()
+
+ assertThat(completable.isDone).isFalse()
+ completable.completeExceptionally(UnsupportedOperationException(ERROR_MESSAGE))
+ assertThat(completable.isDone).isTrue()
+
+ val beginNanos = SystemClock.elapsedRealtimeNanos()
+ val result = CompletableFutureUtil.getResultOrNull(
+ completable, null, null, null, TIMEOUT_MILLI)
+ val elapsed = SystemClock.elapsedRealtimeNanos() - beginNanos
+
+ assertThat(result).isNull()
+ assertThat(elapsed).isLessThan(SHORT_PERIOD_NANO)
+
+ assertRuntimeException(ERROR_MESSAGE) {
+ CompletableFutureUtil.getResult(completable)
+ }
+ }
+
+ @Test
+ fun testCharSequenceAfterCancellation() {
+ val completable = CompletableFuture()
+ val cancellationGroup = CancellationGroup()
+ cancellationGroup.cancelAll()
+
+ val beginNanos = SystemClock.elapsedRealtimeNanos()
+ val result = CompletableFutureUtil.getResultOrNull(
+ completable, null, null, cancellationGroup, TIMEOUT_MILLI)
+ val elapsed = SystemClock.elapsedRealtimeNanos() - beginNanos
+
+ // due to the side-effect of cancellationGroup, the object is already completed here.
+ assertThat(completable.isDone).isTrue()
+ assertThat(result).isNull()
+ assertThat(elapsed).isLessThan(SHORT_PERIOD_NANO)
+
+ // as the object is already cancelled due to the side-effect of cancellationGroup, it cannot
+ // accept a result any more.
+ completable.complete("Hello!")
+ assertThat(completable.isCancelled).isTrue()
+ }
+
+ @Test
+ fun testCharSequenceAfterCompleteAndCancellation() {
+ val expectedValue = "Expected Value"
+ val completable = CompletableFuture()
+ completable.complete(expectedValue)
+
+ val cancellationGroup = CancellationGroup()
+ cancellationGroup.cancelAll()
+
+ val beginNanos = SystemClock.elapsedRealtimeNanos()
+ val result = CompletableFutureUtil.getResultOrNull(
+ completable, null, null, cancellationGroup, TIMEOUT_MILLI)
+ val elapsed = SystemClock.elapsedRealtimeNanos() - beginNanos
+
+ assertThat(result).isEqualTo(expectedValue)
+ assertThat(CompletableFutureUtil.getResult(completable)).isEqualTo(expectedValue)
+ assertThat(elapsed).isLessThan(SHORT_PERIOD_NANO)
+ }
+
+ @Test
+ fun testCharSequenceMultipleAssignment() {
+ val expectedValue = "Expected Value"
+ val notExpectedValue = "Not Expected Value"
+ val completable = CompletableFuture()
+ completable.complete(expectedValue)
+ completable.complete(notExpectedValue)
+ assertThat(completable.isDone).isTrue()
+
+ assertThat(CompletableFutureUtil.getResult(completable)).isEqualTo(expectedValue)
+ }
+
+ @Test
+ fun testCharSequenceUnblockByCompletion() {
+ val expectedValue = "Expected Value"
+ val completable = CompletableFuture()
+
+ val beginNanos = SystemClock.elapsedRealtimeNanos()
+ runOnMainDelayed(SHORT_PERIOD_MILLI) {
+ completable.complete(expectedValue)
+ }
+ val result = CompletableFutureUtil.getResultOrNull(
+ completable, null, null, null, TIMEOUT_MILLI)
+ val elapsed = SystemClock.elapsedRealtimeNanos() - beginNanos
+
+ assertThat(completable.isDone).isTrue()
+ assertThat(result).isEqualTo(expectedValue)
+ assertThat(elapsed).isIn(Range.closedOpen(SHORT_PERIOD_NANO, TIMEOUT_NANO))
+ }
+
+ @Test
+ fun testCharSequenceUnblockByCompletionWithCancellationGroup() {
+ val expectedValue = "Expected Value"
+ val completable = CompletableFuture()
+ var cancellationGroup = CancellationGroup()
+
+ assertThat(cancellationGroup.isCanceled).isFalse()
+
+ val beginNanos = SystemClock.elapsedRealtimeNanos()
+ runOnMainDelayed(SHORT_PERIOD_MILLI) {
+ completable.complete(expectedValue)
+ }
+ val result = CompletableFutureUtil.getResultOrNull(
+ completable, null, null, cancellationGroup, TIMEOUT_MILLI)
+ val elapsed = SystemClock.elapsedRealtimeNanos() - beginNanos
+
+ assertThat(cancellationGroup.isCanceled).isFalse()
+ assertThat(completable.isDone).isTrue()
+ assertThat(result).isEqualTo(expectedValue)
+ assertThat(elapsed).isIn(Range.closedOpen(SHORT_PERIOD_NANO, TIMEOUT_NANO))
+ }
+
+ @Test
+ fun testCharSequenceUnblockByError() {
+ val completable = CompletableFuture()
+
+ val beginNanos = SystemClock.elapsedRealtimeNanos()
+ runOnMainDelayed(SHORT_PERIOD_MILLI) {
+ completable.completeExceptionally(UnsupportedOperationException(ERROR_MESSAGE))
+ }
+ val result = CompletableFutureUtil.getResultOrNull(
+ completable, null, null, null, TIMEOUT_MILLI)
+ val elapsed = SystemClock.elapsedRealtimeNanos() - beginNanos
+
+ assertThat(completable.isDone).isTrue()
+ assertThat(result).isNull()
+ assertThat(elapsed).isIn(Range.closedOpen(SHORT_PERIOD_NANO, TIMEOUT_NANO))
+ }
+
+ @Test
+ fun testCharSequenceUnblockByCancellation() {
+ val completable = CompletableFuture()
+ val cancellationGroup = CancellationGroup()
+
+ val beginNanos = SystemClock.elapsedRealtimeNanos()
+ runOnMainDelayed(SHORT_PERIOD_MILLI) {
+ cancellationGroup.cancelAll()
+ }
+ val result = CompletableFutureUtil.getResultOrNull(
+ completable, null, null, cancellationGroup, TIMEOUT_MILLI)
+ val elapsed = SystemClock.elapsedRealtimeNanos() - beginNanos
+
+ // due to the side-effect of cancellationGroup.
+ assertThat(completable.isDone).isTrue()
+ assertThat(result).isNull()
+ assertThat(elapsed).isIn(Range.closedOpen(SHORT_PERIOD_NANO, TIMEOUT_NANO))
+ }
+}