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