1 /* 2 * Copyright (C) 2022 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 androidx.core.uwb.backend.impl.internal; 18 19 import static androidx.core.uwb.backend.impl.internal.Utils.TAG; 20 21 import static java.util.concurrent.TimeUnit.MILLISECONDS; 22 23 import android.util.Log; 24 25 import androidx.annotation.Nullable; 26 import androidx.annotation.WorkerThread; 27 import androidx.concurrent.futures.CallbackToFutureAdapter; 28 import androidx.concurrent.futures.CallbackToFutureAdapter.Completer; 29 30 import com.google.common.util.concurrent.ListenableFuture; 31 32 import java.util.concurrent.ExecutionException; 33 import java.util.concurrent.TimeoutException; 34 35 /** 36 * Execute an operation and wait for its completion. 37 * 38 * <p>Typical usage: Execute an operation that should trigger an asynchronous callback. When the 39 * callback is invoked, inside the callback the opCompleter is set and unblocks the execution. 40 * 41 * @param <T> T is the type of the value that sets in operation's completion. 42 */ 43 public class OpAsyncCallbackRunner<T> { 44 45 /** Default timeout value of an operation */ 46 private static final int DEFAULT_OPERATION_TIMEOUT_MILLIS = 3000; 47 48 private int mOperationTimeoutMillis = DEFAULT_OPERATION_TIMEOUT_MILLIS; 49 50 @Nullable private Completer<T> mOpCompleter; 51 52 @Nullable private T mResult; 53 54 private boolean mActive = false; 55 56 /** Set the timeout value in Millis */ setOperationTimeoutMillis(int timeoutMillis)57 public void setOperationTimeoutMillis(int timeoutMillis) { 58 mOperationTimeoutMillis = timeoutMillis; 59 } 60 61 /** Completes the operation and set the result */ complete(T result)62 public void complete(T result) { 63 if (!mActive) { 64 throw new IllegalStateException("Calling complete() without active operation."); 65 } 66 Completer<T> opCompleter = this.mOpCompleter; 67 if (opCompleter != null) { 68 opCompleter.set(result); 69 this.mResult = result; 70 } 71 } 72 73 /** Complete the operation if active, useful for unexpected callback. */ completeIfActive(T result)74 public synchronized void completeIfActive(T result) { 75 if (!mActive) { 76 return; 77 } 78 Completer<T> opCompleter = this.mOpCompleter; 79 if (opCompleter != null) { 80 opCompleter.set(result); 81 this.mResult = result; 82 } 83 } 84 85 @Nullable getResult()86 public T getResult() { 87 return mResult; 88 } 89 90 /** 91 * Execute op in current thread and wait until the completer is set. Since this is a blocking 92 * operation, make sure it's not running on main thread. 93 */ 94 @WorkerThread execOperation(Runnable op, String opDescription)95 public boolean execOperation(Runnable op, String opDescription) { 96 mResult = null; 97 if (mActive) { 98 throw new IllegalStateException("Calling execOperation() while operation is running."); 99 } 100 mActive = true; 101 ListenableFuture<T> opFuture = 102 CallbackToFutureAdapter.getFuture( 103 completer -> { 104 mOpCompleter = completer; 105 op.run(); 106 return "Async " + opDescription; 107 }); 108 try { 109 mResult = opFuture.get(mOperationTimeoutMillis, MILLISECONDS); 110 return mResult != null; 111 } catch (TimeoutException e) { 112 Log.w(TAG, String.format("Callback timeout in Op %s", opDescription), e); 113 return false; 114 } catch (InterruptedException e) { 115 Thread.currentThread().interrupt(); 116 return false; 117 } catch (ExecutionException e) { 118 Log.w(TAG, String.format("ExecutionException in Op %s", opDescription), e); 119 return false; 120 } finally { 121 mOpCompleter = null; 122 mActive = false; 123 } 124 } 125 isActive()126 public boolean isActive() { 127 return mActive; 128 } 129 } 130