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