1 /*
2  * Copyright (C) 2016 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.Nullable;
22 import android.annotation.RequiresFeature;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SuppressLint;
25 import android.annotation.SystemApi;
26 import android.annotation.SystemService;
27 import android.app.PendingIntent;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.PackageManager;
31 import android.os.Handler;
32 import android.os.HandlerExecutor;
33 import android.os.Looper;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.os.ServiceManager.ServiceNotFoundException;
37 import android.util.Log;
38 
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.util.List;
42 import java.util.Objects;
43 import java.util.concurrent.Executor;
44 
45 /**
46  * A class that exposes the Context hubs on a device to applications.
47  *
48  * Please note that this class is not expected to be used by unbundled applications. Also, calling
49  * applications are expected to have LOCATION_HARDWARE or ACCESS_CONTEXT_HUB permissions to use this
50  * class. Use of LOCATION_HARDWARE to enable access to these APIs is deprecated and may be removed
51  * in the future - all applications are recommended to move to the ACCESS_CONTEXT_HUB permission.
52  *
53  * @hide
54  */
55 @SystemApi
56 @SystemService(Context.CONTEXTHUB_SERVICE)
57 @RequiresFeature(PackageManager.FEATURE_CONTEXT_HUB)
58 public final class ContextHubManager {
59     private static final String TAG = "ContextHubManager";
60 
61     /**
62      * An extra of type {@link ContextHubInfo} describing the source of the event.
63      */
64     public static final String EXTRA_CONTEXT_HUB_INFO =
65             "android.hardware.location.extra.CONTEXT_HUB_INFO";
66 
67     /**
68      * An extra of type {@link ContextHubManager.Event} describing the event type.
69      */
70     public static final String EXTRA_EVENT_TYPE = "android.hardware.location.extra.EVENT_TYPE";
71 
72     /**
73      * An extra of type long describing the ID of the nanoapp an event is for.
74      */
75     public static final String EXTRA_NANOAPP_ID = "android.hardware.location.extra.NANOAPP_ID";
76 
77     /**
78      * An extra of type int describing the nanoapp-specific abort code.
79      */
80     public static final String EXTRA_NANOAPP_ABORT_CODE =
81             "android.hardware.location.extra.NANOAPP_ABORT_CODE";
82 
83     /**
84      * An extra of type {@link NanoAppMessage} describing contents of a message from a nanoapp.
85      */
86     public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE";
87 
88     /**
89      * Constants describing the type of events from a Context Hub.
90      * {@hide}
91      */
92     @Retention(RetentionPolicy.SOURCE)
93     @IntDef(prefix = { "EVENT_" }, value = {
94         EVENT_NANOAPP_LOADED,
95         EVENT_NANOAPP_UNLOADED,
96         EVENT_NANOAPP_ENABLED,
97         EVENT_NANOAPP_DISABLED,
98         EVENT_NANOAPP_ABORTED,
99         EVENT_NANOAPP_MESSAGE,
100         EVENT_HUB_RESET,
101     })
102     public @interface Event { }
103 
104     /**
105      * An event describing that a nanoapp has been loaded. Contains the EXTRA_NANOAPP_ID extra.
106      */
107     public static final int EVENT_NANOAPP_LOADED = 0;
108 
109     /**
110      * An event describing that a nanoapp has been unloaded. Contains the EXTRA_NANOAPP_ID extra.
111      */
112     public static final int EVENT_NANOAPP_UNLOADED = 1;
113 
114     /**
115      * An event describing that a nanoapp has been enabled. Contains the EXTRA_NANOAPP_ID extra.
116      */
117     public static final int EVENT_NANOAPP_ENABLED = 2;
118 
119     /**
120      * An event describing that a nanoapp has been disabled. Contains the EXTRA_NANOAPP_ID extra.
121      */
122     public static final int EVENT_NANOAPP_DISABLED = 3;
123 
124     /**
125      * An event describing that a nanoapp has aborted. Contains the EXTRA_NANOAPP_ID and
126      * EXTRA_NANOAPP_ABORT_CODE extras.
127      */
128     public static final int EVENT_NANOAPP_ABORTED = 4;
129 
130     /**
131      * An event containing a message sent from a nanoapp. Contains the EXTRA_NANOAPP_ID and
132      * EXTRA_NANOAPP_MESSAGE extras.
133      */
134     public static final int EVENT_NANOAPP_MESSAGE = 5;
135 
136     /**
137      * An event describing that the Context Hub has reset.
138      */
139     public static final int EVENT_HUB_RESET = 6;
140 
141     private final Looper mMainLooper;
142     private final IContextHubService mService;
143     private Callback mCallback;
144     private Handler mCallbackHandler;
145 
146     /**
147      * @deprecated Use {@code mCallback} instead.
148      */
149     @Deprecated
150     private ICallback mLocalCallback;
151 
152     /**
153      * An interface to receive asynchronous communication from the context hub.
154      *
155      * @deprecated Use the more refined {@link android.hardware.location.ContextHubClientCallback}
156      *             instead for notification callbacks.
157      */
158     @Deprecated
159     public abstract static class Callback {
Callback()160         protected Callback() {}
161 
162         /**
163          * Callback function called on message receipt from context hub.
164          *
165          * @param hubHandle Handle (system-wide unique identifier) of the hub of the message.
166          * @param nanoAppHandle Handle (unique identifier) for app instance that sent the message.
167          * @param message The context hub message.
168          *
169          * @see ContextHubMessage
170          */
onMessageReceipt( int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message)171         public abstract void onMessageReceipt(
172                 int hubHandle,
173                 int nanoAppHandle,
174                 @NonNull ContextHubMessage message);
175     }
176 
177     /**
178      * @deprecated Use {@link Callback} instead.
179      * @hide
180      */
181     @Deprecated
182     public interface ICallback {
183         /**
184          * Callback function called on message receipt from context hub.
185          *
186          * @param hubHandle Handle (system-wide unique identifier) of the hub of the message.
187          * @param nanoAppHandle Handle (unique identifier) for app instance that sent the message.
188          * @param message The context hub message.
189          *
190          * @see ContextHubMessage
191          */
onMessageReceipt(int hubHandle, int nanoAppHandle, ContextHubMessage message)192         void onMessageReceipt(int hubHandle, int nanoAppHandle, ContextHubMessage message);
193     }
194 
195     /**
196      * Get a handle to all the context hubs in the system
197      *
198      * @return array of context hub handles
199      *
200      * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the
201      *             new APIs.
202      */
203     @Deprecated
204     @RequiresPermission(anyOf = {
205             android.Manifest.permission.LOCATION_HARDWARE,
206             android.Manifest.permission.ACCESS_CONTEXT_HUB
207     })
getContextHubHandles()208     public int[] getContextHubHandles() {
209         try {
210             return mService.getContextHubHandles();
211         } catch (RemoteException e) {
212             throw e.rethrowFromSystemServer();
213         }
214     }
215 
216     /**
217      * Get more information about a specific hub.
218      *
219      * @param hubHandle Handle (system-wide unique identifier) of a context hub.
220      * @return ContextHubInfo Information about the requested context hub.
221      *
222      * @see ContextHubInfo
223      *
224      * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the
225      *             new APIs.
226      */
227     @Deprecated
228     @RequiresPermission(anyOf = {
229             android.Manifest.permission.LOCATION_HARDWARE,
230             android.Manifest.permission.ACCESS_CONTEXT_HUB
231     })
getContextHubInfo(int hubHandle)232     public ContextHubInfo getContextHubInfo(int hubHandle) {
233         try {
234             return mService.getContextHubInfo(hubHandle);
235         } catch (RemoteException e) {
236             throw e.rethrowFromSystemServer();
237         }
238     }
239 
240     /**
241      * Load a nano app on a specified context hub.
242      *
243      * Note that loading is asynchronous.  When we return from this method,
244      * the nano app (probably) hasn't loaded yet.  Assuming a return of 0
245      * from this method, then the final success/failure for the load, along
246      * with the "handle" for the nanoapp, is all delivered in a byte
247      * string via a call to Callback.onMessageReceipt.
248      *
249      * TODO(b/30784270): Provide a better success/failure and "handle" delivery.
250      *
251      * @param hubHandle handle of context hub to load the app on.
252      * @param app the nanoApp to load on the hub
253      *
254      * @return 0 if the command for loading was sent to the context hub;
255      *         -1 otherwise
256      *
257      * @see NanoApp
258      *
259      * @deprecated Use {@link #loadNanoApp(ContextHubInfo, NanoAppBinary)} instead.
260      */
261     @Deprecated
262     @RequiresPermission(anyOf = {
263             android.Manifest.permission.LOCATION_HARDWARE,
264             android.Manifest.permission.ACCESS_CONTEXT_HUB
265     })
loadNanoApp(int hubHandle, @NonNull NanoApp app)266     public int loadNanoApp(int hubHandle, @NonNull NanoApp app) {
267         try {
268             return mService.loadNanoApp(hubHandle, app);
269         } catch (RemoteException e) {
270             throw e.rethrowFromSystemServer();
271         }
272     }
273 
274     /**
275      * Unload a specified nanoApp
276      *
277      * Note that unloading is asynchronous.  When we return from this method,
278      * the nano app (probably) hasn't unloaded yet.  Assuming a return of 0
279      * from this method, then the final success/failure for the unload is
280      * delivered in a byte string via a call to Callback.onMessageReceipt.
281      *
282      * TODO(b/30784270): Provide a better success/failure delivery.
283      *
284      * @param nanoAppHandle handle of the nanoApp to unload
285      *
286      * @return 0 if the command for unloading was sent to the context hub;
287      *         -1 otherwise
288      *
289      * @deprecated Use {@link #unloadNanoApp(ContextHubInfo, long)} instead.
290      */
291     @Deprecated
292     @RequiresPermission(anyOf = {
293             android.Manifest.permission.LOCATION_HARDWARE,
294             android.Manifest.permission.ACCESS_CONTEXT_HUB
295     })
unloadNanoApp(int nanoAppHandle)296     public int unloadNanoApp(int nanoAppHandle) {
297         try {
298             return mService.unloadNanoApp(nanoAppHandle);
299         } catch (RemoteException e) {
300             throw e.rethrowFromSystemServer();
301         }
302     }
303 
304     /**
305      * get information about the nano app instance
306      *
307      * NOTE: The returned NanoAppInstanceInfo does _not_ contain correct
308      * information for several fields, specifically:
309      * - getName()
310      * - getPublisher()
311      * - getNeededExecMemBytes()
312      * - getNeededReadMemBytes()
313      * - getNeededWriteMemBytes()
314      *
315      * For example, say you call loadNanoApp() with a NanoApp that has
316      * getName() returning "My Name".  Later, if you call getNanoAppInstanceInfo
317      * for that nanoapp, the returned NanoAppInstanceInfo's getName()
318      * method will claim "Preloaded app, unknown", even though you would
319      * have expected "My Name".  For now, as the user, you'll need to
320      * separately track the above fields if they are of interest to you.
321      *
322      * TODO(b/30943489): Have the returned NanoAppInstanceInfo contain the
323      *     correct information.
324      *
325      * @param nanoAppHandle handle of the nanoapp instance
326      * @return NanoAppInstanceInfo the NanoAppInstanceInfo of the nanoapp, or null if the nanoapp
327      *                             does not exist
328      *
329      * @see NanoAppInstanceInfo
330      *
331      * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub
332      *             for loaded nanoapps.
333      */
334     @Deprecated
335     @RequiresPermission(anyOf = {
336             android.Manifest.permission.LOCATION_HARDWARE,
337             android.Manifest.permission.ACCESS_CONTEXT_HUB
338     })
getNanoAppInstanceInfo(int nanoAppHandle)339     @Nullable public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) {
340         try {
341             return mService.getNanoAppInstanceInfo(nanoAppHandle);
342         } catch (RemoteException e) {
343             throw e.rethrowFromSystemServer();
344         }
345     }
346 
347     /**
348      * Find a specified nano app on the system
349      *
350      * @param hubHandle handle of hub to search for nano app
351      * @param filter filter specifying the search criteria for app
352      *
353      * @see NanoAppFilter
354      *
355      * @return int[] Array of handles to any found nano apps
356      *
357      * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub
358      *             for loaded nanoapps.
359      */
360     @Deprecated
361     @RequiresPermission(anyOf = {
362             android.Manifest.permission.LOCATION_HARDWARE,
363             android.Manifest.permission.ACCESS_CONTEXT_HUB
364     })
findNanoAppOnHub(int hubHandle, @NonNull NanoAppFilter filter)365     @NonNull public int[] findNanoAppOnHub(int hubHandle, @NonNull NanoAppFilter filter) {
366         try {
367             return mService.findNanoAppOnHub(hubHandle, filter);
368         } catch (RemoteException e) {
369             throw e.rethrowFromSystemServer();
370         }
371     }
372 
373     /**
374      * Send a message to a specific nano app instance on a context hub.
375      *
376      * Note that the return value of this method only speaks of success
377      * up to the point of sending this to the Context Hub.  It is not
378      * an assurance that the Context Hub successfully sent this message
379      * on to the nanoapp.  If assurance is desired, a protocol should be
380      * established between your code and the nanoapp, with the nanoapp
381      * sending a confirmation message (which will be reported via
382      * Callback.onMessageReceipt).
383      *
384      * @param hubHandle handle of the hub to send the message to
385      * @param nanoAppHandle  handle of the nano app to send to
386      * @param message Message to be sent
387      *
388      * @see ContextHubMessage
389      *
390      * @return int 0 on success, -1 otherwise
391      *
392      * @deprecated Use {@link android.hardware.location.ContextHubClient#sendMessageToNanoApp(
393      *             NanoAppMessage)} instead, after creating a
394      *             {@link android.hardware.location.ContextHubClient} with
395      *             {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
396      *             or {@link #createClient(ContextHubInfo, ContextHubClientCallback)}.
397      */
398     @Deprecated
399     @RequiresPermission(anyOf = {
400             android.Manifest.permission.LOCATION_HARDWARE,
401             android.Manifest.permission.ACCESS_CONTEXT_HUB
402     })
sendMessage(int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message)403     public int sendMessage(int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message) {
404         try {
405             return mService.sendMessage(hubHandle, nanoAppHandle, message);
406         } catch (RemoteException e) {
407             throw e.rethrowFromSystemServer();
408         }
409     }
410 
411     /**
412      * Returns the list of ContextHubInfo objects describing the available Context Hubs.
413      *
414      * @return the list of ContextHubInfo objects
415      *
416      * @see ContextHubInfo
417      */
418     @RequiresPermission(anyOf = {
419             android.Manifest.permission.LOCATION_HARDWARE,
420             android.Manifest.permission.ACCESS_CONTEXT_HUB
421     })
getContextHubs()422     @NonNull public List<ContextHubInfo> getContextHubs() {
423         try {
424             return mService.getContextHubs();
425         } catch (RemoteException e) {
426             throw e.rethrowFromSystemServer();
427         }
428     }
429 
430     /**
431      * Helper function to generate a stub for a non-query transaction callback.
432      *
433      * @param transaction the transaction to unblock when complete
434      *
435      * @return the callback
436      *
437      * @hide
438      */
createTransactionCallback( ContextHubTransaction<Void> transaction)439     private IContextHubTransactionCallback createTransactionCallback(
440             ContextHubTransaction<Void> transaction) {
441         return new IContextHubTransactionCallback.Stub() {
442             @Override
443             public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
444                 Log.e(TAG, "Received a query callback on a non-query request");
445                 transaction.setResponse(new ContextHubTransaction.Response<Void>(
446                         ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
447             }
448 
449             @Override
450             public void onTransactionComplete(int result) {
451                 transaction.setResponse(new ContextHubTransaction.Response<Void>(result, null));
452             }
453         };
454     }
455 
456    /**
457     * Helper function to generate a stub for a query transaction callback.
458     *
459     * @param transaction the transaction to unblock when complete
460     *
461     * @return the callback
462     *
463     * @hide
464     */
465     private IContextHubTransactionCallback createQueryCallback(
466             ContextHubTransaction<List<NanoAppState>> transaction) {
467         return new IContextHubTransactionCallback.Stub() {
468             @Override
469             public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
470                 transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
471                         result, nanoappList));
472             }
473 
474             @Override
475             public void onTransactionComplete(int result) {
476                 Log.e(TAG, "Received a non-query callback on a query request");
477                 transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
478                         ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
479             }
480         };
481     }
482 
483     /**
484      * Loads a nanoapp at the specified Context Hub.
485      *
486      * After the nanoapp binary is successfully loaded at the specified hub, the nanoapp will be in
487      * the enabled state.
488      *
489      * @param hubInfo the hub to load the nanoapp on
490      * @param appBinary The app binary to load
491      *
492      * @return the ContextHubTransaction of the request
493      *
494      * @throws NullPointerException if hubInfo or NanoAppBinary is null
495      *
496      * @see NanoAppBinary
497      */
498     @RequiresPermission(anyOf = {
499             android.Manifest.permission.LOCATION_HARDWARE,
500             android.Manifest.permission.ACCESS_CONTEXT_HUB
501     })
502     @NonNull public ContextHubTransaction<Void> loadNanoApp(
503             @NonNull ContextHubInfo hubInfo, @NonNull NanoAppBinary appBinary) {
504         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
505         Objects.requireNonNull(appBinary, "NanoAppBinary cannot be null");
506 
507         ContextHubTransaction<Void> transaction =
508                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_LOAD_NANOAPP);
509         IContextHubTransactionCallback callback = createTransactionCallback(transaction);
510 
511         try {
512             mService.loadNanoAppOnHub(hubInfo.getId(), callback, appBinary);
513         } catch (RemoteException e) {
514             throw e.rethrowFromSystemServer();
515         }
516 
517         return transaction;
518     }
519 
520     /**
521      * Unloads a nanoapp at the specified Context Hub.
522      *
523      * @param hubInfo the hub to unload the nanoapp from
524      * @param nanoAppId the app to unload
525      *
526      * @return the ContextHubTransaction of the request
527      *
528      * @throws NullPointerException if hubInfo is null
529      */
530     @RequiresPermission(anyOf = {
531             android.Manifest.permission.LOCATION_HARDWARE,
532             android.Manifest.permission.ACCESS_CONTEXT_HUB
533     })
534     @NonNull public ContextHubTransaction<Void> unloadNanoApp(
535             @NonNull ContextHubInfo hubInfo, long nanoAppId) {
536         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
537 
538         ContextHubTransaction<Void> transaction =
539                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_UNLOAD_NANOAPP);
540         IContextHubTransactionCallback callback = createTransactionCallback(transaction);
541 
542         try {
543             mService.unloadNanoAppFromHub(hubInfo.getId(), callback, nanoAppId);
544         } catch (RemoteException e) {
545             throw e.rethrowFromSystemServer();
546         }
547 
548         return transaction;
549     }
550 
551     /**
552      * Enables a nanoapp at the specified Context Hub.
553      *
554      * @param hubInfo the hub to enable the nanoapp on
555      * @param nanoAppId the app to enable
556      *
557      * @return the ContextHubTransaction of the request
558      *
559      * @throws NullPointerException if hubInfo is null
560      */
561     @RequiresPermission(anyOf = {
562             android.Manifest.permission.LOCATION_HARDWARE,
563             android.Manifest.permission.ACCESS_CONTEXT_HUB
564     })
565     @NonNull public ContextHubTransaction<Void> enableNanoApp(
566             @NonNull ContextHubInfo hubInfo, long nanoAppId) {
567         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
568 
569         ContextHubTransaction<Void> transaction =
570                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_ENABLE_NANOAPP);
571         IContextHubTransactionCallback callback = createTransactionCallback(transaction);
572 
573         try {
574             mService.enableNanoApp(hubInfo.getId(), callback, nanoAppId);
575         } catch (RemoteException e) {
576             throw e.rethrowFromSystemServer();
577         }
578 
579         return transaction;
580     }
581 
582     /**
583      * Disables a nanoapp at the specified Context Hub.
584      *
585      * @param hubInfo the hub to disable the nanoapp on
586      * @param nanoAppId the app to disable
587      *
588      * @return the ContextHubTransaction of the request
589      *
590      * @throws NullPointerException if hubInfo is null
591      */
592     @RequiresPermission(anyOf = {
593             android.Manifest.permission.LOCATION_HARDWARE,
594             android.Manifest.permission.ACCESS_CONTEXT_HUB
595     })
596     @NonNull public ContextHubTransaction<Void> disableNanoApp(
597             @NonNull ContextHubInfo hubInfo, long nanoAppId) {
598         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
599 
600         ContextHubTransaction<Void> transaction =
601                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_DISABLE_NANOAPP);
602         IContextHubTransactionCallback callback = createTransactionCallback(transaction);
603 
604         try {
605             mService.disableNanoApp(hubInfo.getId(), callback, nanoAppId);
606         } catch (RemoteException e) {
607             throw e.rethrowFromSystemServer();
608         }
609 
610         return transaction;
611     }
612 
613     /**
614      * Requests a query for nanoapps loaded at the specified Context Hub.
615      *
616      * @param hubInfo the hub to query a list of nanoapps from
617      *
618      * @return the ContextHubTransaction of the request
619      *
620      * @throws NullPointerException if hubInfo is null
621      */
622     @RequiresPermission(anyOf = {
623             android.Manifest.permission.LOCATION_HARDWARE,
624             android.Manifest.permission.ACCESS_CONTEXT_HUB
625     })
626     @NonNull public ContextHubTransaction<List<NanoAppState>> queryNanoApps(
627             @NonNull ContextHubInfo hubInfo) {
628         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
629 
630         ContextHubTransaction<List<NanoAppState>> transaction =
631                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_QUERY_NANOAPPS);
632         IContextHubTransactionCallback callback = createQueryCallback(transaction);
633 
634         try {
635             mService.queryNanoApps(hubInfo.getId(), callback);
636         } catch (RemoteException e) {
637             throw e.rethrowFromSystemServer();
638         }
639 
640         return transaction;
641     }
642 
643     /**
644      * Set a callback to receive messages from the context hub
645      *
646      * @param callback Callback object
647      *
648      * @see Callback
649      *
650      * @return int 0 on success, -1 otherwise
651      *
652      * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
653      *             or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to
654      *             register a {@link android.hardware.location.ContextHubClientCallback}.
655      */
656     @Deprecated
657     @SuppressLint("Doclava125")
658     public int registerCallback(@NonNull Callback callback) {
659         return registerCallback(callback, null);
660     }
661 
662     /**
663      * @deprecated Use {@link #registerCallback(Callback)} instead.
664      * @hide
665      */
666     @Deprecated
667     public int registerCallback(ICallback callback) {
668         if (mLocalCallback != null) {
669             Log.w(TAG, "Max number of local callbacks reached!");
670             return -1;
671         }
672         mLocalCallback = callback;
673         return 0;
674     }
675 
676     /**
677      * Set a callback to receive messages from the context hub
678      *
679      * @param callback Callback object
680      * @param handler Handler object, if null uses the Handler of the main Looper
681      *
682      * @see Callback
683      *
684      * @return int 0 on success, -1 otherwise
685      *
686      * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
687      *             or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to
688      *             register a {@link android.hardware.location.ContextHubClientCallback}.
689      */
690     @Deprecated
691     @SuppressLint("Doclava125")
692     public int registerCallback(Callback callback, Handler handler) {
693         synchronized(this) {
694             if (mCallback != null) {
695                 Log.w(TAG, "Max number of callbacks reached!");
696                 return -1;
697             }
698             mCallback = callback;
699             mCallbackHandler = (handler == null) ? new Handler(mMainLooper) : handler;
700         }
701         return 0;
702     }
703 
704     /**
705      * Creates an interface to the ContextHubClient to send down to the service.
706      *
707      * @param client the ContextHubClient object associated with this callback
708      * @param callback the callback to invoke at the client process
709      * @param executor the executor to invoke callbacks for this client
710      *
711      * @return the callback interface
712      */
713     private IContextHubClientCallback createClientCallback(
714             ContextHubClient client, ContextHubClientCallback callback, Executor executor) {
715         return new IContextHubClientCallback.Stub() {
716             @Override
717             public void onMessageFromNanoApp(NanoAppMessage message) {
718                 executor.execute(() -> callback.onMessageFromNanoApp(client, message));
719             }
720 
721             @Override
722             public void onHubReset() {
723                 executor.execute(() -> callback.onHubReset(client));
724             }
725 
726             @Override
727             public void onNanoAppAborted(long nanoAppId, int abortCode) {
728                 executor.execute(() -> callback.onNanoAppAborted(client, nanoAppId, abortCode));
729             }
730 
731             @Override
732             public void onNanoAppLoaded(long nanoAppId) {
733                 executor.execute(() -> callback.onNanoAppLoaded(client, nanoAppId));
734             }
735 
736             @Override
737             public void onNanoAppUnloaded(long nanoAppId) {
738                 executor.execute(() -> callback.onNanoAppUnloaded(client, nanoAppId));
739             }
740 
741             @Override
742             public void onNanoAppEnabled(long nanoAppId) {
743                 executor.execute(() -> callback.onNanoAppEnabled(client, nanoAppId));
744             }
745 
746             @Override
747             public void onNanoAppDisabled(long nanoAppId) {
748                 executor.execute(() -> callback.onNanoAppDisabled(client, nanoAppId));
749             }
750         };
751     }
752 
753     /**
754      * Creates and registers a client and its callback with the Context Hub Service.
755      *
756      * A client is registered with the Context Hub Service for a specified Context Hub. When the
757      * registration succeeds, the client can send messages to nanoapps through the returned
758      * {@link ContextHubClient} object, and receive notifications through the provided callback.
759      *
760      * @param hubInfo  the hub to attach this client to
761      * @param callback the notification callback to register
762      * @param executor the executor to invoke the callback
763      * @return the registered client object
764      *
765      * @throws IllegalArgumentException if hubInfo does not represent a valid hub
766      * @throws IllegalStateException    if there were too many registered clients at the service
767      * @throws NullPointerException     if callback, hubInfo, or executor is null
768      *
769      * @see ContextHubClientCallback
770      */
771     @RequiresPermission(anyOf = {
772             android.Manifest.permission.LOCATION_HARDWARE,
773             android.Manifest.permission.ACCESS_CONTEXT_HUB
774     })
775     @NonNull public ContextHubClient createClient(
776             @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
777             @NonNull @CallbackExecutor Executor executor) {
778         Objects.requireNonNull(callback, "Callback cannot be null");
779         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
780         Objects.requireNonNull(executor, "Executor cannot be null");
781 
782         ContextHubClient client = new ContextHubClient(hubInfo, false /* persistent */);
783         IContextHubClientCallback clientInterface = createClientCallback(
784                 client, callback, executor);
785 
786         IContextHubClient clientProxy;
787         try {
788             clientProxy = mService.createClient(hubInfo.getId(), clientInterface);
789         } catch (RemoteException e) {
790             throw e.rethrowFromSystemServer();
791         }
792 
793         client.setClientProxy(clientProxy);
794         return client;
795     }
796 
797     /**
798      * Equivalent to {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
799      * with the executor using the main thread's Looper.
800      *
801      * @param hubInfo  the hub to attach this client to
802      * @param callback the notification callback to register
803      * @return the registered client object
804      *
805      * @throws IllegalArgumentException if hubInfo does not represent a valid hub
806      * @throws IllegalStateException    if there were too many registered clients at the service
807      * @throws NullPointerException     if callback or hubInfo is null
808      *
809      * @see ContextHubClientCallback
810      */
811     @RequiresPermission(anyOf = {
812             android.Manifest.permission.LOCATION_HARDWARE,
813             android.Manifest.permission.ACCESS_CONTEXT_HUB
814     })
815     @NonNull public ContextHubClient createClient(
816             @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback) {
817         return createClient(hubInfo, callback, new HandlerExecutor(Handler.getMain()));
818     }
819 
820     /**
821      * Creates a ContextHubClient that will receive notifications based on Intent events.
822      *
823      * This method should be used instead of {@link #createClient(ContextHubInfo,
824      * ContextHubClientCallback)} or {@link #createClient(ContextHubInfo, ContextHubClientCallback,
825      * Executor)} if the caller wants to preserve the messaging endpoint of a ContextHubClient, even
826      * after a process exits. If the PendingIntent with the provided nanoapp has already been
827      * registered at the service, then the same ContextHubClient will be regenerated without
828      * creating a new client connection at the service. Note that the PendingIntent, nanoapp, and
829      * Context Hub must all match in identifying a previously registered ContextHubClient.
830      * If a client is regenerated, the host endpoint identifier attached to messages sent to the
831      * nanoapp remains consistent, even if the original process has exited.
832      *
833      * To avoid unintentionally spreading data from the Context Hub to external applications, it is
834      * strongly recommended that the PendingIntent supplied to this API is an explicit intent.
835      *
836      * If registered successfully, intents will be delivered regarding events or messages from the
837      * specified nanoapp from the attached Context Hub. The intent will have an extra
838      * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which
839      * describes the Context Hub the intent event was for. The intent will also have an extra
840      * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which
841      * will contain the type of the event. See {@link ContextHubManager.Event} for description of
842      * each event type, along with event-specific extra fields. The client can also use
843      * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event.
844      *
845      * Intent events will be delivered until {@link ContextHubClient.close()} is called. Note that
846      * the registration of this ContextHubClient at the Context Hub Service will be maintained until
847      * {@link ContextHubClient.close()} is called. If {@link PendingIntent.cancel()} is called
848      * on the provided PendingIntent, then the client will be automatically unregistered by the
849      * service.
850      *
851      * @param hubInfo       the hub to attach this client to
852      * @param pendingIntent the PendingIntent to register to the client
853      * @param nanoAppId     the ID of the nanoapp that Intent events will be generated for
854      * @return the registered client object
855      *
856      * @throws IllegalArgumentException if hubInfo does not represent a valid hub
857      * @throws IllegalStateException    if there were too many registered clients at the service
858      * @throws NullPointerException     if pendingIntent or hubInfo is null
859      */
860     @RequiresPermission(anyOf = {
861             android.Manifest.permission.LOCATION_HARDWARE,
862             android.Manifest.permission.ACCESS_CONTEXT_HUB
863     })
864     @NonNull public ContextHubClient createClient(
865             @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
866         Objects.requireNonNull(pendingIntent);
867         Objects.requireNonNull(hubInfo);
868 
869         ContextHubClient client = new ContextHubClient(hubInfo, true /* persistent */);
870 
871         IContextHubClient clientProxy;
872         try {
873             clientProxy = mService.createPendingIntentClient(
874                     hubInfo.getId(), pendingIntent, nanoAppId);
875         } catch (RemoteException e) {
876             throw e.rethrowFromSystemServer();
877         }
878 
879         client.setClientProxy(clientProxy);
880         return client;
881     }
882 
883     /**
884      * Unregister a callback for receive messages from the context hub.
885      *
886      * @see Callback
887      *
888      * @param callback method to deregister
889      *
890      * @return int 0 on success, -1 otherwise
891      *
892      * @deprecated Use {@link android.hardware.location.ContextHubClient#close()} to unregister
893      *             a {@link android.hardware.location.ContextHubClientCallback}.
894      */
895     @SuppressLint("Doclava125")
896     @Deprecated
897     public int unregisterCallback(@NonNull Callback callback) {
898       synchronized(this) {
899           if (callback != mCallback) {
900               Log.w(TAG, "Cannot recognize callback!");
901               return -1;
902           }
903 
904           mCallback = null;
905           mCallbackHandler = null;
906       }
907       return 0;
908     }
909 
910     /**
911      * @deprecated Use {@link #unregisterCallback(Callback)} instead.
912      * @hide
913      */
914     @Deprecated
915     public synchronized int unregisterCallback(ICallback callback) {
916         if (callback != mLocalCallback) {
917             Log.w(TAG, "Cannot recognize local callback!");
918             return -1;
919         }
920         mLocalCallback = null;
921         return 0;
922     }
923 
924     /**
925      * Invokes the ContextHubManager.Callback callback registered with the ContextHubManager.
926      *
927      * @param hubId The ID of the Context Hub the message came from
928      * @param nanoAppId The instance ID of the nanoapp the message came from
929      * @param message The message to provide the callback
930      */
931     private synchronized void invokeOnMessageReceiptCallback(
932             int hubId, int nanoAppId, ContextHubMessage message) {
933         if (mCallback != null) {
934             mCallback.onMessageReceipt(hubId, nanoAppId, message);
935         }
936     }
937 
938     private final IContextHubCallback.Stub mClientCallback = new IContextHubCallback.Stub() {
939         @Override
940         public void onMessageReceipt(
941                 final int hubId, final int nanoAppId, final ContextHubMessage message) {
942             synchronized (ContextHubManager.this) {
943                 if (mCallback != null) {
944                     mCallbackHandler.post(
945                             () -> invokeOnMessageReceiptCallback(hubId, nanoAppId, message));
946                 } else if (mLocalCallback != null) {
947                     // We always ensure that mCallback takes precedence, because mLocalCallback is
948                     // only for internal compatibility
949                     mLocalCallback.onMessageReceipt(hubId, nanoAppId, message);
950                 }
951             }
952         }
953     };
954 
955     /** @throws ServiceNotFoundException
956      * @hide */
957     public ContextHubManager(Context context, Looper mainLooper) throws ServiceNotFoundException {
958         mMainLooper = mainLooper;
959         mService = IContextHubService.Stub.asInterface(
960                 ServiceManager.getServiceOrThrow(Context.CONTEXTHUB_SERVICE));
961         try {
962             mService.registerCallback(mClientCallback);
963         } catch (RemoteException e) {
964             throw e.rethrowFromSystemServer();
965         }
966     }
967 }
968