• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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 static java.lang.annotation.RetentionPolicy.SOURCE;
20 
21 import android.annotation.AnyThread;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.util.Log;
26 import android.view.inputmethod.InputMethodSubtype;
27 
28 import com.android.internal.annotations.GuardedBy;
29 
30 import java.lang.annotation.Retention;
31 import java.util.List;
32 import java.util.concurrent.CountDownLatch;
33 import java.util.concurrent.TimeUnit;
34 
35 /**
36  * An class to consolidate completable object types supported by
37  * {@link CancellationGroup}.
38  */
39 public final class Completable {
40 
41     /**
42      * Not intended to be instantiated.
43      */
Completable()44     private Completable() {
45     }
46 
47     /**
48      * Base class of all the completable types supported by {@link CancellationGroup}.
49      */
50     protected static class ValueBase {
51         /**
52          * {@link CountDownLatch} to be signaled to unblock
53          * {@link #await(int, TimeUnit, CancellationGroup)}.
54          */
55         private final CountDownLatch mLatch = new CountDownLatch(1);
56 
57         /**
58          * Lock {@link Object} to guard complete operations within this class.
59          */
60         protected final Object mStateLock = new Object();
61 
62         /**
63          * Indicates the completion state of this object.
64          */
65         @GuardedBy("mStateLock")
66         @CompletionState
67         protected int mState = CompletionState.NOT_COMPLETED;
68 
69         /**
70          * {@link Throwable} message passed to {@link #onError(ThrowableHolder)}.
71          *
72          * <p>This is not {@code null} only when {@link #mState} is
73          * {@link CompletionState#COMPLETED_WITH_ERROR}.</p>
74          */
75         @GuardedBy("mStateLock")
76         @Nullable
77         protected String mMessage = null;
78 
79         @Retention(SOURCE)
80         @IntDef({
81                 CompletionState.NOT_COMPLETED,
82                 CompletionState.COMPLETED_WITH_VALUE,
83                 CompletionState.COMPLETED_WITH_ERROR})
84         protected @interface CompletionState {
85             /**
86              * This object is not completed yet.
87              */
88             int NOT_COMPLETED = 0;
89             /**
90              * This object is already completed with a value.
91              */
92             int COMPLETED_WITH_VALUE = 1;
93             /**
94              * This object is already completed with an error.
95              */
96             int COMPLETED_WITH_ERROR = 2;
97         }
98 
99         /**
100          * Converts the given {@link CompletionState} into a human-readable string.
101          *
102          * @param state {@link CompletionState} to be converted.
103          * @return a human-readable {@link String} for the given {@code state}.
104          */
105         @AnyThread
stateToString(@ompletionState int state)106         protected static String stateToString(@CompletionState int state) {
107             switch (state) {
108                 case CompletionState.NOT_COMPLETED:
109                     return "NOT_COMPLETED";
110                 case CompletionState.COMPLETED_WITH_VALUE:
111                     return "COMPLETED_WITH_VALUE";
112                 case CompletionState.COMPLETED_WITH_ERROR:
113                     return "COMPLETED_WITH_ERROR";
114                 default:
115                     return "Unknown(value=" + state + ")";
116             }
117         }
118 
119         /**
120          * @return {@code true} if {@link #onComplete()} gets called and {@link #mState} is
121          *         {@link CompletionState#COMPLETED_WITH_VALUE}.
122          */
123         @AnyThread
hasValue()124         public boolean hasValue() {
125             synchronized (mStateLock) {
126                 return mState == CompletionState.COMPLETED_WITH_VALUE;
127             }
128         }
129 
130         /**
131          * Provides the base implementation of {@code getValue()} for derived classes.
132          *
133          * <p>Must be called after acquiring {@link #mStateLock}.</p>
134          *
135          * @throws RuntimeException when {@link #mState} is
136          *                          {@link CompletionState#COMPLETED_WITH_ERROR}.
137          * @throws UnsupportedOperationException when {@link #mState} is not
138          *                                       {@link CompletionState#COMPLETED_WITH_VALUE} and
139          *                                       {@link CompletionState#COMPLETED_WITH_ERROR}.
140          */
141         @GuardedBy("mStateLock")
enforceGetValueLocked()142         protected void enforceGetValueLocked() {
143             switch (mState) {
144                 case CompletionState.NOT_COMPLETED:
145                     throw new UnsupportedOperationException(
146                             "getValue() is allowed only if hasValue() returns true");
147                 case CompletionState.COMPLETED_WITH_VALUE:
148                     return;
149                 case CompletionState.COMPLETED_WITH_ERROR:
150                     throw new RuntimeException(mMessage);
151                 default:
152                     throw new UnsupportedOperationException(
153                             "getValue() is not allowed on state=" + stateToString(mState));
154             }
155         }
156 
157         /**
158          * Called by subclasses to signale {@link #mLatch}.
159          */
160         @AnyThread
onComplete()161         protected void onComplete() {
162             mLatch.countDown();
163         }
164 
165         /**
166          * Notify when exception happened.
167          *
168          * @param throwableHolder contains the {@link Throwable} object when exception happened.
169          */
170         @AnyThread
onError(ThrowableHolder throwableHolder)171         protected void onError(ThrowableHolder throwableHolder) {
172             synchronized (mStateLock) {
173                 switch (mState) {
174                     case CompletionState.NOT_COMPLETED:
175                         mMessage = throwableHolder.getMessage();
176                         mState = CompletionState.COMPLETED_WITH_ERROR;
177                         break;
178                     default:
179                         throw new UnsupportedOperationException(
180                                 "onError() is not allowed on state=" + stateToString(mState));
181                 }
182             }
183             onComplete();
184         }
185 
186         /**
187          * Blocks the calling thread until at least one of the following conditions is met.
188          *
189          * <p>
190          *     <ol>
191          *         <li>This object becomes ready to return the value.</li>
192          *         <li>{@link CancellationGroup#cancelAll()} gets called.</li>
193          *         <li>The given timeout period has passed.</li>
194          *     </ol>
195          * </p>
196          *
197          * <p>The caller can distinguish the case 1 and case 2 by calling {@link #hasValue()}.
198          * Note that the return value of {@link #hasValue()} can change from {@code false} to
199          * {@code true} at any time, even after this methods finishes with returning
200          * {@code true}.</p>
201          *
202          * @param timeout length of the timeout.
203          * @param timeUnit unit of {@code timeout}.
204          * @param cancellationGroup {@link CancellationGroup} to cancel completable objects.
205          * @return {@code false} if and only if the given timeout period has passed. Otherwise
206          *         {@code true}.
207          */
208         @AnyThread
await(int timeout, @NonNull TimeUnit timeUnit, @Nullable CancellationGroup cancellationGroup)209         public boolean await(int timeout, @NonNull TimeUnit timeUnit,
210                 @Nullable CancellationGroup cancellationGroup) {
211             if (cancellationGroup == null) {
212                 return awaitInner(timeout, timeUnit);
213             }
214 
215             if (!cancellationGroup.registerLatch(mLatch)) {
216                 // Already canceled when this method gets called.
217                 return false;
218             }
219             try {
220                 return awaitInner(timeout, timeUnit);
221             } finally {
222                 cancellationGroup.unregisterLatch(mLatch);
223             }
224         }
225 
awaitInner(int timeout, @NonNull TimeUnit timeUnit)226         private boolean awaitInner(int timeout, @NonNull TimeUnit timeUnit) {
227             try {
228                 return mLatch.await(timeout, timeUnit);
229             } catch (InterruptedException e) {
230                 return true;
231             }
232         }
233 
234         /**
235          * Blocks the calling thread until this object becomes ready to return the value, even if
236          * {@link InterruptedException} is thrown.
237          */
238         @AnyThread
await()239         public void await() {
240             boolean interrupted = false;
241             while (true) {
242                 try {
243                     mLatch.await();
244                     break;
245                 } catch (InterruptedException ignored) {
246                     interrupted = true;
247                 }
248             }
249 
250             if (interrupted) {
251                 // Try to preserve the interrupt bit on this thread.
252                 Thread.currentThread().interrupt();
253             }
254         }
255     }
256 
257     /**
258      * Completable object of integer primitive.
259      */
260     public static final class Int extends ValueBase {
261         @GuardedBy("mStateLock")
262         private int mValue = 0;
263 
264         /**
265          * Notify when a value is set to this completable object.
266          *
267          * @param value value to be set.
268          */
269         @AnyThread
onComplete(int value)270         void onComplete(int value) {
271             synchronized (mStateLock) {
272                 switch (mState) {
273                     case CompletionState.NOT_COMPLETED:
274                         mValue = value;
275                         mState = CompletionState.COMPLETED_WITH_VALUE;
276                         break;
277                     default:
278                         throw new UnsupportedOperationException(
279                                 "onComplete() is not allowed on state=" + stateToString(mState));
280                 }
281             }
282             onComplete();
283         }
284 
285         /**
286          * @return value associated with this object.
287          * @throws RuntimeException when called while {@link #onError} happened.
288          * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
289          *                                       {@code false}.
290          */
291         @AnyThread
getValue()292         public int getValue() {
293             synchronized (mStateLock) {
294                 enforceGetValueLocked();
295                 return mValue;
296             }
297         }
298     }
299 
300     /**
301      * Completable object of {@link java.lang.Void}.
302      */
303     public static final class Void extends ValueBase {
304         /**
305          * Notify when this completable object callback.
306          */
307         @AnyThread
308         @Override
onComplete()309         protected void onComplete() {
310             synchronized (mStateLock) {
311                 switch (mState) {
312                     case CompletionState.NOT_COMPLETED:
313                         mState = CompletionState.COMPLETED_WITH_VALUE;
314                         break;
315                     default:
316                         throw new UnsupportedOperationException(
317                                 "onComplete() is not allowed on state=" + stateToString(mState));
318                 }
319             }
320             super.onComplete();
321         }
322 
323         /**
324          * @throws RuntimeException when called while {@link #onError} happened.
325          * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
326          *                                       {@code false}.
327          */
328         @AnyThread
getValue()329         public void getValue() {
330             synchronized (mStateLock) {
331                 enforceGetValueLocked();
332             }
333         }
334     }
335 
336     /**
337      * Base class of completable object types.
338      *
339      * @param <T> type associated with this completable object.
340      */
341     public static class Values<T> extends ValueBase {
342         @GuardedBy("mStateLock")
343         @Nullable
344         private T mValue = null;
345 
346         /**
347          * Notify when a value is set to this completable value object.
348          *
349          * @param value value to be set.
350          */
351         @AnyThread
onComplete(@ullable T value)352         void onComplete(@Nullable T value) {
353             synchronized (mStateLock) {
354                 switch (mState) {
355                     case CompletionState.NOT_COMPLETED:
356                         mValue = value;
357                         mState = CompletionState.COMPLETED_WITH_VALUE;
358                         break;
359                     default:
360                         throw new UnsupportedOperationException(
361                                 "onComplete() is not allowed on state=" + stateToString(mState));
362                 }
363             }
364             onComplete();
365         }
366 
367         /**
368          * @return value associated with this object.
369          * @throws RuntimeException when called while {@link #onError} happened
370          * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
371          *                                       {@code false}.
372          */
373         @AnyThread
374         @Nullable
getValue()375         public T getValue() {
376             synchronized (mStateLock) {
377                 enforceGetValueLocked();
378                 return mValue;
379             }
380         }
381     }
382 
383     /**
384      * @return an instance of {@link Completable.Int}.
385      */
createInt()386     public static Completable.Int createInt() {
387         return new Completable.Int();
388     }
389 
390     /**
391      * @return an instance of {@link Completable.Boolean}.
392      */
createBoolean()393     public static Completable.Boolean createBoolean() {
394         return new Completable.Boolean();
395     }
396 
397     /**
398      * @return an instance of {@link Completable.CharSequence}.
399      */
createCharSequence()400     public static Completable.CharSequence createCharSequence() {
401         return new Completable.CharSequence();
402     }
403 
404     /**
405      * @return an instance of {@link Completable.ExtractedText}.
406      */
createExtractedText()407     public static Completable.ExtractedText createExtractedText() {
408         return new Completable.ExtractedText();
409     }
410 
411     /**
412      * @return an instance of {@link Completable.SurroundingText}.
413      */
createSurroundingText()414     public static Completable.SurroundingText createSurroundingText() {
415         return new Completable.SurroundingText();
416     }
417 
418     /**
419      * @return an instance of {@link Completable.InputBindResult}.
420      */
createInputBindResult()421     public static Completable.InputBindResult createInputBindResult() {
422         return new Completable.InputBindResult();
423     }
424 
425     /**
426      * @return an instance of {@link Completable.InputMethodSubtype}.
427      */
createInputMethodSubtype()428     public static Completable.InputMethodSubtype createInputMethodSubtype() {
429         return new Completable.InputMethodSubtype();
430     }
431 
432     /**
433      * @return an instance of {@link Completable.InputMethodSubtypeList}.
434      */
createInputMethodSubtypeList()435     public static Completable.InputMethodSubtypeList createInputMethodSubtypeList() {
436         return new Completable.InputMethodSubtypeList();
437     }
438 
439     /**
440      * @return an instance of {@link Completable.InputMethodInfoList}.
441      */
createInputMethodInfoList()442     public static Completable.InputMethodInfoList createInputMethodInfoList() {
443         return new Completable.InputMethodInfoList();
444     }
445 
446     /**
447      * @return an instance of {@link Completable.IInputContentUriToken}.
448      */
createIInputContentUriToken()449     public static Completable.IInputContentUriToken createIInputContentUriToken() {
450         return new Completable.IInputContentUriToken();
451     }
452 
453     /**
454      * @return an instance of {@link Completable.Void}.
455      */
createVoid()456     public static Completable.Void createVoid() {
457         return new Completable.Void();
458     }
459 
460     /**
461      * Completable object of {@link java.lang.Boolean}.
462      */
463     public static final class Boolean extends Values<java.lang.Boolean> { }
464 
465     /**
466      * Completable object of {@link java.lang.CharSequence}.
467      */
468     public static final class CharSequence extends Values<java.lang.CharSequence> { }
469 
470     /**
471      * Completable object of {@link android.view.inputmethod.ExtractedText}.
472      */
473     public static final class ExtractedText
474             extends Values<android.view.inputmethod.ExtractedText> { }
475 
476     /**
477      * Completable object of {@link android.view.inputmethod.SurroundingText}.
478      */
479     public static final class SurroundingText
480             extends Values<android.view.inputmethod.SurroundingText> { }
481 
482     /**
483      * Completable object of {@link com.android.internal.view.InputBindResult}.
484      */
485     public static final class InputBindResult
486             extends Values<com.android.internal.view.InputBindResult> { }
487 
488     /**
489      * Completable object of {@link android.view.inputmethod.InputMethodSubtype}.
490      */
491     public static final class InputMethodSubtype
492             extends Values<android.view.inputmethod.InputMethodSubtype> { }
493 
494     /**
495      * Completable object of {@link List<android.view.inputmethod.InputMethodSubtype>}.
496      */
497     public static final class InputMethodSubtypeList
498             extends Values<List<android.view.inputmethod.InputMethodSubtype>> { }
499 
500     /**
501      * Completable object of {@link List<android.view.inputmethod.InputMethodInfo>}.
502      */
503     public static final class InputMethodInfoList
504             extends Values<List<android.view.inputmethod.InputMethodInfo>> { }
505 
506     /**
507      * Completable object of {@link IInputContentUriToken>}.
508      */
509     public static final class IInputContentUriToken
510             extends Values<com.android.internal.inputmethod.IInputContentUriToken> { }
511 
512     /**
513      * Await the result by the {@link Completable.Values}.
514      *
515      * @return the result once {@link ValueBase#onComplete()}.
516      */
517     @AnyThread
518     @Nullable
getResult(@onNull Completable.Values<T> value)519     public static <T> T getResult(@NonNull Completable.Values<T> value) {
520         value.await();
521         return value.getValue();
522     }
523 
524     /**
525      * Await the int result by the {@link Completable.Int}.
526      *
527      * @return the result once {@link ValueBase#onComplete()}.
528      */
529     @AnyThread
getIntResult(@onNull Completable.Int value)530     public static int getIntResult(@NonNull Completable.Int value) {
531         value.await();
532         return value.getValue();
533     }
534 
535     /**
536      * Await the result by the {@link Completable.Void}.
537      *
538      * Check the result once {@link ValueBase#onComplete()}
539      */
540     @AnyThread
getResult(@onNull Completable.Void value)541     public static void getResult(@NonNull Completable.Void value) {
542         value.await();
543         value.getValue();
544     }
545 
546     /**
547      * Await the result by the {@link Completable.Int}, and log it if there is no result after
548      * given timeout.
549      *
550      * @return the result once {@link ValueBase#onComplete()}
551      */
552     @AnyThread
getResultOrZero(@onNull Completable.Int value, String tag, @NonNull String methodName, @Nullable CancellationGroup cancellationGroup, int maxWaitTime)553     public static int getResultOrZero(@NonNull Completable.Int value, String tag,
554             @NonNull String methodName, @Nullable CancellationGroup cancellationGroup,
555             int maxWaitTime) {
556         final boolean timedOut = value.await(maxWaitTime, TimeUnit.MILLISECONDS, cancellationGroup);
557         if (value.hasValue()) {
558             return value.getValue();
559         }
560         logInternal(tag, methodName, timedOut, maxWaitTime, 0);
561         return 0;
562     }
563 
564     /**
565      * Await the result by the {@link Completable.Values}, and log it if there is no result after
566      * given timeout.
567      *
568      * @return the result once {@link ValueBase#onComplete()}
569      */
570     @AnyThread
571     @Nullable
getResultOrNull(@onNull Completable.Values<T> value, String tag, @NonNull String methodName, @Nullable CancellationGroup cancellationGroup, int maxWaitTime)572     public static <T> T getResultOrNull(@NonNull Completable.Values<T> value, String tag,
573             @NonNull String methodName, @Nullable CancellationGroup cancellationGroup,
574             int maxWaitTime) {
575         final boolean timedOut = value.await(maxWaitTime, TimeUnit.MILLISECONDS, cancellationGroup);
576         if (value.hasValue()) {
577             return value.getValue();
578         }
579         logInternal(tag, methodName, timedOut, maxWaitTime, null);
580         return null;
581     }
582 
583     @AnyThread
logInternal(String tag, @Nullable String methodName, boolean timedOut, int maxWaitTime, @Nullable Object defaultValue)584     private static void logInternal(String tag, @Nullable String methodName, boolean timedOut,
585             int maxWaitTime, @Nullable Object defaultValue) {
586         if (timedOut) {
587             Log.w(tag, methodName + " didn't respond in " + maxWaitTime + " msec."
588                     + " Returning default: " + defaultValue);
589         } else {
590             Log.w(tag, methodName + " was canceled before complete. Returning default: "
591                     + defaultValue);
592         }
593     }
594 }
595