• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static java.util.Objects.requireNonNull;
19 
20 import android.annotation.CallbackExecutor;
21 import android.annotation.FlaggedApi;
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresFeature;
26 import android.annotation.RequiresPermission;
27 import android.annotation.SuppressLint;
28 import android.annotation.SystemApi;
29 import android.annotation.SystemService;
30 import android.annotation.TestApi;
31 import android.app.ActivityThread;
32 import android.app.PendingIntent;
33 import android.chre.flags.Flags;
34 import android.content.Context;
35 import android.content.pm.PackageManager;
36 import android.hardware.contexthub.ErrorCode;
37 import android.hardware.contexthub.HubDiscoveryInfo;
38 import android.hardware.contexthub.HubEndpoint;
39 import android.hardware.contexthub.HubEndpointDiscoveryCallback;
40 import android.hardware.contexthub.HubEndpointInfo;
41 import android.hardware.contexthub.HubEndpointLifecycleCallback;
42 import android.hardware.contexthub.HubServiceInfo;
43 import android.hardware.contexthub.IContextHubEndpointDiscoveryCallback;
44 import android.os.Handler;
45 import android.os.HandlerExecutor;
46 import android.os.Looper;
47 import android.os.RemoteException;
48 import android.util.Log;
49 
50 import java.lang.annotation.Retention;
51 import java.lang.annotation.RetentionPolicy;
52 import java.util.ArrayList;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Objects;
56 import java.util.concurrent.ConcurrentHashMap;
57 import java.util.concurrent.Executor;
58 
59 /**
60  * A class that exposes the Context hubs on a device to applications.
61  *
62  * Please note that this class is not expected to be used by unbundled applications. Also, calling
63  * applications are expected to have the ACCESS_CONTEXT_HUB permission to use this class.
64  *
65  * @hide
66  */
67 @SystemApi
68 @SystemService(Context.CONTEXTHUB_SERVICE)
69 @RequiresFeature(PackageManager.FEATURE_CONTEXT_HUB)
70 public final class ContextHubManager {
71     private static final String TAG = "ContextHubManager";
72 
73     /**
74      * An extra containing one of the {@code AUTHORIZATION_*} constants such as
75      * {@link #AUTHORIZATION_GRANTED} describing the client's authorization state.
76      */
77     public static final String EXTRA_CLIENT_AUTHORIZATION_STATE =
78             "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE";
79 
80     /**
81      * An extra of type {@link ContextHubInfo} describing the source of the event.
82      */
83     public static final String EXTRA_CONTEXT_HUB_INFO =
84             "android.hardware.location.extra.CONTEXT_HUB_INFO";
85 
86     /**
87      * An extra of type {@link ContextHubManager.Event} describing the event type.
88      */
89     public static final String EXTRA_EVENT_TYPE = "android.hardware.location.extra.EVENT_TYPE";
90 
91     /**
92      * An extra of type long describing the ID of the nanoapp an event is for.
93      */
94     public static final String EXTRA_NANOAPP_ID = "android.hardware.location.extra.NANOAPP_ID";
95 
96     /**
97      * An extra of type int describing the nanoapp-specific abort code.
98      */
99     public static final String EXTRA_NANOAPP_ABORT_CODE =
100             "android.hardware.location.extra.NANOAPP_ABORT_CODE";
101 
102     /**
103      * An extra of type {@link NanoAppMessage} describing contents of a message from a nanoapp.
104      */
105     public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE";
106 
107     /**
108      * Constants describing if a {@link ContextHubClient} and a {@link NanoApp} are authorized to
109      * communicate.
110      *
111      * @hide
112      */
113     @Retention(RetentionPolicy.SOURCE)
114     @IntDef(prefix = { "AUTHORIZATION_" }, value = {
115         AUTHORIZATION_DENIED,
116         AUTHORIZATION_DENIED_GRACE_PERIOD,
117         AUTHORIZATION_GRANTED,
118     })
119     public @interface AuthorizationState { }
120 
121     /**
122      * Indicates that the {@link ContextHubClient} can no longer communicate with a nanoapp. If the
123      * {@link ContextHubClient} attempts to send messages to the nanoapp, it will continue to
124      * receive this authorization state if the connection is still closed.
125      */
126     public static final int AUTHORIZATION_DENIED = 0;
127 
128     /**
129      * Indicates the {@link ContextHubClient} will soon lose its authorization to communicate with a
130      * nanoapp. After receiving this state event, the {@link ContextHubClient} has one minute to
131      * perform any cleanup with the nanoapp such that the nanoapp is no longer performing work on
132      * behalf of the {@link ContextHubClient}.
133      */
134     public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1;
135 
136     /**
137      * The {@link ContextHubClient} is authorized to communicate with the nanoapp.
138      */
139     public static final int AUTHORIZATION_GRANTED = 2;
140 
141     /**
142      * Constants describing the type of events from a Context Hub, as defined in
143      * {@link ContextHubClientCallback}.
144      * {@hide}
145      */
146     @Retention(RetentionPolicy.SOURCE)
147     @IntDef(prefix = { "EVENT_" }, value = {
148         EVENT_NANOAPP_LOADED,
149         EVENT_NANOAPP_UNLOADED,
150         EVENT_NANOAPP_ENABLED,
151         EVENT_NANOAPP_DISABLED,
152         EVENT_NANOAPP_ABORTED,
153         EVENT_NANOAPP_MESSAGE,
154         EVENT_HUB_RESET,
155         EVENT_CLIENT_AUTHORIZATION,
156     })
157     public @interface Event { }
158 
159     /**
160      * An event describing that a nanoapp has been loaded. Contains the EXTRA_NANOAPP_ID extra.
161      */
162     public static final int EVENT_NANOAPP_LOADED = 0;
163 
164     /**
165      * An event describing that a nanoapp has been unloaded. Contains the EXTRA_NANOAPP_ID extra.
166      */
167     public static final int EVENT_NANOAPP_UNLOADED = 1;
168 
169     /**
170      * An event describing that a nanoapp has been enabled. Contains the EXTRA_NANOAPP_ID extra.
171      */
172     public static final int EVENT_NANOAPP_ENABLED = 2;
173 
174     /**
175      * An event describing that a nanoapp has been disabled. Contains the EXTRA_NANOAPP_ID extra.
176      */
177     public static final int EVENT_NANOAPP_DISABLED = 3;
178 
179     /**
180      * An event describing that a nanoapp has aborted. Contains the EXTRA_NANOAPP_ID and
181      * EXTRA_NANOAPP_ABORT_CODE extras.
182      */
183     public static final int EVENT_NANOAPP_ABORTED = 4;
184 
185     /**
186      * An event containing a message sent from a nanoapp. Contains the EXTRA_NANOAPP_ID and
187      * EXTRA_NANOAPP_MESSAGE extras.
188      */
189     public static final int EVENT_NANOAPP_MESSAGE = 5;
190 
191     /**
192      * An event describing that the Context Hub has reset.
193      */
194     public static final int EVENT_HUB_RESET = 6;
195 
196     /**
197      * An event describing a client authorization state change. See
198      * {@link ContextHubClientCallback#onClientAuthorizationChanged} for more details on when this
199      * event will be sent. Contains the EXTRA_NANOAPP_ID and EXTRA_CLIENT_AUTHORIZATION_STATE
200      * extras.
201      */
202     public static final int EVENT_CLIENT_AUTHORIZATION = 7;
203 
204     private final Looper mMainLooper;
205     private final IContextHubService mService;
206     private Callback mCallback;
207     private Handler mCallbackHandler;
208 
209     /** A map of endpoint discovery callbacks currently registered */
210     private Map<HubEndpointDiscoveryCallback, IContextHubEndpointDiscoveryCallback>
211             mDiscoveryCallbacks = new ConcurrentHashMap<>();
212 
213     /**
214      * @deprecated Use {@code mCallback} instead.
215      */
216     @Deprecated
217     private ICallback mLocalCallback;
218 
219     /**
220      * An interface to receive asynchronous communication from the context hub.
221      *
222      * @deprecated Use the more refined {@link android.hardware.location.ContextHubClientCallback}
223      *             instead for notification callbacks.
224      */
225     @Deprecated
226     public abstract static class Callback {
Callback()227         protected Callback() {}
228 
229         /**
230          * Callback function called on message receipt from context hub.
231          *
232          * @param hubHandle Handle (system-wide unique identifier) of the hub of the message.
233          * @param nanoAppHandle Handle (unique identifier) for app instance that sent the message.
234          * @param message The context hub message.
235          *
236          * @see ContextHubMessage
237          */
onMessageReceipt( int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message)238         public abstract void onMessageReceipt(
239                 int hubHandle,
240                 int nanoAppHandle,
241                 @NonNull ContextHubMessage message);
242     }
243 
244     /**
245      * @deprecated Use {@link Callback} instead.
246      * @hide
247      */
248     @Deprecated
249     public interface ICallback {
250         /**
251          * Callback function called on message receipt from context hub.
252          *
253          * @param hubHandle Handle (system-wide unique identifier) of the hub of the message.
254          * @param nanoAppHandle Handle (unique identifier) for app instance that sent the message.
255          * @param message The context hub message.
256          *
257          * @see ContextHubMessage
258          */
onMessageReceipt(int hubHandle, int nanoAppHandle, ContextHubMessage message)259         void onMessageReceipt(int hubHandle, int nanoAppHandle, ContextHubMessage message);
260     }
261 
262     /**
263      * Get a handle to all the context hubs in the system
264      *
265      * @return array of context hub handles
266      *
267      * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the
268      *             new APIs.
269      */
270     @Deprecated
271     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
getContextHubHandles()272     public int[] getContextHubHandles() {
273         if (Flags.removeOldContextHubApis()) {
274             return null;
275         }
276 
277         try {
278             return mService.getContextHubHandles();
279         } catch (RemoteException e) {
280             throw e.rethrowFromSystemServer();
281         }
282     }
283 
284     /**
285      * Get more information about a specific hub.
286      *
287      * @param hubHandle Handle (system-wide unique identifier) of a context hub.
288      * @return ContextHubInfo Information about the requested context hub.
289      *
290      * @see ContextHubInfo
291      *
292      * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the
293      *             new APIs.
294      */
295     @Deprecated
296     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
getContextHubInfo(int hubHandle)297     public ContextHubInfo getContextHubInfo(int hubHandle) {
298         if (Flags.removeOldContextHubApis()) {
299             return null;
300         }
301 
302         try {
303             return mService.getContextHubInfo(hubHandle);
304         } catch (RemoteException e) {
305             throw e.rethrowFromSystemServer();
306         }
307     }
308 
309     /**
310      * Load a nano app on a specified context hub.
311      *
312      * Note that loading is asynchronous.  When we return from this method,
313      * the nano app (probably) hasn't loaded yet.  Assuming a return of 0
314      * from this method, then the final success/failure for the load, along
315      * with the "handle" for the nanoapp, is all delivered in a byte
316      * string via a call to Callback.onMessageReceipt.
317      *
318      * TODO(b/30784270): Provide a better success/failure and "handle" delivery.
319      *
320      * @param hubHandle handle of context hub to load the app on.
321      * @param app the nanoApp to load on the hub
322      *
323      * @return 0 if the command for loading was sent to the context hub;
324      *         -1 otherwise
325      *
326      * @see NanoApp
327      *
328      * @deprecated Use {@link #loadNanoApp(ContextHubInfo, NanoAppBinary)} instead.
329      */
330     @Deprecated
331     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
loadNanoApp(int hubHandle, @NonNull NanoApp app)332     public int loadNanoApp(int hubHandle, @NonNull NanoApp app) {
333         if (Flags.removeOldContextHubApis()) {
334             return -1;
335         }
336 
337         try {
338             return mService.loadNanoApp(hubHandle, app);
339         } catch (RemoteException e) {
340             throw e.rethrowFromSystemServer();
341         }
342     }
343 
344     /**
345      * Unload a specified nanoApp
346      *
347      * Note that unloading is asynchronous.  When we return from this method,
348      * the nano app (probably) hasn't unloaded yet.  Assuming a return of 0
349      * from this method, then the final success/failure for the unload is
350      * delivered in a byte string via a call to Callback.onMessageReceipt.
351      *
352      * TODO(b/30784270): Provide a better success/failure delivery.
353      *
354      * @param nanoAppHandle handle of the nanoApp to unload
355      *
356      * @return 0 if the command for unloading was sent to the context hub;
357      *         -1 otherwise
358      *
359      * @deprecated Use {@link #unloadNanoApp(ContextHubInfo, long)} instead.
360      */
361     @Deprecated
362     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
unloadNanoApp(int nanoAppHandle)363     public int unloadNanoApp(int nanoAppHandle) {
364         if (Flags.removeOldContextHubApis()) {
365             return -1;
366         }
367 
368         try {
369             return mService.unloadNanoApp(nanoAppHandle);
370         } catch (RemoteException e) {
371             throw e.rethrowFromSystemServer();
372         }
373     }
374 
375     /**
376      * get information about the nano app instance
377      *
378      * NOTE: The returned NanoAppInstanceInfo does _not_ contain correct
379      * information for several fields, specifically:
380      * - getName()
381      * - getPublisher()
382      * - getNeededExecMemBytes()
383      * - getNeededReadMemBytes()
384      * - getNeededWriteMemBytes()
385      *
386      * For example, say you call loadNanoApp() with a NanoApp that has
387      * getName() returning "My Name".  Later, if you call getNanoAppInstanceInfo
388      * for that nanoapp, the returned NanoAppInstanceInfo's getName()
389      * method will claim "Preloaded app, unknown", even though you would
390      * have expected "My Name".  For now, as the user, you'll need to
391      * separately track the above fields if they are of interest to you.
392      *
393      * TODO(b/30943489): Have the returned NanoAppInstanceInfo contain the
394      *     correct information.
395      *
396      * @param nanoAppHandle handle of the nanoapp instance
397      * @return NanoAppInstanceInfo the NanoAppInstanceInfo of the nanoapp, or null if the nanoapp
398      *                             does not exist
399      *
400      * @see NanoAppInstanceInfo
401      *
402      * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub
403      *             for loaded nanoapps.
404      */
405     @Deprecated
406     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
getNanoAppInstanceInfo(int nanoAppHandle)407     @Nullable public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) {
408         if (Flags.removeOldContextHubApis()) {
409             return null;
410         }
411 
412         try {
413             return mService.getNanoAppInstanceInfo(nanoAppHandle);
414         } catch (RemoteException e) {
415             throw e.rethrowFromSystemServer();
416         }
417     }
418 
419     /**
420      * Find a specified nano app on the system
421      *
422      * @param hubHandle handle of hub to search for nano app
423      * @param filter filter specifying the search criteria for app
424      *
425      * @see NanoAppFilter
426      *
427      * @return int[] Array of handles to any found nano apps
428      *
429      * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub
430      *             for loaded nanoapps.
431      */
432     @Deprecated
433     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
findNanoAppOnHub(int hubHandle, @NonNull NanoAppFilter filter)434     @NonNull public int[] findNanoAppOnHub(int hubHandle, @NonNull NanoAppFilter filter) {
435         if (Flags.removeOldContextHubApis()) {
436             return null;
437         }
438 
439         try {
440             return mService.findNanoAppOnHub(hubHandle, filter);
441         } catch (RemoteException e) {
442             throw e.rethrowFromSystemServer();
443         }
444     }
445 
446     /**
447      * Send a message to a specific nano app instance on a context hub.
448      *
449      * Note that the return value of this method only speaks of success
450      * up to the point of sending this to the Context Hub.  It is not
451      * an assurance that the Context Hub successfully sent this message
452      * on to the nanoapp.  If assurance is desired, a protocol should be
453      * established between your code and the nanoapp, with the nanoapp
454      * sending a confirmation message (which will be reported via
455      * Callback.onMessageReceipt).
456      *
457      * @param hubHandle handle of the hub to send the message to
458      * @param nanoAppHandle  handle of the nano app to send to
459      * @param message Message to be sent
460      *
461      * @see ContextHubMessage
462      *
463      * @return int 0 on success, -1 otherwise
464      *
465      * @deprecated Use {@link android.hardware.location.ContextHubClient#sendMessageToNanoApp(
466      *             NanoAppMessage)} instead, after creating a
467      *             {@link android.hardware.location.ContextHubClient} with
468      *             {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
469      *             or {@link #createClient(ContextHubInfo, ContextHubClientCallback)}.
470      */
471     @Deprecated
472     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
sendMessage(int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message)473     public int sendMessage(int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message) {
474         if (Flags.removeOldContextHubApis()) {
475             return -1;
476         }
477 
478         try {
479             return mService.sendMessage(hubHandle, nanoAppHandle, message);
480         } catch (RemoteException e) {
481             throw e.rethrowFromSystemServer();
482         }
483     }
484 
485     /**
486      * Returns the list of ContextHubInfo objects describing the available Context Hubs.
487      *
488      * To find the list of hubs that include all Hubs (including both Context Hubs and Vendor Hubs),
489      * use the {@link #getHubs()} method instead.
490      *
491      * @return the list of ContextHubInfo objects
492      *
493      * @see ContextHubInfo
494      */
495     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
getContextHubs()496     @NonNull public List<ContextHubInfo> getContextHubs() {
497         try {
498             return mService.getContextHubs();
499         } catch (RemoteException e) {
500             throw e.rethrowFromSystemServer();
501         }
502     }
503 
504     /**
505      * Returns the list of HubInfo objects describing the available hubs (including Context Hubs and
506      * Vendor Hubs). This method is primarily used for debugging purposes as most clients care about
507      * endpoints and services more than hubs.
508      *
509      * @return the list of HubInfo objects
510      * @see HubInfo
511      * @hide
512      */
513     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
514     @NonNull
515     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
getHubs()516     public List<HubInfo> getHubs() {
517         try {
518             return mService.getHubs();
519         } catch (RemoteException e) {
520             throw e.rethrowFromSystemServer();
521         }
522     }
523 
524     /**
525      * Helper function to generate a stub for a query transaction callback.
526      *
527      * @param transaction the transaction to unblock when complete
528      * @return the callback
529      * @hide
530      */
createQueryCallback( ContextHubTransaction<List<NanoAppState>> transaction)531     private IContextHubTransactionCallback createQueryCallback(
532             ContextHubTransaction<List<NanoAppState>> transaction) {
533         return new IContextHubTransactionCallback.Stub() {
534             @Override
535             public void onQueryResponse(int result, List<NanoAppState> nanoappList) {
536                 transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
537                         result, nanoappList));
538             }
539 
540             @Override
541             public void onTransactionComplete(int result) {
542                 Log.e(TAG, "Received a non-query callback on a query request");
543                 transaction.setResponse(new ContextHubTransaction.Response<List<NanoAppState>>(
544                         ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null));
545             }
546         };
547     }
548 
549     /**
550      * Loads a nanoapp at the specified Context Hub.
551      *
552      * After the nanoapp binary is successfully loaded at the specified hub, the nanoapp will be in
553      * the enabled state.
554      *
555      * @param hubInfo the hub to load the nanoapp on
556      * @param appBinary The app binary to load
557      *
558      * @return the ContextHubTransaction of the request
559      *
560      * @throws NullPointerException if hubInfo or NanoAppBinary is null
561      *
562      * @see NanoAppBinary
563      */
564     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
565     @NonNull public ContextHubTransaction<Void> loadNanoApp(
566             @NonNull ContextHubInfo hubInfo, @NonNull NanoAppBinary appBinary) {
567         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
568         Objects.requireNonNull(appBinary, "NanoAppBinary cannot be null");
569 
570         ContextHubTransaction<Void> transaction =
571                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_LOAD_NANOAPP);
572         IContextHubTransactionCallback callback =
573                 ContextHubTransactionHelper.createTransactionCallback(transaction);
574 
575         try {
576             mService.loadNanoAppOnHub(hubInfo.getId(), callback, appBinary);
577         } catch (RemoteException e) {
578             throw e.rethrowFromSystemServer();
579         }
580 
581         return transaction;
582     }
583 
584     /**
585      * Unloads a nanoapp at the specified Context Hub.
586      *
587      * @param hubInfo the hub to unload the nanoapp from
588      * @param nanoAppId the app to unload
589      *
590      * @return the ContextHubTransaction of the request
591      *
592      * @throws NullPointerException if hubInfo is null
593      */
594     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
595     @NonNull public ContextHubTransaction<Void> unloadNanoApp(
596             @NonNull ContextHubInfo hubInfo, long nanoAppId) {
597         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
598 
599         ContextHubTransaction<Void> transaction =
600                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_UNLOAD_NANOAPP);
601         IContextHubTransactionCallback callback =
602                 ContextHubTransactionHelper.createTransactionCallback(transaction);
603 
604         try {
605             mService.unloadNanoAppFromHub(hubInfo.getId(), callback, nanoAppId);
606         } catch (RemoteException e) {
607             throw e.rethrowFromSystemServer();
608         }
609 
610         return transaction;
611     }
612 
613     /**
614      * Enables a nanoapp at the specified Context Hub.
615      *
616      * @param hubInfo the hub to enable the nanoapp on
617      * @param nanoAppId the app to enable
618      *
619      * @return the ContextHubTransaction of the request
620      *
621      * @throws NullPointerException if hubInfo is null
622      */
623     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
624     @NonNull public ContextHubTransaction<Void> enableNanoApp(
625             @NonNull ContextHubInfo hubInfo, long nanoAppId) {
626         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
627 
628         ContextHubTransaction<Void> transaction =
629                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_ENABLE_NANOAPP);
630         IContextHubTransactionCallback callback =
631                 ContextHubTransactionHelper.createTransactionCallback(transaction);
632 
633         try {
634             mService.enableNanoApp(hubInfo.getId(), callback, nanoAppId);
635         } catch (RemoteException e) {
636             throw e.rethrowFromSystemServer();
637         }
638 
639         return transaction;
640     }
641 
642     /**
643      * Disables a nanoapp at the specified Context Hub.
644      *
645      * @param hubInfo the hub to disable the nanoapp on
646      * @param nanoAppId the app to disable
647      *
648      * @return the ContextHubTransaction of the request
649      *
650      * @throws NullPointerException if hubInfo is null
651      */
652     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
653     @NonNull public ContextHubTransaction<Void> disableNanoApp(
654             @NonNull ContextHubInfo hubInfo, long nanoAppId) {
655         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
656 
657         ContextHubTransaction<Void> transaction =
658                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_DISABLE_NANOAPP);
659         IContextHubTransactionCallback callback =
660                 ContextHubTransactionHelper.createTransactionCallback(transaction);
661 
662         try {
663             mService.disableNanoApp(hubInfo.getId(), callback, nanoAppId);
664         } catch (RemoteException e) {
665             throw e.rethrowFromSystemServer();
666         }
667 
668         return transaction;
669     }
670 
671     /**
672      * Requests a query for nanoapps loaded at the specified Context Hub.
673      *
674      * @param hubInfo the hub to query a list of nanoapps from
675      *
676      * @return the ContextHubTransaction of the request
677      *
678      * @throws NullPointerException if hubInfo is null
679      */
680     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
681     @NonNull public ContextHubTransaction<List<NanoAppState>> queryNanoApps(
682             @NonNull ContextHubInfo hubInfo) {
683         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
684 
685         ContextHubTransaction<List<NanoAppState>> transaction =
686                 new ContextHubTransaction<>(ContextHubTransaction.TYPE_QUERY_NANOAPPS);
687         IContextHubTransactionCallback callback = createQueryCallback(transaction);
688 
689         try {
690             mService.queryNanoApps(hubInfo.getId(), callback);
691         } catch (RemoteException e) {
692             throw e.rethrowFromSystemServer();
693         }
694 
695         return transaction;
696     }
697 
698     /**
699      * Find a list of endpoints that matches a specific ID.
700      *
701      * @param endpointId Statically generated ID for an endpoint.
702      * @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery.
703      * @throws UnsupportedOperationException If the operation is not supported.
704      */
705     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
706     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
707     @NonNull
708     public List<HubDiscoveryInfo> findEndpoints(long endpointId) {
709         try {
710             List<HubEndpointInfo> endpointInfos = mService.findEndpoints(endpointId);
711             List<HubDiscoveryInfo> results = new ArrayList<>(endpointInfos.size());
712             // Wrap with result type
713             for (HubEndpointInfo endpointInfo : endpointInfos) {
714                 results.add(new HubDiscoveryInfo(endpointInfo));
715             }
716             return results;
717         } catch (RemoteException e) {
718             throw e.rethrowFromSystemServer();
719         }
720     }
721 
722     /**
723      * Find a list of endpoints that provides a specific service.
724      *
725      * <p>Service descriptor should uniquely identify the interface (scoped to type). Convention of
726      * the descriptor depend on interface type.
727      *
728      * <p>Examples:
729      *
730      * <ol>
731      *   <li>AOSP-defined AIDL: android.hardware.something.IFoo/default
732      *   <li>Vendor-defined AIDL: com.example.something.IBar/default
733      *   <li>Pigweed RPC with Protobuf: com.example.proto.ExampleService
734      * </ol>
735      *
736      * @param serviceDescriptor The service descriptor for a service provided by the hub. The value
737      *     cannot be null or empty.
738      * @return A list of {@link HubDiscoveryInfo} objects that represents the result of discovery.
739      * @throws IllegalArgumentException if the serviceDescriptor is empty/null.
740      * @throws UnsupportedOperationException If the operation is not supported.
741      */
742     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
743     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
744     @NonNull
745     public List<HubDiscoveryInfo> findEndpoints(@NonNull String serviceDescriptor) {
746         if (serviceDescriptor.isBlank()) {
747             throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor);
748         }
749         try {
750             List<HubEndpointInfo> endpointInfos =
751                     mService.findEndpointsWithService(serviceDescriptor);
752             List<HubDiscoveryInfo> results = new ArrayList<>(endpointInfos.size());
753             // Wrap with result type
754             for (HubEndpointInfo endpointInfo : endpointInfos) {
755                 for (HubServiceInfo serviceInfo : endpointInfo.getServiceInfoCollection()) {
756                     if (serviceInfo.getServiceDescriptor().equals(serviceDescriptor)) {
757                         results.add(new HubDiscoveryInfo(endpointInfo, serviceInfo));
758                     }
759                 }
760             }
761             return results;
762         } catch (RemoteException e) {
763             throw e.rethrowFromSystemServer();
764         }
765     }
766 
767     /**
768      * Creates an interface to invoke endpoint discovery callbacks to send down to the service.
769      *
770      * @param executor the executor to invoke callbacks for this client
771      * @param callback the callback to invoke at the client process
772      * @param serviceDescriptor an optional descriptor to match discovery list with
773      * @return the callback interface
774      */
775     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
776     private IContextHubEndpointDiscoveryCallback createDiscoveryCallback(
777             IContextHubService service,
778             Executor executor,
779             HubEndpointDiscoveryCallback callback,
780             @Nullable String serviceDescriptor) {
781         return new IContextHubEndpointDiscoveryCallback.Stub() {
782             @Override
783             public void onEndpointsStarted(HubEndpointInfo[] hubEndpointInfoList) {
784                 if (hubEndpointInfoList.length == 0) {
785                     Log.w(TAG, "onEndpointsStarted: received empty discovery list");
786                     invokeCallbackFinished(service);
787                     return;
788                 }
789                 executor.execute(
790                         () -> {
791                             List<HubDiscoveryInfo> discoveryList =
792                                     getMatchingEndpointDiscoveryList(
793                                             hubEndpointInfoList, serviceDescriptor);
794                             if (discoveryList.isEmpty()) {
795                                 Log.w(TAG, "onEndpointsStarted: no matching service descriptor");
796                             } else {
797                                 callback.onEndpointsStarted(discoveryList);
798                             }
799                             invokeCallbackFinished(service);
800                         });
801             }
802 
803             @Override
804             public void onEndpointsStopped(HubEndpointInfo[] hubEndpointInfoList, int reason) {
805                 if (hubEndpointInfoList.length == 0) {
806                     Log.w(TAG, "onEndpointsStopped: received empty discovery list");
807                     invokeCallbackFinished(service);
808                     return;
809                 }
810                 executor.execute(
811                         () -> {
812                             List<HubDiscoveryInfo> discoveryList =
813                                     getMatchingEndpointDiscoveryList(
814                                             hubEndpointInfoList, serviceDescriptor);
815                             if (discoveryList.isEmpty()) {
816                                 Log.w(TAG, "onEndpointsStopped: no matching service descriptor");
817                             } else {
818                                 callback.onEndpointsStopped(discoveryList, reason);
819                             }
820                             invokeCallbackFinished(service);
821                         });
822             }
823 
824             private void invokeCallbackFinished(IContextHubService service) {
825                 try {
826                     service.onDiscoveryCallbackFinished();
827                 } catch (RemoteException e) {
828                     e.rethrowFromSystemServer();
829                 }
830             }
831         };
832     }
833 
834     /**
835      * Generates a list of matching endpoint discovery info, given the list and an (optional)
836      * service descriptor. If service descriptor is null, all endpoints are added to the filtered
837      * output list.
838      *
839      * @param hubEndpointInfoList The hub endpoints to filter.
840      * @param serviceDescriptor The optional service descriptor to match, null if adding all
841      *     endpoints.
842      * @return The list of filtered HubDiscoveryInfo which matches the serviceDescriptor.
843      */
844     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
845     private List<HubDiscoveryInfo> getMatchingEndpointDiscoveryList(
846             HubEndpointInfo[] hubEndpointInfoList, @Nullable String serviceDescriptor) {
847         List<HubDiscoveryInfo> discoveryList = new ArrayList<>(hubEndpointInfoList.length);
848         for (HubEndpointInfo info : hubEndpointInfoList) {
849             if (serviceDescriptor != null) {
850                 for (HubServiceInfo sInfo : info.getServiceInfoCollection()) {
851                     if (sInfo.getServiceDescriptor().equals(serviceDescriptor)) {
852                         discoveryList.add(new HubDiscoveryInfo(info, sInfo));
853                     }
854                 }
855             } else {
856                 discoveryList.add(new HubDiscoveryInfo(info));
857             }
858         }
859         return discoveryList;
860     }
861 
862     /**
863      * Equivalent to {@link #registerEndpointDiscoveryCallback(Executor,
864      * HubEndpointDiscoveryCallback, long)} with the default executor in the main thread.
865      */
866     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
867     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
868     public void registerEndpointDiscoveryCallback(
869             @NonNull HubEndpointDiscoveryCallback callback, long endpointId) {
870         registerEndpointDiscoveryCallback(
871                 new HandlerExecutor(Handler.getMain()), callback, endpointId);
872     }
873 
874     /**
875      * Registers a callback to be notified when the hub endpoint with the corresponding endpoint ID
876      * has started or stopped.
877      *
878      * @param executor The executor to invoke the callback on.
879      * @param callback The callback to be invoked.
880      * @param endpointId The identifier of the hub endpoint.
881      * @throws UnsupportedOperationException If the operation is not supported.
882      */
883     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
884     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
885     public void registerEndpointDiscoveryCallback(
886             @NonNull Executor executor,
887             @NonNull HubEndpointDiscoveryCallback callback,
888             long endpointId) {
889         Objects.requireNonNull(executor, "executor cannot be null");
890         Objects.requireNonNull(callback, "callback cannot be null");
891         IContextHubEndpointDiscoveryCallback iCallback =
892                 createDiscoveryCallback(mService, executor, callback, null);
893         try {
894             mService.registerEndpointDiscoveryCallbackId(endpointId, iCallback);
895         } catch (RemoteException e) {
896             e.rethrowFromSystemServer();
897         }
898 
899         mDiscoveryCallbacks.put(callback, iCallback);
900     }
901 
902     /**
903      * Equivalent to {@link #registerEndpointDiscoveryCallback(Executor,
904      * HubEndpointDiscoveryCallback, String)} with the default executor in the main thread.
905      */
906     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
907     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
908     public void registerEndpointDiscoveryCallback(
909             @NonNull HubEndpointDiscoveryCallback callback, @NonNull String serviceDescriptor) {
910         registerEndpointDiscoveryCallback(
911                 new HandlerExecutor(Handler.getMain()), callback, serviceDescriptor);
912     }
913 
914     /**
915      * Registers a callback to be notified when the hub endpoint with the corresponding service
916      * descriptor has started or stopped.
917      *
918      * @param executor The executor to invoke the callback on.
919      * @param serviceDescriptor The service descriptor of the hub endpoint.
920      * @param callback The callback to be invoked.
921      * @throws IllegalArgumentException if the serviceDescriptor is empty.
922      * @throws UnsupportedOperationException If the operation is not supported.
923      */
924     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
925     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
926     public void registerEndpointDiscoveryCallback(
927             @NonNull Executor executor,
928             @NonNull HubEndpointDiscoveryCallback callback,
929             @NonNull String serviceDescriptor) {
930         Objects.requireNonNull(executor, "executor cannot be null");
931         Objects.requireNonNull(callback, "callback cannot be null");
932         Objects.requireNonNull(serviceDescriptor, "serviceDescriptor cannot be null");
933         if (serviceDescriptor.isBlank()) {
934             throw new IllegalArgumentException("Invalid service descriptor: " + serviceDescriptor);
935         }
936 
937         IContextHubEndpointDiscoveryCallback iCallback =
938                 createDiscoveryCallback(mService, executor, callback, serviceDescriptor);
939         try {
940             mService.registerEndpointDiscoveryCallbackDescriptor(serviceDescriptor, iCallback);
941         } catch (RemoteException e) {
942             e.rethrowFromSystemServer();
943         }
944 
945         mDiscoveryCallbacks.put(callback, iCallback);
946     }
947 
948     /**
949      * Unregisters a previously registered endpoint discovery callback.
950      *
951      * @param callback The callback previously registered.
952      * @throws IllegalArgumentException If the callback was not previously registered.
953      * @throws UnsupportedOperationException If the operation is not supported.
954      */
955     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
956     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
957     public void unregisterEndpointDiscoveryCallback(
958             @NonNull HubEndpointDiscoveryCallback callback) {
959         Objects.requireNonNull(callback, "callback cannot be null");
960         IContextHubEndpointDiscoveryCallback iCallback = mDiscoveryCallbacks.remove(callback);
961         if (iCallback == null) {
962             throw new IllegalArgumentException("Callback not previously registered");
963         }
964 
965         try {
966             mService.unregisterEndpointDiscoveryCallback(iCallback);
967         } catch (RemoteException e) {
968             e.rethrowFromSystemServer();
969         }
970     }
971 
972     /**
973      * Set a callback to receive messages from the context hub
974      *
975      * @param callback Callback object
976      *
977      * @see Callback
978      *
979      * @return int 0 on success, -1 otherwise
980      *
981      * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
982      *             or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to
983      *             register a {@link android.hardware.location.ContextHubClientCallback}.
984      */
985     @Deprecated
986     @SuppressLint("RequiresPermission")
987     public int registerCallback(@NonNull Callback callback) {
988         if (Flags.removeOldContextHubApis()) {
989             return -1;
990         }
991 
992         return registerCallback(callback, null);
993     }
994 
995     /**
996      * @deprecated Use {@link #registerCallback(Callback)} instead.
997      * @hide
998      */
999     @Deprecated
1000     public int registerCallback(ICallback callback) {
1001         if (Flags.removeOldContextHubApis()) {
1002             return -1;
1003         }
1004 
1005         if (mLocalCallback != null) {
1006             Log.w(TAG, "Max number of local callbacks reached!");
1007             return -1;
1008         }
1009         mLocalCallback = callback;
1010         return 0;
1011     }
1012 
1013     /**
1014      * Set a callback to receive messages from the context hub
1015      *
1016      * @param callback Callback object
1017      * @param handler Handler object, if null uses the Handler of the main Looper
1018      *
1019      * @see Callback
1020      *
1021      * @return int 0 on success, -1 otherwise
1022      *
1023      * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
1024      *             or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to
1025      *             register a {@link android.hardware.location.ContextHubClientCallback}.
1026      */
1027     @Deprecated
1028     @SuppressLint("RequiresPermission")
1029     public int registerCallback(Callback callback, Handler handler) {
1030         if (Flags.removeOldContextHubApis()) {
1031             return -1;
1032         }
1033 
1034         synchronized(this) {
1035             if (mCallback != null) {
1036                 Log.w(TAG, "Max number of callbacks reached!");
1037                 return -1;
1038             }
1039             mCallback = callback;
1040             mCallbackHandler = (handler == null) ? new Handler(mMainLooper) : handler;
1041         }
1042         return 0;
1043     }
1044 
1045     /**
1046      * Creates an interface to the ContextHubClient to send down to the service.
1047      *
1048      * @param client the ContextHubClient object associated with this callback
1049      * @param callback the callback to invoke at the client process
1050      * @param executor the executor to invoke callbacks for this client
1051      *
1052      * @return the callback interface
1053      */
1054     private IContextHubClientCallback createClientCallback(
1055             ContextHubClient client, ContextHubClientCallback callback, Executor executor) {
1056         return new IContextHubClientCallback.Stub() {
1057             @Override
1058             public void onMessageFromNanoApp(NanoAppMessage message) {
1059                 executor.execute(
1060                         () -> {
1061                             callback.onMessageFromNanoApp(client, message);
1062                             if (message.isReliable()) {
1063                                 client.reliableMessageCallbackFinished(
1064                                         message.getMessageSequenceNumber(), ErrorCode.OK);
1065                             } else {
1066                                 client.callbackFinished();
1067                             }
1068                         });
1069             }
1070 
1071             @Override
1072             public void onHubReset() {
1073                 executor.execute(
1074                         () -> {
1075                             callback.onHubReset(client);
1076                             client.callbackFinished();
1077                         });
1078             }
1079 
1080             @Override
1081             public void onNanoAppAborted(long nanoAppId, int abortCode) {
1082                 executor.execute(
1083                         () -> {
1084                             callback.onNanoAppAborted(client, nanoAppId, abortCode);
1085                             client.callbackFinished();
1086                         });
1087             }
1088 
1089             @Override
1090             public void onNanoAppLoaded(long nanoAppId) {
1091                 executor.execute(
1092                         () -> {
1093                             callback.onNanoAppLoaded(client, nanoAppId);
1094                             client.callbackFinished();
1095                         });
1096             }
1097 
1098             @Override
1099             public void onNanoAppUnloaded(long nanoAppId) {
1100                 executor.execute(
1101                         () -> {
1102                             callback.onNanoAppUnloaded(client, nanoAppId);
1103                             client.callbackFinished();
1104                         });
1105             }
1106 
1107             @Override
1108             public void onNanoAppEnabled(long nanoAppId) {
1109                 executor.execute(
1110                         () -> {
1111                             callback.onNanoAppEnabled(client, nanoAppId);
1112                             client.callbackFinished();
1113                         });
1114             }
1115 
1116             @Override
1117             public void onNanoAppDisabled(long nanoAppId) {
1118                 executor.execute(
1119                         () -> {
1120                             callback.onNanoAppDisabled(client, nanoAppId);
1121                             client.callbackFinished();
1122                         });
1123             }
1124 
1125             @Override
1126             public void onClientAuthorizationChanged(
1127                     long nanoAppId, @ContextHubManager.AuthorizationState int authorization) {
1128                 executor.execute(
1129                         () -> {
1130                             callback.onClientAuthorizationChanged(client, nanoAppId, authorization);
1131                             client.callbackFinished();
1132                         });
1133             }
1134         };
1135     }
1136 
1137     /**
1138      * Creates and registers a client and its callback with the Context Hub Service.
1139      *
1140      * A client is registered with the Context Hub Service for a specified Context Hub. When the
1141      * registration succeeds, the client can send messages to nanoapps through the returned
1142      * {@link ContextHubClient} object, and receive notifications through the provided callback.
1143      *
1144      * @param context  the context of the application
1145      * @param hubInfo  the hub to attach this client to
1146      * @param executor the executor to invoke the callback
1147      * @param callback the notification callback to register
1148      * @return the registered client object
1149      *
1150      * @throws IllegalArgumentException if hubInfo does not represent a valid hub
1151      * @throws IllegalStateException    if there were too many registered clients at the service
1152      * @throws NullPointerException     if callback, hubInfo, or executor is null
1153      *
1154      * @see ContextHubClientCallback
1155      */
1156     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1157     @NonNull public ContextHubClient createClient(
1158             @Nullable Context context, @NonNull ContextHubInfo hubInfo,
1159             @NonNull @CallbackExecutor Executor executor,
1160             @NonNull ContextHubClientCallback callback) {
1161         Objects.requireNonNull(callback, "Callback cannot be null");
1162         Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null");
1163         Objects.requireNonNull(executor, "Executor cannot be null");
1164 
1165         ContextHubClient client = new ContextHubClient(hubInfo, false /* persistent */);
1166         IContextHubClientCallback clientInterface = createClientCallback(
1167                 client, callback, executor);
1168 
1169         String attributionTag = null;
1170         if (context != null) {
1171             attributionTag = context.getAttributionTag();
1172         }
1173 
1174         // Workaround for old APIs not providing a context
1175         String packageName;
1176         if (context != null) {
1177             packageName = context.getPackageName();
1178         } else {
1179             packageName = ActivityThread.currentPackageName();
1180         }
1181 
1182         IContextHubClient clientProxy;
1183         try {
1184             clientProxy = mService.createClient(
1185                     hubInfo.getId(), clientInterface, attributionTag, packageName);
1186         } catch (RemoteException e) {
1187             throw e.rethrowFromSystemServer();
1188         }
1189 
1190         client.setClientProxy(clientProxy);
1191         return client;
1192     }
1193 
1194 
1195     /**
1196      * Equivalent to
1197      * {@link  #createClient(Context, ContextHubInfo, Executor, ContextHubClientCallback)}
1198      * with the {@link Context} being set to null.
1199      */
1200     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1201     @NonNull public ContextHubClient createClient(
1202             @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback,
1203             @NonNull @CallbackExecutor Executor executor) {
1204         return createClient(null /* context */, hubInfo, executor, callback);
1205     }
1206 
1207     /**
1208      * Equivalent to {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)}
1209      * with the executor using the main thread's Looper.
1210      */
1211     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1212     @NonNull public ContextHubClient createClient(
1213             @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback) {
1214         return createClient(null /* context */, hubInfo, new HandlerExecutor(Handler.getMain()),
1215                             callback);
1216     }
1217 
1218     /**
1219      * Creates a ContextHubClient that will receive notifications based on Intent events.
1220      *
1221      * This method should be used instead of {@link #createClient(ContextHubInfo,
1222      * ContextHubClientCallback)} or {@link #createClient(ContextHubInfo, ContextHubClientCallback,
1223      * Executor)} if the caller wants to preserve the messaging endpoint of a ContextHubClient, even
1224      * after a process exits. If the PendingIntent with the provided nanoapp has already been
1225      * registered at the service, then the same ContextHubClient will be regenerated without
1226      * creating a new client connection at the service. Note that the PendingIntent, nanoapp, and
1227      * Context Hub must all match in identifying a previously registered ContextHubClient.
1228      * If a client is regenerated, the host endpoint identifier attached to messages sent to the
1229      * nanoapp remains consistent, even if the original process has exited.
1230      *
1231      * To avoid unintentionally spreading data from the Context Hub to external applications, it is
1232      * strongly recommended that the PendingIntent supplied to this API is an explicit intent.
1233      *
1234      * If registered successfully, intents will be delivered regarding events or messages from the
1235      * specified nanoapp from the attached Context Hub. The intent will have an extra
1236      * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which
1237      * describes the Context Hub the intent event was for. The intent will also have an extra
1238      * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which
1239      * will contain the type of the event. See {@link ContextHubManager.Event} for description of
1240      * each event type, along with event-specific extra fields. The client can also use
1241      * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event.
1242      *
1243      * Intent events will be delivered until {@link ContextHubClient.close()} is called. Note that
1244      * the registration of this ContextHubClient at the Context Hub Service will be maintained until
1245      * {@link ContextHubClient.close()} is called. If {@link PendingIntent.cancel()} is called
1246      * on the provided PendingIntent, then the client will be automatically unregistered by the
1247      * service.
1248      *
1249      * Note that the {@link PendingIntent} supplied to this API must be mutable for Intent
1250      * notifications to work.
1251      *
1252      * @param context       the context of the application. If a PendingIntent client is recreated,
1253      * the latest state in the context will be used and old state will be discarded
1254      * @param hubInfo       the hub to attach this client to
1255      * @param pendingIntent the PendingIntent to register to the client
1256      * @param nanoAppId     the ID of the nanoapp that Intent events will be generated for
1257      * @return the registered client object
1258      *
1259      * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or an immutable
1260      *                                  PendingIntent was supplied
1261      * @throws IllegalStateException    if there were too many registered clients at the service
1262      * @throws NullPointerException     if pendingIntent or hubInfo is null
1263      */
1264     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1265     @NonNull public ContextHubClient createClient(
1266             @Nullable Context context, @NonNull ContextHubInfo hubInfo,
1267             @NonNull PendingIntent pendingIntent, long nanoAppId) {
1268         Objects.requireNonNull(pendingIntent);
1269         Objects.requireNonNull(hubInfo);
1270         if (pendingIntent.isImmutable()) {
1271             throw new IllegalArgumentException("PendingIntent must be mutable");
1272         }
1273 
1274         ContextHubClient client = new ContextHubClient(hubInfo, true /* persistent */);
1275 
1276         String attributionTag = null;
1277         if (context != null) {
1278             attributionTag = context.getAttributionTag();
1279         }
1280 
1281         IContextHubClient clientProxy;
1282         try {
1283             clientProxy = mService.createPendingIntentClient(
1284                     hubInfo.getId(), pendingIntent, nanoAppId, attributionTag);
1285         } catch (RemoteException e) {
1286             throw e.rethrowFromSystemServer();
1287         }
1288 
1289         client.setClientProxy(clientProxy);
1290         return client;
1291     }
1292 
1293     /**
1294      * Equivalent to {@link #createClient(ContextHubInfo, PendingIntent, long, String)}
1295      * with {@link Context} being set to null.
1296      */
1297     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1298     @NonNull public ContextHubClient createClient(
1299             @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) {
1300         return createClient(null /* context */, hubInfo, pendingIntent, nanoAppId);
1301     }
1302 
1303     /**
1304      * Registers an endpoint and its callback with the Context Hub Service.
1305      *
1306      * <p>An endpoint is registered with the Context Hub Service and published to the HAL. When the
1307      * registration succeeds, the endpoint can receive notifications through the provided callback.
1308      *
1309      * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}
1310      * @throws IllegalStateException if the registration failed, for example if too many endpoints
1311      *     are registered at the service
1312      * @throws UnsupportedOperationException if endpoint registration is not supported
1313      */
1314     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1315     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
1316     public void registerEndpoint(@NonNull HubEndpoint hubEndpoint) {
1317         hubEndpoint.register(mService);
1318     }
1319 
1320     /**
1321      * Use a registered endpoint to connect to another endpoint (destination) without specifying a
1322      * service.
1323      *
1324      * <p>Context Hub Service will create the endpoint session and notify the registered endpoint.
1325      * The registered endpoint will receive callbacks on its {@link HubEndpointLifecycleCallback}
1326      * object regarding the lifecycle events of the session.
1327      *
1328      * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link
1329      *     ContextHubManager#registerEndpoint(HubEndpoint)}.
1330      * @param destination {@link HubEndpointInfo} object that represents an endpoint from previous
1331      *     endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}).
1332      */
1333     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1334     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
1335     public void openSession(
1336             @NonNull HubEndpoint hubEndpoint, @NonNull HubEndpointInfo destination) {
1337         hubEndpoint.openSession(destination, null);
1338     }
1339 
1340     /**
1341      * Use a registered endpoint to connect to another endpoint (destination) for a service
1342      * described by a {@link HubServiceInfo} object.
1343      *
1344      * <p>Context Hub Service will create the endpoint session and notify the registered endpoint.
1345      * The registered endpoint will receive callbacks on its {@link HubEndpointLifecycleCallback}
1346      * object regarding the lifecycle events of the session.
1347      *
1348      * @param hubEndpoint {@link HubEndpoint} object previously registered via {@link
1349      *     ContextHubManager#registerEndpoint(HubEndpoint)}.
1350      * @param destination {@link HubEndpointInfo} object that represents an endpoint from previous
1351      *     endpoint discovery results (e.g. from {@link ContextHubManager#findEndpoints(long)}).
1352      * @param serviceDescriptor A string that describes the service associated with this session.
1353      *     The information will be sent to the destination as part of open request.
1354      * @throws IllegalStateException if hubEndpoint was not successfully registered, or if there is
1355      *     insufficient capacity for creating a session.
1356      */
1357     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1358     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
1359     public void openSession(
1360             @NonNull HubEndpoint hubEndpoint,
1361             @NonNull HubEndpointInfo destination,
1362             @NonNull String serviceDescriptor) {
1363         hubEndpoint.openSession(destination, serviceDescriptor);
1364     }
1365 
1366     /**
1367      * Unregisters an endpoint and its callback with the Context Hub Service.
1368      *
1369      * <p>An endpoint is unregistered from the HAL. The endpoint object will no longer receive
1370      * notification through the provided callback.
1371      *
1372      * @param hubEndpoint {@link HubEndpoint} object created by {@link HubEndpoint.Builder}. This
1373      *     should match a previously registered object via {@link
1374      *     ContextHubManager#registerEndpoint(HubEndpoint)}.
1375      */
1376     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1377     @FlaggedApi(Flags.FLAG_OFFLOAD_API)
1378     public void unregisterEndpoint(@NonNull HubEndpoint hubEndpoint) {
1379         hubEndpoint.unregister();
1380     }
1381 
1382     /**
1383      * Queries for the list of preloaded nanoapp IDs on the system.
1384      *
1385      * @param hubInfo The Context Hub to query a list of nanoapp IDs from.
1386      *
1387      * @return The list of 64-bit IDs of the preloaded nanoapps.
1388      *
1389      * @throws NullPointerException if hubInfo is null
1390      * @hide
1391      */
1392     @TestApi
1393     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1394     @NonNull public long[] getPreloadedNanoAppIds(@NonNull ContextHubInfo hubInfo) {
1395         Objects.requireNonNull(hubInfo, "hubInfo cannot be null");
1396 
1397         long[] nanoappIds = null;
1398         try {
1399             nanoappIds = mService.getPreloadedNanoAppIds(hubInfo);
1400         } catch (RemoteException e) {
1401             throw e.rethrowFromSystemServer();
1402         }
1403 
1404         if (nanoappIds == null) {
1405             nanoappIds = new long[0];
1406         }
1407         return nanoappIds;
1408     }
1409 
1410     /**
1411      * Puts the Context Hub in test mode.
1412      *
1413      * The purpose of this API is to make testing CHRE/Context Hub more
1414      * predictable and robust. This temporarily unloads all
1415      * nanoapps.
1416      *
1417      * Note that this API must not cause CHRE/Context Hub to behave differently
1418      * in test compared to production.
1419      *
1420      * @return true if the enable test mode operation succeeded.
1421      * @hide
1422      */
1423     @TestApi
1424     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1425     @NonNull public boolean enableTestMode() {
1426         try {
1427             return mService.setTestMode(true);
1428         } catch (RemoteException e) {
1429             throw e.rethrowFromSystemServer();
1430         }
1431     }
1432 
1433     /**
1434      * Puts the Context Hub out of test mode.
1435      *
1436      * This API will undo any previously made enableTestMode() calls.
1437      * After this API is called, it should restore the state of the system
1438      * to the normal/production mode before any enableTestMode() call was
1439      * made. If the enableTestMode() call unloaded any nanoapps
1440      * to enter test mode, it should reload those nanoapps in this API call.
1441      *
1442      * @return true if the disable operation succeeded.
1443      * @hide
1444      */
1445     @TestApi
1446     @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB)
1447     @NonNull public boolean disableTestMode() {
1448         try {
1449             return mService.setTestMode(false);
1450         } catch (RemoteException e) {
1451             throw e.rethrowFromSystemServer();
1452         }
1453     }
1454 
1455     /**
1456      * Unregister a callback for receive messages from the context hub.
1457      *
1458      * @see Callback
1459      *
1460      * @param callback method to deregister
1461      *
1462      * @return int 0 on success, -1 otherwise
1463      *
1464      * @deprecated Use {@link android.hardware.location.ContextHubClient#close()} to unregister
1465      *             a {@link android.hardware.location.ContextHubClientCallback}.
1466      */
1467     @SuppressLint("RequiresPermission")
1468     @Deprecated
1469     public int unregisterCallback(@NonNull Callback callback) {
1470         if (Flags.removeOldContextHubApis()) {
1471             return -1;
1472         }
1473 
1474         synchronized (this) {
1475             if (callback != mCallback) {
1476                 Log.w(TAG, "Cannot recognize callback!");
1477                 return -1;
1478             }
1479 
1480             mCallback = null;
1481             mCallbackHandler = null;
1482         }
1483         return 0;
1484     }
1485 
1486     /**
1487      * @deprecated Use {@link #unregisterCallback(Callback)} instead.
1488      * @hide
1489      */
1490     @Deprecated
1491     public synchronized int unregisterCallback(ICallback callback) {
1492         if (Flags.removeOldContextHubApis()) {
1493             return -1;
1494         }
1495 
1496         if (callback != mLocalCallback) {
1497             Log.w(TAG, "Cannot recognize local callback!");
1498             return -1;
1499         }
1500         mLocalCallback = null;
1501         return 0;
1502     }
1503 
1504     /**
1505      * Invokes the ContextHubManager.Callback callback registered with the ContextHubManager.
1506      *
1507      * @param hubId The ID of the Context Hub the message came from
1508      * @param nanoAppId The instance ID of the nanoapp the message came from
1509      * @param message The message to provide the callback
1510      */
1511     private synchronized void invokeOnMessageReceiptCallback(
1512             int hubId, int nanoAppId, ContextHubMessage message) {
1513         if (mCallback != null) {
1514             mCallback.onMessageReceipt(hubId, nanoAppId, message);
1515         }
1516     }
1517 
1518     private final IContextHubCallback.Stub mClientCallback = new IContextHubCallback.Stub() {
1519         @Override
1520         public void onMessageReceipt(
1521                 final int hubId, final int nanoAppId, final ContextHubMessage message) {
1522             synchronized (ContextHubManager.this) {
1523                 if (mCallback != null) {
1524                     mCallbackHandler.post(
1525                             () -> invokeOnMessageReceiptCallback(hubId, nanoAppId, message));
1526                 } else if (mLocalCallback != null) {
1527                     // We always ensure that mCallback takes precedence, because mLocalCallback is
1528                     // only for internal compatibility
1529                     mLocalCallback.onMessageReceipt(hubId, nanoAppId, message);
1530                 }
1531             }
1532         }
1533     };
1534 
1535     /** @hide */
1536     public ContextHubManager(@NonNull IContextHubService service, @NonNull Looper mainLooper) {
1537         requireNonNull(service, "service cannot be null");
1538         requireNonNull(mainLooper, "mainLooper cannot be null");
1539         mService = service;
1540         mMainLooper = mainLooper;
1541 
1542         try {
1543             mService.registerCallback(mClientCallback);
1544         } catch (RemoteException e) {
1545             throw e.rethrowFromSystemServer();
1546         }
1547     }
1548 }
1549