Merge "Use CompletableFuture instead"
This commit is contained in:
committed by
Android (Google) Code Review
commit
8a4ab6743a
@@ -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