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