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