1 /*
2  * Copyright 2017 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 package android.hardware.location;
17 
18 import android.annotation.CallbackExecutor;
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.SystemApi;
22 import android.os.Handler;
23 import android.os.HandlerExecutor;
24 
25 import java.lang.annotation.Retention;
26 import java.lang.annotation.RetentionPolicy;
27 import java.util.Objects;
28 import java.util.concurrent.CountDownLatch;
29 import java.util.concurrent.Executor;
30 import java.util.concurrent.TimeUnit;
31 import java.util.concurrent.TimeoutException;
32 
33 /**
34  * A class describing a request sent to the Context Hub Service.
35  *
36  * This object is generated as a result of an asynchronous request sent to the Context Hub
37  * through the ContextHubManager APIs. The caller can either retrieve the result
38  * synchronously through a blocking call ({@link #waitForResponse(long, TimeUnit)}) or
39  * asynchronously through a user-defined listener
40  * ({@link #setOnCompleteListener(OnCompleteListener, Executor)} )}).
41  *
42  * @param <T> the type of the contents in the transaction response
43  *
44  * @hide
45  */
46 @SystemApi
47 public class ContextHubTransaction<T> {
48     private static final String TAG = "ContextHubTransaction";
49 
50     /**
51      * Constants describing the type of a transaction through the Context Hub Service.
52      * {@hide}
53      */
54     @Retention(RetentionPolicy.SOURCE)
55     @IntDef(prefix = { "TYPE_" }, value = {
56             TYPE_LOAD_NANOAPP,
57             TYPE_UNLOAD_NANOAPP,
58             TYPE_ENABLE_NANOAPP,
59             TYPE_DISABLE_NANOAPP,
60             TYPE_QUERY_NANOAPPS
61     })
62     public @interface Type { }
63 
64     public static final int TYPE_LOAD_NANOAPP = 0;
65     public static final int TYPE_UNLOAD_NANOAPP = 1;
66     public static final int TYPE_ENABLE_NANOAPP = 2;
67     public static final int TYPE_DISABLE_NANOAPP = 3;
68     public static final int TYPE_QUERY_NANOAPPS = 4;
69 
70     /**
71      * Constants describing the result of a transaction or request through the Context Hub Service.
72      * {@hide}
73      */
74     @Retention(RetentionPolicy.SOURCE)
75     @IntDef(prefix = { "RESULT_" }, value = {
76             RESULT_SUCCESS,
77             RESULT_FAILED_UNKNOWN,
78             RESULT_FAILED_BAD_PARAMS,
79             RESULT_FAILED_UNINITIALIZED,
80             RESULT_FAILED_BUSY,
81             RESULT_FAILED_AT_HUB,
82             RESULT_FAILED_TIMEOUT,
83             RESULT_FAILED_SERVICE_INTERNAL_FAILURE,
84             RESULT_FAILED_HAL_UNAVAILABLE
85     })
86     public @interface Result {}
87     public static final int RESULT_SUCCESS = 0;
88     /**
89      * Generic failure mode.
90      */
91     public static final int RESULT_FAILED_UNKNOWN = 1;
92     /**
93      * Failure mode when the request parameters were not valid.
94      */
95     public static final int RESULT_FAILED_BAD_PARAMS = 2;
96     /**
97      * Failure mode when the Context Hub is not initialized.
98      */
99     public static final int RESULT_FAILED_UNINITIALIZED = 3;
100     /**
101      * Failure mode when there are too many transactions pending.
102      */
103     public static final int RESULT_FAILED_BUSY = 4;
104     /**
105      * Failure mode when the request went through, but failed asynchronously at the hub.
106      */
107     public static final int RESULT_FAILED_AT_HUB = 5;
108     /**
109      * Failure mode when the transaction has timed out.
110      */
111     public static final int RESULT_FAILED_TIMEOUT = 6;
112     /**
113      * Failure mode when the transaction has failed internally at the service.
114      */
115     public static final int RESULT_FAILED_SERVICE_INTERNAL_FAILURE = 7;
116     /**
117      * Failure mode when the Context Hub HAL was not available.
118      */
119     public static final int RESULT_FAILED_HAL_UNAVAILABLE = 8;
120 
121     /**
122      * A class describing the response for a ContextHubTransaction.
123      *
124      * @param <R> the type of the contents in the response
125      */
126     public static class Response<R> {
127         /*
128          * The result of the transaction.
129          */
130         @ContextHubTransaction.Result
131         private int mResult;
132 
133         /*
134          * The contents of the response from the Context Hub.
135          */
136         private R mContents;
137 
Response(@ontextHubTransaction.Result int result, R contents)138         Response(@ContextHubTransaction.Result int result, R contents) {
139             mResult = result;
140             mContents = contents;
141         }
142 
143         @ContextHubTransaction.Result
getResult()144         public int getResult() {
145             return mResult;
146         }
147 
getContents()148         public R getContents() {
149             return mContents;
150         }
151     }
152 
153     /**
154      * An interface describing the listener for a transaction completion.
155      *
156      * @param <L> the type of the contents in the transaction response
157      */
158     @FunctionalInterface
159     public interface OnCompleteListener<L> {
160         /**
161          * The listener function to invoke when the transaction completes.
162          *
163          * @param transaction the transaction that this callback was attached to.
164          * @param response the response of the transaction.
165          */
onComplete( ContextHubTransaction<L> transaction, ContextHubTransaction.Response<L> response)166         void onComplete(
167                 ContextHubTransaction<L> transaction, ContextHubTransaction.Response<L> response);
168     }
169 
170     /*
171      * The type of the transaction.
172      */
173     @Type
174     private int mTransactionType;
175 
176     /*
177      * The response of the transaction.
178      */
179     private ContextHubTransaction.Response<T> mResponse;
180 
181     /*
182      * The executor to invoke the onComplete async callback.
183      */
184     private Executor mExecutor = null;
185 
186     /*
187      * The listener to be invoked when the transaction completes.
188      */
189     private ContextHubTransaction.OnCompleteListener<T> mListener = null;
190 
191     /*
192      * Synchronization latch used to block on response.
193      */
194     private final CountDownLatch mDoneSignal = new CountDownLatch(1);
195 
196     /*
197      * true if the response has been set throught setResponse, false otherwise.
198      */
199     private boolean mIsResponseSet = false;
200 
ContextHubTransaction(@ype int type)201     ContextHubTransaction(@Type int type) {
202         mTransactionType = type;
203     }
204 
205     /**
206      * Converts a transaction type to a human-readable string
207      *
208      * @param type the type of a transaction
209      * @param upperCase {@code true} if upper case the first letter, {@code false} otherwise
210      * @return a string describing the transaction
211      */
typeToString(@ype int type, boolean upperCase)212     public static String typeToString(@Type int type, boolean upperCase) {
213         switch (type) {
214             case ContextHubTransaction.TYPE_LOAD_NANOAPP:
215                 return upperCase ? "Load" : "load";
216             case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
217                 return upperCase ? "Unload" : "unload";
218             case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
219                 return upperCase ? "Enable" : "enable";
220             case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
221                 return upperCase ? "Disable" : "disable";
222             case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
223                 return upperCase ? "Query" : "query";
224             default:
225                 return upperCase ? "Unknown" : "unknown";
226         }
227     }
228 
229     /**
230      * @return the type of the transaction
231      */
232     @Type
getType()233     public int getType() {
234         return mTransactionType;
235     }
236 
237     /**
238      * Waits to receive the asynchronous transaction result.
239      *
240      * This function blocks until the Context Hub Service has received a response
241      * for the transaction represented by this object by the Context Hub, or a
242      * specified timeout period has elapsed.
243      *
244      * If the specified timeout has passed, a TimeoutException will be thrown and the caller may
245      * retry the invocation of this method at a later time.
246      *
247      * @param timeout the timeout duration
248      * @param unit the unit of the timeout
249      *
250      * @return the transaction response
251      *
252      * @throws InterruptedException if the current thread is interrupted while waiting for response
253      * @throws TimeoutException if the timeout period has passed
254      */
waitForResponse( long timeout, TimeUnit unit)255     public ContextHubTransaction.Response<T> waitForResponse(
256             long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {
257         boolean success = mDoneSignal.await(timeout, unit);
258 
259         if (!success) {
260             throw new TimeoutException("Timed out while waiting for transaction");
261         }
262 
263         return mResponse;
264     }
265 
266     /**
267      * Sets the listener to be invoked invoked when the transaction completes.
268      *
269      * This function provides an asynchronous approach to retrieve the result of the
270      * transaction. When the transaction response has been provided by the Context Hub,
271      * the given listener will be invoked.
272      *
273      * If the transaction has already completed at the time of invocation, the listener
274      * will be immediately invoked. If the transaction has been invalidated,
275      * the listener will never be invoked.
276      *
277      * A transaction can be invalidated if the process owning the transaction is no longer active
278      * and the reference to this object is lost.
279      *
280      * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener)} can
281      * only be invoked once, or an IllegalStateException will be thrown.
282      *
283      * @param listener the listener to be invoked upon completion
284      * @param executor the executor to invoke the callback
285      *
286      * @throws IllegalStateException if this method is called multiple times
287      * @throws NullPointerException if the callback or handler is null
288      */
setOnCompleteListener( @onNull ContextHubTransaction.OnCompleteListener<T> listener, @NonNull @CallbackExecutor Executor executor)289     public void setOnCompleteListener(
290             @NonNull ContextHubTransaction.OnCompleteListener<T> listener,
291             @NonNull @CallbackExecutor Executor executor) {
292         synchronized (this) {
293             Objects.requireNonNull(listener, "OnCompleteListener cannot be null");
294             Objects.requireNonNull(executor, "Executor cannot be null");
295             if (mListener != null) {
296                 throw new IllegalStateException(
297                         "Cannot set ContextHubTransaction listener multiple times");
298             }
299 
300             mListener = listener;
301             mExecutor = executor;
302 
303             if (mDoneSignal.getCount() == 0) {
304                 mExecutor.execute(() -> mListener.onComplete(this, mResponse));
305             }
306         }
307     }
308 
309     /**
310      * Sets the listener to be invoked invoked when the transaction completes.
311      *
312      * Equivalent to {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener,
313      * Executor)} with the executor using the main thread's Looper.
314      *
315      * This method or {@link #setOnCompleteListener(ContextHubTransaction.OnCompleteListener,
316      * Executor)} can only be invoked once, or an IllegalStateException will be thrown.
317      *
318      * @param listener the listener to be invoked upon completion
319      *
320      * @throws IllegalStateException if this method is called multiple times
321      * @throws NullPointerException if the callback is null
322      */
setOnCompleteListener( @onNull ContextHubTransaction.OnCompleteListener<T> listener)323     public void setOnCompleteListener(
324             @NonNull ContextHubTransaction.OnCompleteListener<T> listener) {
325         setOnCompleteListener(listener, new HandlerExecutor(Handler.getMain()));
326     }
327 
328     /**
329      * Sets the response of the transaction.
330      *
331      * This method should only be invoked by ContextHubManager as a result of a callback from
332      * the Context Hub Service indicating the response from a transaction. This method should not be
333      * invoked more than once.
334      *
335      * @param response the response to set
336      *
337      * @throws IllegalStateException if this method is invoked multiple times
338      * @throws NullPointerException if the response is null
339      */
setResponse(ContextHubTransaction.Response<T> response)340     /* package */ void setResponse(ContextHubTransaction.Response<T> response) {
341         synchronized (this) {
342             Objects.requireNonNull(response, "Response cannot be null");
343             if (mIsResponseSet) {
344                 throw new IllegalStateException(
345                         "Cannot set response of ContextHubTransaction multiple times");
346             }
347 
348             mResponse = response;
349             mIsResponseSet = true;
350 
351             mDoneSignal.countDown();
352             if (mListener != null) {
353                 mExecutor.execute(() -> mListener.onComplete(this, mResponse));
354             }
355         }
356     }
357 }
358