1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.inputmethod; 18 19 import android.annotation.AnyThread; 20 import android.annotation.DurationMillisLong; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.util.Log; 24 25 import java.util.concurrent.CancellationException; 26 import java.util.concurrent.CompletableFuture; 27 import java.util.concurrent.CompletionException; 28 import java.util.concurrent.ExecutionException; 29 import java.util.concurrent.TimeUnit; 30 import java.util.concurrent.TimeoutException; 31 32 /** 33 * A set of helper methods to retrieve result values from {@link CompletableFuture}. 34 */ 35 public final class CompletableFutureUtil { 36 /** 37 * Not intended to be instantiated. 38 */ CompletableFutureUtil()39 private CompletableFutureUtil() { 40 } 41 42 @AnyThread 43 @Nullable getValueOrRethrowErrorInternal(@onNull CompletableFuture<T> future)44 private static <T> T getValueOrRethrowErrorInternal(@NonNull CompletableFuture<T> future) { 45 boolean interrupted = false; 46 try { 47 while (true) { 48 try { 49 return future.get(); 50 } catch (ExecutionException e) { 51 final Throwable cause = e.getCause(); 52 throw new RuntimeException(cause.getMessage(), cause.getCause()); 53 } catch (InterruptedException e) { 54 interrupted = true; 55 } 56 } 57 } finally { 58 if (interrupted) { 59 Thread.currentThread().interrupt(); 60 } 61 } 62 } 63 64 @AnyThread 65 @Nullable getValueOrNullInternal(@onNull CompletableFuture<T> future, @Nullable String tag, @Nullable String methodName, @DurationMillisLong long timeoutMillis, @Nullable CancellationGroup cancellationGroup)66 private static <T> T getValueOrNullInternal(@NonNull CompletableFuture<T> future, 67 @Nullable String tag, @Nullable String methodName, 68 @DurationMillisLong long timeoutMillis, @Nullable CancellationGroup cancellationGroup) { 69 // We intentionally do not use CompletableFuture.anyOf() to avoid additional object 70 // allocations. 71 final boolean needsToUnregister = cancellationGroup != null 72 && cancellationGroup.tryRegisterFutureOrCancelImmediately(future); 73 boolean interrupted = false; 74 try { 75 while (true) { 76 try { 77 return future.get(timeoutMillis, TimeUnit.MILLISECONDS); 78 } catch (CompletionException e) { 79 if (e.getCause() instanceof CancellationException) { 80 logCancellationInternal(tag, methodName); 81 return null; 82 } 83 logErrorInternal(tag, methodName, e.getMessage()); 84 return null; 85 } catch (CancellationException e) { 86 logCancellationInternal(tag, methodName); 87 return null; 88 } catch (InterruptedException e) { 89 interrupted = true; 90 } catch (TimeoutException e) { 91 logTimeoutInternal(tag, methodName, timeoutMillis); 92 return null; 93 } catch (Throwable e) { 94 logErrorInternal(tag, methodName, e.getMessage()); 95 return null; 96 } 97 } 98 } finally { 99 if (needsToUnregister) { 100 cancellationGroup.unregisterFuture(future); 101 } 102 if (interrupted) { 103 Thread.currentThread().interrupt(); 104 } 105 } 106 } 107 108 @AnyThread logTimeoutInternal(@ullable String tag, @Nullable String methodName, @DurationMillisLong long timeout)109 private static void logTimeoutInternal(@Nullable String tag, @Nullable String methodName, 110 @DurationMillisLong long timeout) { 111 if (tag == null || methodName == null) { 112 return; 113 } 114 Log.w(tag, methodName + " didn't respond in " + timeout + " msec."); 115 } 116 117 @AnyThread logErrorInternal(@ullable String tag, @Nullable String methodName, @Nullable String errorString)118 private static void logErrorInternal(@Nullable String tag, @Nullable String methodName, 119 @Nullable String errorString) { 120 if (tag == null || methodName == null) { 121 return; 122 } 123 Log.w(tag, methodName + " was failed with an exception=" + errorString); 124 } 125 126 @AnyThread logCancellationInternal(@ullable String tag, @Nullable String methodName)127 private static void logCancellationInternal(@Nullable String tag, @Nullable String methodName) { 128 if (tag == null || methodName == null) { 129 return; 130 } 131 Log.w(tag, methodName + " was cancelled."); 132 } 133 134 /** 135 * Return the result of the given {@link CompletableFuture<T>}. 136 * 137 * <p>This method may throw exception is the task is completed with an error.</p> 138 * 139 * @param future the object to extract the result from. 140 * @param <T> type of the result. 141 * @return the result. 142 */ 143 @AnyThread 144 @Nullable getResult(@onNull CompletableFuture<T> future)145 public static <T> T getResult(@NonNull CompletableFuture<T> future) { 146 return getValueOrRethrowErrorInternal(future); 147 } 148 149 /** 150 * Return the result of the given {@link CompletableFuture<Boolean>}. 151 * 152 * <p>This method may throw exception is the task is completed with an error.</p> 153 * 154 * @param future the object to extract the result from. 155 * @return the result. 156 */ 157 @AnyThread getBooleanResult(@onNull CompletableFuture<Boolean> future)158 public static boolean getBooleanResult(@NonNull CompletableFuture<Boolean> future) { 159 return getValueOrRethrowErrorInternal(future); 160 } 161 162 /** 163 * Return the result of the given {@link CompletableFuture<Integer>}. 164 * 165 * <p>This method may throw exception is the task is completed with an error.</p> 166 * 167 * @param future the object to extract the result from. 168 * @return the result. 169 */ 170 @AnyThread getIntegerResult(@onNull CompletableFuture<Integer> future)171 public static int getIntegerResult(@NonNull CompletableFuture<Integer> future) { 172 return getValueOrRethrowErrorInternal(future); 173 } 174 175 /** 176 * Return the result of the given {@link CompletableFuture<Boolean>}. 177 * 178 * <p>This method is agnostic to {@link Thread#interrupt()}.</p> 179 * 180 * <p>CAVEAT: when {@code cancellationGroup} is specified and it is signalled, {@code future} 181 * will be cancelled permanently. You have to duplicate the {@link CompletableFuture} if you 182 * want to avoid this side-effect.</p> 183 * 184 * @param future the object to extract the result from. 185 * @param tag tag name for logging. Pass {@code null} to disable logging. 186 * @param methodName method name for logging. Pass {@code null} to disable logging. 187 * @param cancellationGroup an optional {@link CancellationGroup} to cancel {@code future} 188 * object. Can be {@code null}. 189 * @param timeoutMillis length of the timeout in millisecond. 190 * @return the result if it is completed within the given timeout. {@code false} otherwise. 191 */ 192 @AnyThread getResultOrFalse(@onNull CompletableFuture<Boolean> future, @Nullable String tag, @Nullable String methodName, @Nullable CancellationGroup cancellationGroup, @DurationMillisLong long timeoutMillis)193 public static boolean getResultOrFalse(@NonNull CompletableFuture<Boolean> future, 194 @Nullable String tag, @Nullable String methodName, 195 @Nullable CancellationGroup cancellationGroup, 196 @DurationMillisLong long timeoutMillis) { 197 final Boolean obj = getValueOrNullInternal(future, tag, methodName, timeoutMillis, 198 cancellationGroup); 199 return obj != null ? obj : false; 200 } 201 202 /** 203 * Return the result of the given {@link CompletableFuture<Integer>}. 204 * 205 * <p>This method is agnostic to {@link Thread#interrupt()}.</p> 206 * 207 * <p>CAVEAT: when {@code cancellationGroup} is specified and it is signalled, {@code future} 208 * will be cancelled permanently. You have to duplicate the {@link CompletableFuture} if you 209 * want to avoid this side-effect.</p> 210 * 211 * @param future the object to extract the result from. 212 * @param tag tag name for logging. Pass {@code null} to disable logging. 213 * @param methodName method name for logging. Pass {@code null} to disable logging. 214 * @param cancellationGroup an optional {@link CancellationGroup} to cancel {@code future} 215 * object. Can be {@code null}. 216 * @param timeoutMillis length of the timeout in millisecond. 217 * @return the result if it is completed within the given timeout. {@code 0} otherwise. 218 */ 219 @AnyThread getResultOrZero(@onNull CompletableFuture<Integer> future, @Nullable String tag, @Nullable String methodName, @Nullable CancellationGroup cancellationGroup, @DurationMillisLong long timeoutMillis)220 public static int getResultOrZero(@NonNull CompletableFuture<Integer> future, 221 @Nullable String tag, @Nullable String methodName, 222 @Nullable CancellationGroup cancellationGroup, @DurationMillisLong long timeoutMillis) { 223 final Integer obj = getValueOrNullInternal(future, tag, methodName, timeoutMillis, 224 cancellationGroup); 225 return obj != null ? obj : 0; 226 } 227 228 /** 229 * Return the result of the given {@link CompletableFuture<T>}. 230 * 231 * <p>This method is agnostic to {@link Thread#interrupt()}.</p> 232 * 233 * <p>CAVEAT: when {@code cancellationGroup} is specified and it is signalled, {@code future} 234 * will be cancelled permanently. You have to duplicate the {@link CompletableFuture} if you 235 * want to avoid this side-effect.</p> 236 * 237 * @param future the object to extract the result from. 238 * @param tag tag name for logging. Pass {@code null} to disable logging. 239 * @param methodName method name for logging. Pass {@code null} to disable logging. 240 * @param cancellationGroup an optional {@link CancellationGroup} to cancel {@code future} 241 * object. Can be {@code null}. 242 * @param timeoutMillis length of the timeout in millisecond. 243 * @param <T> Type of the result. 244 * @return the result if it is completed within the given timeout. {@code null} otherwise. 245 */ 246 @AnyThread 247 @Nullable getResultOrNull(@onNull CompletableFuture<T> future, @Nullable String tag, @Nullable String methodName, @Nullable CancellationGroup cancellationGroup, @DurationMillisLong long timeoutMillis)248 public static <T> T getResultOrNull(@NonNull CompletableFuture<T> future, @Nullable String tag, 249 @Nullable String methodName, @Nullable CancellationGroup cancellationGroup, 250 @DurationMillisLong long timeoutMillis) { 251 return getValueOrNullInternal(future, tag, methodName, timeoutMillis, cancellationGroup); 252 } 253 } 254