Use CompletableFuture instead
This CL converts our inhouse Completable class with CompletableFuture. One of downsides of switching into CompletableFuture is performance. It creates much more objects, especially in unsuccessful cases including timeout scenarios: * CompletableFuture#cancel() always creates a CancellationException object with full stack trace. This is going to be problematic when we start cancelling pending InputConnection tasks in Bug 195115071 from the IME client side. * Timeout cases always creates a TimeoutException object with a full stack trace. * Exception cases always creates a ExecutionException object with full stack trace. Also, none of its getter methods directly fits our existing use cases in the input method framework world. We must always use CompletableFutureUtil to retrieve the result value so as not to accidentally break existing APIs. Other than above, there should be no observable semantic behavior changes in this CL. Bug: 192412909 Bug: 195699814 Test: presubmit Test: atest -c FrameworksCoreTests:CompletableFutureUtilTest Change-Id: I215bbc870f952effa262fa431064b36ace28e8f4
This commit is contained in:
@@ -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<CharSequence> 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<CharSequence> 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<CharSequence> 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<SurroundingText> 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<Integer> 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<ExtractedText> 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<Boolean> 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<Boolean> value =
|
||||
mInvoker.commitContent(inputContentInfo, flags, opts);
|
||||
return CompletableFutureUtil.getResultOrFalse(
|
||||
value, TAG, "commitContent()", mCancellationGroup, MAX_WAIT_TIME_MILLIS);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>TODO: Make this lock-free.</p>
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* <p>This will be lazily instantiated to avoid unnecessary object allocations.</p>
|
||||
*/
|
||||
@Nullable
|
||||
@GuardedBy("mLock")
|
||||
private ArrayList<CountDownLatch> mLatchList = null;
|
||||
private ArrayList<CompletableFuture<?>> 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.
|
||||
*
|
||||
* <p>If this {@link CancellationGroup} is already cancelled, then this method will immediately
|
||||
* call {@link CompletableFuture#cancel(boolean)} then return {@code false}.</p>
|
||||
*
|
||||
* <p>When this method returns {@code true}, call {@link #unregisterFuture(CompletableFuture)}
|
||||
* to remove the unnecessary object reference.</p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)}.
|
||||
*
|
||||
* <p>This is not {@code null} only when {@link #mState} is
|
||||
* {@link CompletionState#COMPLETED_WITH_ERROR}.</p>
|
||||
*/
|
||||
@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.
|
||||
*
|
||||
* <p>Must be called after acquiring {@link #mStateLock}.</p>
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* <p>
|
||||
* <ol>
|
||||
* <li>This object becomes ready to return the value.</li>
|
||||
* <li>{@link CancellationGroup#cancelAll()} gets called.</li>
|
||||
* <li>The given timeout period has passed.</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*
|
||||
* <p>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}.</p>
|
||||
*
|
||||
* @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 <T> type associated with this completable object.
|
||||
*/
|
||||
public static class Values<T> 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<java.lang.Boolean> { }
|
||||
|
||||
/**
|
||||
* Completable object of {@link java.lang.CharSequence}.
|
||||
*/
|
||||
public static final class CharSequence extends Values<java.lang.CharSequence> { }
|
||||
|
||||
/**
|
||||
* Completable object of {@link android.view.inputmethod.ExtractedText}.
|
||||
*/
|
||||
public static final class ExtractedText
|
||||
extends Values<android.view.inputmethod.ExtractedText> { }
|
||||
|
||||
/**
|
||||
* Completable object of {@link android.view.inputmethod.SurroundingText}.
|
||||
*/
|
||||
public static final class SurroundingText
|
||||
extends Values<android.view.inputmethod.SurroundingText> { }
|
||||
|
||||
/**
|
||||
* Completable object of {@link IInputContentUriToken>}.
|
||||
*/
|
||||
public static final class IInputContentUriToken
|
||||
extends Values<com.android.internal.inputmethod.IInputContentUriToken> { }
|
||||
|
||||
/**
|
||||
* Await the result by the {@link Completable.Values}.
|
||||
*
|
||||
* @return the result once {@link ValueBase#onComplete()}.
|
||||
*/
|
||||
@AnyThread
|
||||
@Nullable
|
||||
public static <T> T getResult(@NonNull Completable.Values<T> 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> T getResultOrNull(@NonNull Completable.Values<T> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> T getValueOrRethrowErrorInternal(@NonNull CompletableFuture<T> 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> T getValueOrNullInternal(@NonNull CompletableFuture<T> 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<T>}.
|
||||
*
|
||||
* <p>This method may throw exception is the task is completed with an error.</p>
|
||||
*
|
||||
* @param future the object to extract the result from.
|
||||
* @param <T> type of the result.
|
||||
* @return the result.
|
||||
*/
|
||||
@AnyThread
|
||||
@Nullable
|
||||
public static <T> T getResult(@NonNull CompletableFuture<T> future) {
|
||||
return getValueOrRethrowErrorInternal(future);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result of the given {@link CompletableFuture<Boolean>}.
|
||||
*
|
||||
* <p>This method may throw exception is the task is completed with an error.</p>
|
||||
*
|
||||
* @param future the object to extract the result from.
|
||||
* @return the result.
|
||||
*/
|
||||
@AnyThread
|
||||
public static boolean getBooleanResult(@NonNull CompletableFuture<Boolean> future) {
|
||||
return getValueOrRethrowErrorInternal(future);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result of the given {@link CompletableFuture<Integer>}.
|
||||
*
|
||||
* <p>This method may throw exception is the task is completed with an error.</p>
|
||||
*
|
||||
* @param future the object to extract the result from.
|
||||
* @return the result.
|
||||
*/
|
||||
@AnyThread
|
||||
public static int getIntegerResult(@NonNull CompletableFuture<Integer> future) {
|
||||
return getValueOrRethrowErrorInternal(future);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result of the given {@link CompletableFuture<Boolean>}.
|
||||
*
|
||||
* <p>This method is agnostic to {@link Thread#interrupt()}.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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<Boolean> 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<Integer>}.
|
||||
*
|
||||
* <p>This method is agnostic to {@link Thread#interrupt()}.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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<Integer> 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<T>}.
|
||||
*
|
||||
* <p>This method is agnostic to {@link Thread#interrupt()}.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* @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 <T> Type of the result.
|
||||
* @return the result if it is completed within the given timeout. {@code null} otherwise.
|
||||
*/
|
||||
@AnyThread
|
||||
@Nullable
|
||||
public static <T> T getResultOrNull(@NonNull CompletableFuture<T> future, @Nullable String tag,
|
||||
@Nullable String methodName, @Nullable CancellationGroup cancellationGroup,
|
||||
@DurationMillisLong long timeoutMillis) {
|
||||
return getValueOrNullInternal(future, tag, methodName, timeoutMillis, cancellationGroup);
|
||||
}
|
||||
}
|
||||
@@ -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<CharSequence>} 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<CharSequence> getTextAfterCursor(int length, int flags) {
|
||||
final CompletableFuture<CharSequence> 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<CharSequence>} 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<CharSequence> getTextBeforeCursor(int length, int flags) {
|
||||
final CompletableFuture<CharSequence> 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<CharSequence>} 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<CharSequence> getSelectedText(int flags) {
|
||||
final CompletableFuture<CharSequence> 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<SurroundingText>} 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<SurroundingText> getSurroundingText(int beforeLength, int afterLength,
|
||||
int flags) {
|
||||
final Completable.SurroundingText value = Completable.createSurroundingText();
|
||||
final CompletableFuture<SurroundingText> 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<Integer>} 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<Integer> getCursorCapsMode(int reqModes) {
|
||||
final CompletableFuture<Integer> 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<ExtractedText>} 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<ExtractedText> getExtractedText(ExtractedTextRequest request,
|
||||
int flags) {
|
||||
final CompletableFuture<ExtractedText> 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<Boolean>} 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<Boolean> requestCursorUpdates(int cursorUpdateMode) {
|
||||
final CompletableFuture<Boolean> 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<Boolean>} 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<Boolean> commitContent(InputContentInfo inputContentInfo, int flags,
|
||||
Bundle opts) {
|
||||
final Completable.Boolean value = Completable.createBoolean();
|
||||
final CompletableFuture<Boolean> 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;
|
||||
}
|
||||
|
||||
@@ -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<IInputContentUriToken> 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<Void> 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<Void> 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<Void> 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<Void> 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<Boolean> 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<Boolean> 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<Boolean> value = new CompletableFuture<>();
|
||||
ops.shouldOfferSwitchingToNextInputMethod(ResultCallbacks.ofBoolean(value));
|
||||
return CompletableFutureUtil.getResult(value);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
|
||||
@@ -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> T unwrap(@NonNull AtomicReference<T> 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<Integer>}
|
||||
* when receiving the result.
|
||||
*
|
||||
* @param value {@link Completable.Int} to be set when receiving the result.
|
||||
* @param value {@link CompletableFuture<Integer>} 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<Completable.Int> atomicRef = new AtomicReference<>(value);
|
||||
public static IIntResultCallback.Stub ofInteger(@NonNull CompletableFuture<Integer> value) {
|
||||
final AtomicReference<CompletableFuture<Integer>> atomicRef = new AtomicReference<>(value);
|
||||
|
||||
return new IIntResultCallback.Stub() {
|
||||
@BinderThread
|
||||
@Override
|
||||
public void onResult(int result) {
|
||||
final Completable.Int value = unwrap(atomicRef);
|
||||
final CompletableFuture<Integer> 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<Integer> 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<CharSequence>} when receiving the result.
|
||||
*
|
||||
* @param value {@link Completable.CharSequence} to be set when receiving the result.
|
||||
* @param value {@link CompletableFuture<CharSequence>} 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<Completable.CharSequence> atomicRef = new AtomicReference<>(value);
|
||||
public static ICharSequenceResultCallback.Stub ofCharSequence(
|
||||
@NonNull CompletableFuture<CharSequence> value) {
|
||||
final AtomicReference<CompletableFuture<CharSequence>> atomicRef =
|
||||
new AtomicReference<>(value);
|
||||
|
||||
return new ICharSequenceResultCallback.Stub() {
|
||||
@BinderThread
|
||||
@Override
|
||||
public void onResult(CharSequence result) {
|
||||
final Completable.CharSequence value = unwrap(atomicRef);
|
||||
final CompletableFuture<CharSequence> 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<ExtractedText>} when receiving the result.
|
||||
*
|
||||
* @param value {@link Completable.ExtractedText} to be set when receiving the result.
|
||||
* @param value {@link CompletableFuture<ExtractedText>} 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<Completable.ExtractedText> atomicRef = new AtomicReference<>(value);
|
||||
public static IExtractedTextResultCallback.Stub ofExtractedText(
|
||||
@NonNull CompletableFuture<ExtractedText> value) {
|
||||
final AtomicReference<CompletableFuture<ExtractedText>> 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<ExtractedText> 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<SurroundingText>} when receiving the result.
|
||||
*
|
||||
* @param value {@link Completable.SurroundingText} to be set when receiving the result.
|
||||
* @param value {@link CompletableFuture<SurroundingText>} 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<Completable.SurroundingText> atomicRef = new AtomicReference<>(value);
|
||||
public static ISurroundingTextResultCallback.Stub ofSurroundingText(
|
||||
@NonNull CompletableFuture<SurroundingText> value) {
|
||||
final AtomicReference<CompletableFuture<SurroundingText>> 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<SurroundingText> 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<Boolean>}
|
||||
* when receiving the result.
|
||||
*
|
||||
* @param value {@link Completable.Boolean} to be set when receiving the result.
|
||||
* @param value {@link CompletableFuture<Boolean>} 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<Completable.Boolean> atomicRef = new AtomicReference<>(value);
|
||||
public static IBooleanResultCallback.Stub ofBoolean(@NonNull CompletableFuture<Boolean> value) {
|
||||
final AtomicReference<CompletableFuture<Boolean>> atomicRef = new AtomicReference<>(value);
|
||||
|
||||
return new IBooleanResultCallback.Stub() {
|
||||
@BinderThread
|
||||
@Override
|
||||
public void onResult(boolean result) {
|
||||
final Completable.Boolean value = unwrap(atomicRef);
|
||||
final CompletableFuture<Boolean> 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<Boolean> 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<Void>} when
|
||||
* receiving the result.
|
||||
*
|
||||
* @param value {@link Completable.Void} to be set when receiving the result.
|
||||
* @param value {@link CompletableFuture<Void>} 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<Completable.Void> atomicRef = new AtomicReference<>(value);
|
||||
public static IVoidResultCallback.Stub ofVoid(@NonNull CompletableFuture<Void> value) {
|
||||
final AtomicReference<CompletableFuture<Void>> atomicRef = new AtomicReference<>(value);
|
||||
|
||||
return new IVoidResultCallback.Stub() {
|
||||
@BinderThread
|
||||
@Override
|
||||
public void onResult() {
|
||||
final Completable.Void value = unwrap(atomicRef);
|
||||
final CompletableFuture<Void> 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<Void> 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<IInputContentUriToken>} when receiving the result.
|
||||
*
|
||||
* @param value {@link Completable.IInputContentUriToken} to be set when receiving the result.
|
||||
* @param value {@link CompletableFuture<IInputContentUriToken>} 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<Completable.IInputContentUriToken>
|
||||
public static IInputContentUriTokenResultCallback.Stub ofIInputContentUriToken(
|
||||
@NonNull CompletableFuture<IInputContentUriToken> value) {
|
||||
final AtomicReference<CompletableFuture<IInputContentUriToken>>
|
||||
atomicRef = new AtomicReference<>(value);
|
||||
|
||||
return new IInputContentUriTokenResultCallback.Stub() {
|
||||
@BinderThread
|
||||
@Override
|
||||
public void onResult(IInputContentUriToken result) {
|
||||
final Completable.IInputContentUriToken value = unwrap(atomicRef);
|
||||
final CompletableFuture<IInputContentUriToken> 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<IInputContentUriToken> value = unwrap(atomicRef);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
value.onError(throwableHolder);
|
||||
value.completeExceptionally(new LightweightThrowable(throwableHolder));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<CharSequence>()
|
||||
|
||||
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<CharSequence>()
|
||||
|
||||
val beginNanosRef = AtomicLong()
|
||||
val endNanosRef = AtomicLong()
|
||||
val isInterruptedRef = AtomicBoolean()
|
||||
val resultRef = AtomicReference<CharSequence>()
|
||||
|
||||
// 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<CharSequence>()
|
||||
|
||||
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<CharSequence>()
|
||||
|
||||
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<CharSequence>()
|
||||
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<CharSequence>()
|
||||
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<CharSequence>()
|
||||
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<CharSequence>()
|
||||
|
||||
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<CharSequence>()
|
||||
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<CharSequence>()
|
||||
|
||||
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<CharSequence>()
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user