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