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