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