/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.hardware.location; import static java.util.Objects.requireNonNull; import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.ActivityThread; import android.app.PendingIntent; import android.chre.flags.Flags; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.contexthub.ErrorCode; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Looper; import android.os.RemoteException; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; /** * A class that exposes the Context hubs on a device to applications. * * Please note that this class is not expected to be used by unbundled applications. Also, calling * applications are expected to have the ACCESS_CONTEXT_HUB permission to use this class. * * @hide */ @SystemApi @SystemService(Context.CONTEXTHUB_SERVICE) @RequiresFeature(PackageManager.FEATURE_CONTEXT_HUB) public final class ContextHubManager { private static final String TAG = "ContextHubManager"; /** * An extra containing one of the {@code AUTHORIZATION_*} constants such as * {@link #AUTHORIZATION_GRANTED} describing the client's authorization state. */ public static final String EXTRA_CLIENT_AUTHORIZATION_STATE = "android.hardware.location.extra.CLIENT_AUTHORIZATION_STATE"; /** * An extra of type {@link ContextHubInfo} describing the source of the event. */ public static final String EXTRA_CONTEXT_HUB_INFO = "android.hardware.location.extra.CONTEXT_HUB_INFO"; /** * An extra of type {@link ContextHubManager.Event} describing the event type. */ public static final String EXTRA_EVENT_TYPE = "android.hardware.location.extra.EVENT_TYPE"; /** * An extra of type long describing the ID of the nanoapp an event is for. */ public static final String EXTRA_NANOAPP_ID = "android.hardware.location.extra.NANOAPP_ID"; /** * An extra of type int describing the nanoapp-specific abort code. */ public static final String EXTRA_NANOAPP_ABORT_CODE = "android.hardware.location.extra.NANOAPP_ABORT_CODE"; /** * An extra of type {@link NanoAppMessage} describing contents of a message from a nanoapp. */ public static final String EXTRA_MESSAGE = "android.hardware.location.extra.MESSAGE"; /** * Constants describing if a {@link ContextHubClient} and a {@link NanoApp} are authorized to * communicate. * * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "AUTHORIZATION_" }, value = { AUTHORIZATION_DENIED, AUTHORIZATION_DENIED_GRACE_PERIOD, AUTHORIZATION_GRANTED, }) public @interface AuthorizationState { } /** * Indicates that the {@link ContextHubClient} can no longer communicate with a nanoapp. If the * {@link ContextHubClient} attempts to send messages to the nanoapp, it will continue to * receive this authorization state if the connection is still closed. */ public static final int AUTHORIZATION_DENIED = 0; /** * Indicates the {@link ContextHubClient} will soon lose its authorization to communicate with a * nanoapp. After receiving this state event, the {@link ContextHubClient} has one minute to * perform any cleanup with the nanoapp such that the nanoapp is no longer performing work on * behalf of the {@link ContextHubClient}. */ public static final int AUTHORIZATION_DENIED_GRACE_PERIOD = 1; /** * The {@link ContextHubClient} is authorized to communicate with the nanoapp. */ public static final int AUTHORIZATION_GRANTED = 2; /** * Constants describing the type of events from a Context Hub, as defined in * {@link ContextHubClientCallback}. * {@hide} */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "EVENT_" }, value = { EVENT_NANOAPP_LOADED, EVENT_NANOAPP_UNLOADED, EVENT_NANOAPP_ENABLED, EVENT_NANOAPP_DISABLED, EVENT_NANOAPP_ABORTED, EVENT_NANOAPP_MESSAGE, EVENT_HUB_RESET, EVENT_CLIENT_AUTHORIZATION, }) public @interface Event { } /** * An event describing that a nanoapp has been loaded. Contains the EXTRA_NANOAPP_ID extra. */ public static final int EVENT_NANOAPP_LOADED = 0; /** * An event describing that a nanoapp has been unloaded. Contains the EXTRA_NANOAPP_ID extra. */ public static final int EVENT_NANOAPP_UNLOADED = 1; /** * An event describing that a nanoapp has been enabled. Contains the EXTRA_NANOAPP_ID extra. */ public static final int EVENT_NANOAPP_ENABLED = 2; /** * An event describing that a nanoapp has been disabled. Contains the EXTRA_NANOAPP_ID extra. */ public static final int EVENT_NANOAPP_DISABLED = 3; /** * An event describing that a nanoapp has aborted. Contains the EXTRA_NANOAPP_ID and * EXTRA_NANOAPP_ABORT_CODE extras. */ public static final int EVENT_NANOAPP_ABORTED = 4; /** * An event containing a message sent from a nanoapp. Contains the EXTRA_NANOAPP_ID and * EXTRA_NANOAPP_MESSAGE extras. */ public static final int EVENT_NANOAPP_MESSAGE = 5; /** * An event describing that the Context Hub has reset. */ public static final int EVENT_HUB_RESET = 6; /** * An event describing a client authorization state change. See * {@link ContextHubClientCallback#onClientAuthorizationChanged} for more details on when this * event will be sent. Contains the EXTRA_NANOAPP_ID and EXTRA_CLIENT_AUTHORIZATION_STATE * extras. */ public static final int EVENT_CLIENT_AUTHORIZATION = 7; private final Looper mMainLooper; private final IContextHubService mService; private Callback mCallback; private Handler mCallbackHandler; /** * @deprecated Use {@code mCallback} instead. */ @Deprecated private ICallback mLocalCallback; /** * An interface to receive asynchronous communication from the context hub. * * @deprecated Use the more refined {@link android.hardware.location.ContextHubClientCallback} * instead for notification callbacks. */ @Deprecated public abstract static class Callback { protected Callback() {} /** * Callback function called on message receipt from context hub. * * @param hubHandle Handle (system-wide unique identifier) of the hub of the message. * @param nanoAppHandle Handle (unique identifier) for app instance that sent the message. * @param message The context hub message. * * @see ContextHubMessage */ public abstract void onMessageReceipt( int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message); } /** * @deprecated Use {@link Callback} instead. * @hide */ @Deprecated public interface ICallback { /** * Callback function called on message receipt from context hub. * * @param hubHandle Handle (system-wide unique identifier) of the hub of the message. * @param nanoAppHandle Handle (unique identifier) for app instance that sent the message. * @param message The context hub message. * * @see ContextHubMessage */ void onMessageReceipt(int hubHandle, int nanoAppHandle, ContextHubMessage message); } /** * Get a handle to all the context hubs in the system * * @return array of context hub handles * * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the * new APIs. */ @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int[] getContextHubHandles() { try { return mService.getContextHubHandles(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Get more information about a specific hub. * * @param hubHandle Handle (system-wide unique identifier) of a context hub. * @return ContextHubInfo Information about the requested context hub. * * @see ContextHubInfo * * @deprecated Use {@link #getContextHubs()} instead. The use of handles are deprecated in the * new APIs. */ @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public ContextHubInfo getContextHubInfo(int hubHandle) { try { return mService.getContextHubInfo(hubHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Load a nano app on a specified context hub. * * Note that loading is asynchronous. When we return from this method, * the nano app (probably) hasn't loaded yet. Assuming a return of 0 * from this method, then the final success/failure for the load, along * with the "handle" for the nanoapp, is all delivered in a byte * string via a call to Callback.onMessageReceipt. * * TODO(b/30784270): Provide a better success/failure and "handle" delivery. * * @param hubHandle handle of context hub to load the app on. * @param app the nanoApp to load on the hub * * @return 0 if the command for loading was sent to the context hub; * -1 otherwise * * @see NanoApp * * @deprecated Use {@link #loadNanoApp(ContextHubInfo, NanoAppBinary)} instead. */ @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int loadNanoApp(int hubHandle, @NonNull NanoApp app) { try { return mService.loadNanoApp(hubHandle, app); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Unload a specified nanoApp * * Note that unloading is asynchronous. When we return from this method, * the nano app (probably) hasn't unloaded yet. Assuming a return of 0 * from this method, then the final success/failure for the unload is * delivered in a byte string via a call to Callback.onMessageReceipt. * * TODO(b/30784270): Provide a better success/failure delivery. * * @param nanoAppHandle handle of the nanoApp to unload * * @return 0 if the command for unloading was sent to the context hub; * -1 otherwise * * @deprecated Use {@link #unloadNanoApp(ContextHubInfo, long)} instead. */ @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int unloadNanoApp(int nanoAppHandle) { try { return mService.unloadNanoApp(nanoAppHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * get information about the nano app instance * * NOTE: The returned NanoAppInstanceInfo does _not_ contain correct * information for several fields, specifically: * - getName() * - getPublisher() * - getNeededExecMemBytes() * - getNeededReadMemBytes() * - getNeededWriteMemBytes() * * For example, say you call loadNanoApp() with a NanoApp that has * getName() returning "My Name". Later, if you call getNanoAppInstanceInfo * for that nanoapp, the returned NanoAppInstanceInfo's getName() * method will claim "Preloaded app, unknown", even though you would * have expected "My Name". For now, as the user, you'll need to * separately track the above fields if they are of interest to you. * * TODO(b/30943489): Have the returned NanoAppInstanceInfo contain the * correct information. * * @param nanoAppHandle handle of the nanoapp instance * @return NanoAppInstanceInfo the NanoAppInstanceInfo of the nanoapp, or null if the nanoapp * does not exist * * @see NanoAppInstanceInfo * * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub * for loaded nanoapps. */ @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @Nullable public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle) { try { return mService.getNanoAppInstanceInfo(nanoAppHandle); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Find a specified nano app on the system * * @param hubHandle handle of hub to search for nano app * @param filter filter specifying the search criteria for app * * @see NanoAppFilter * * @return int[] Array of handles to any found nano apps * * @deprecated Use {@link #queryNanoApps(ContextHubInfo)} instead to explicitly query the hub * for loaded nanoapps. */ @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public int[] findNanoAppOnHub(int hubHandle, @NonNull NanoAppFilter filter) { try { return mService.findNanoAppOnHub(hubHandle, filter); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Send a message to a specific nano app instance on a context hub. * * Note that the return value of this method only speaks of success * up to the point of sending this to the Context Hub. It is not * an assurance that the Context Hub successfully sent this message * on to the nanoapp. If assurance is desired, a protocol should be * established between your code and the nanoapp, with the nanoapp * sending a confirmation message (which will be reported via * Callback.onMessageReceipt). * * @param hubHandle handle of the hub to send the message to * @param nanoAppHandle handle of the nano app to send to * @param message Message to be sent * * @see ContextHubMessage * * @return int 0 on success, -1 otherwise * * @deprecated Use {@link android.hardware.location.ContextHubClient#sendMessageToNanoApp( * NanoAppMessage)} instead, after creating a * {@link android.hardware.location.ContextHubClient} with * {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)}. */ @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) public int sendMessage(int hubHandle, int nanoAppHandle, @NonNull ContextHubMessage message) { try { return mService.sendMessage(hubHandle, nanoAppHandle, message); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Returns the list of ContextHubInfo objects describing the available Context Hubs. * * @return the list of ContextHubInfo objects * * @see ContextHubInfo */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public List getContextHubs() { try { return mService.getContextHubs(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Helper function to generate a stub for a query transaction callback. * * @param transaction the transaction to unblock when complete * * @return the callback * * @hide */ private IContextHubTransactionCallback createQueryCallback( ContextHubTransaction> transaction) { return new IContextHubTransactionCallback.Stub() { @Override public void onQueryResponse(int result, List nanoappList) { transaction.setResponse(new ContextHubTransaction.Response>( result, nanoappList)); } @Override public void onTransactionComplete(int result) { Log.e(TAG, "Received a non-query callback on a query request"); transaction.setResponse(new ContextHubTransaction.Response>( ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE, null)); } }; } /** * Loads a nanoapp at the specified Context Hub. * * After the nanoapp binary is successfully loaded at the specified hub, the nanoapp will be in * the enabled state. * * @param hubInfo the hub to load the nanoapp on * @param appBinary The app binary to load * * @return the ContextHubTransaction of the request * * @throws NullPointerException if hubInfo or NanoAppBinary is null * * @see NanoAppBinary */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public ContextHubTransaction loadNanoApp( @NonNull ContextHubInfo hubInfo, @NonNull NanoAppBinary appBinary) { Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null"); Objects.requireNonNull(appBinary, "NanoAppBinary cannot be null"); ContextHubTransaction transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_LOAD_NANOAPP); IContextHubTransactionCallback callback = ContextHubTransactionHelper.createTransactionCallback(transaction); try { mService.loadNanoAppOnHub(hubInfo.getId(), callback, appBinary); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return transaction; } /** * Unloads a nanoapp at the specified Context Hub. * * @param hubInfo the hub to unload the nanoapp from * @param nanoAppId the app to unload * * @return the ContextHubTransaction of the request * * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public ContextHubTransaction unloadNanoApp( @NonNull ContextHubInfo hubInfo, long nanoAppId) { Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null"); ContextHubTransaction transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_UNLOAD_NANOAPP); IContextHubTransactionCallback callback = ContextHubTransactionHelper.createTransactionCallback(transaction); try { mService.unloadNanoAppFromHub(hubInfo.getId(), callback, nanoAppId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return transaction; } /** * Enables a nanoapp at the specified Context Hub. * * @param hubInfo the hub to enable the nanoapp on * @param nanoAppId the app to enable * * @return the ContextHubTransaction of the request * * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public ContextHubTransaction enableNanoApp( @NonNull ContextHubInfo hubInfo, long nanoAppId) { Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null"); ContextHubTransaction transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_ENABLE_NANOAPP); IContextHubTransactionCallback callback = ContextHubTransactionHelper.createTransactionCallback(transaction); try { mService.enableNanoApp(hubInfo.getId(), callback, nanoAppId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return transaction; } /** * Disables a nanoapp at the specified Context Hub. * * @param hubInfo the hub to disable the nanoapp on * @param nanoAppId the app to disable * * @return the ContextHubTransaction of the request * * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public ContextHubTransaction disableNanoApp( @NonNull ContextHubInfo hubInfo, long nanoAppId) { Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null"); ContextHubTransaction transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_DISABLE_NANOAPP); IContextHubTransactionCallback callback = ContextHubTransactionHelper.createTransactionCallback(transaction); try { mService.disableNanoApp(hubInfo.getId(), callback, nanoAppId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return transaction; } /** * Requests a query for nanoapps loaded at the specified Context Hub. * * @param hubInfo the hub to query a list of nanoapps from * * @return the ContextHubTransaction of the request * * @throws NullPointerException if hubInfo is null */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public ContextHubTransaction> queryNanoApps( @NonNull ContextHubInfo hubInfo) { Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null"); ContextHubTransaction> transaction = new ContextHubTransaction<>(ContextHubTransaction.TYPE_QUERY_NANOAPPS); IContextHubTransactionCallback callback = createQueryCallback(transaction); try { mService.queryNanoApps(hubInfo.getId(), callback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } return transaction; } /** * Set a callback to receive messages from the context hub * * @param callback Callback object * * @see Callback * * @return int 0 on success, -1 otherwise * * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to * register a {@link android.hardware.location.ContextHubClientCallback}. */ @Deprecated @SuppressLint("RequiresPermission") public int registerCallback(@NonNull Callback callback) { return registerCallback(callback, null); } /** * @deprecated Use {@link #registerCallback(Callback)} instead. * @hide */ @Deprecated public int registerCallback(ICallback callback) { if (mLocalCallback != null) { Log.w(TAG, "Max number of local callbacks reached!"); return -1; } mLocalCallback = callback; return 0; } /** * Set a callback to receive messages from the context hub * * @param callback Callback object * @param handler Handler object, if null uses the Handler of the main Looper * * @see Callback * * @return int 0 on success, -1 otherwise * * @deprecated Use {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} * or {@link #createClient(ContextHubInfo, ContextHubClientCallback)} instead to * register a {@link android.hardware.location.ContextHubClientCallback}. */ @Deprecated @SuppressLint("RequiresPermission") public int registerCallback(Callback callback, Handler handler) { synchronized(this) { if (mCallback != null) { Log.w(TAG, "Max number of callbacks reached!"); return -1; } mCallback = callback; mCallbackHandler = (handler == null) ? new Handler(mMainLooper) : handler; } return 0; } /** * Creates an interface to the ContextHubClient to send down to the service. * * @param client the ContextHubClient object associated with this callback * @param callback the callback to invoke at the client process * @param executor the executor to invoke callbacks for this client * * @return the callback interface */ private IContextHubClientCallback createClientCallback( ContextHubClient client, ContextHubClientCallback callback, Executor executor) { return new IContextHubClientCallback.Stub() { @Override public void onMessageFromNanoApp(NanoAppMessage message) { executor.execute( () -> { callback.onMessageFromNanoApp(client, message); if (Flags.reliableMessage() && Flags.reliableMessageImplementation() && message.isReliable()) { client.reliableMessageCallbackFinished( message.getMessageSequenceNumber(), ErrorCode.OK); } else { client.callbackFinished(); } }); } @Override public void onHubReset() { executor.execute( () -> { callback.onHubReset(client); client.callbackFinished(); }); } @Override public void onNanoAppAborted(long nanoAppId, int abortCode) { executor.execute( () -> { callback.onNanoAppAborted(client, nanoAppId, abortCode); client.callbackFinished(); }); } @Override public void onNanoAppLoaded(long nanoAppId) { executor.execute( () -> { callback.onNanoAppLoaded(client, nanoAppId); client.callbackFinished(); }); } @Override public void onNanoAppUnloaded(long nanoAppId) { executor.execute( () -> { callback.onNanoAppUnloaded(client, nanoAppId); client.callbackFinished(); }); } @Override public void onNanoAppEnabled(long nanoAppId) { executor.execute( () -> { callback.onNanoAppEnabled(client, nanoAppId); client.callbackFinished(); }); } @Override public void onNanoAppDisabled(long nanoAppId) { executor.execute( () -> { callback.onNanoAppDisabled(client, nanoAppId); client.callbackFinished(); }); } @Override public void onClientAuthorizationChanged( long nanoAppId, @ContextHubManager.AuthorizationState int authorization) { executor.execute( () -> { callback.onClientAuthorizationChanged(client, nanoAppId, authorization); client.callbackFinished(); }); } }; } /** * Creates and registers a client and its callback with the Context Hub Service. * * A client is registered with the Context Hub Service for a specified Context Hub. When the * registration succeeds, the client can send messages to nanoapps through the returned * {@link ContextHubClient} object, and receive notifications through the provided callback. * * @param context the context of the application * @param hubInfo the hub to attach this client to * @param executor the executor to invoke the callback * @param callback the notification callback to register * @return the registered client object * * @throws IllegalArgumentException if hubInfo does not represent a valid hub * @throws IllegalStateException if there were too many registered clients at the service * @throws NullPointerException if callback, hubInfo, or executor is null * * @see ContextHubClientCallback */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public ContextHubClient createClient( @Nullable Context context, @NonNull ContextHubInfo hubInfo, @NonNull @CallbackExecutor Executor executor, @NonNull ContextHubClientCallback callback) { Objects.requireNonNull(callback, "Callback cannot be null"); Objects.requireNonNull(hubInfo, "ContextHubInfo cannot be null"); Objects.requireNonNull(executor, "Executor cannot be null"); ContextHubClient client = new ContextHubClient(hubInfo, false /* persistent */); IContextHubClientCallback clientInterface = createClientCallback( client, callback, executor); String attributionTag = null; if (context != null) { attributionTag = context.getAttributionTag(); } // Workaround for old APIs not providing a context String packageName; if (context != null) { packageName = context.getPackageName(); } else { packageName = ActivityThread.currentPackageName(); } IContextHubClient clientProxy; try { clientProxy = mService.createClient( hubInfo.getId(), clientInterface, attributionTag, packageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } client.setClientProxy(clientProxy); return client; } /** * Equivalent to * {@link #createClient(Context, ContextHubInfo, Executor, ContextHubClientCallback)} * with the {@link Context} being set to null. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public ContextHubClient createClient( @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback, @NonNull @CallbackExecutor Executor executor) { return createClient(null /* context */, hubInfo, executor, callback); } /** * Equivalent to {@link #createClient(ContextHubInfo, ContextHubClientCallback, Executor)} * with the executor using the main thread's Looper. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public ContextHubClient createClient( @NonNull ContextHubInfo hubInfo, @NonNull ContextHubClientCallback callback) { return createClient(null /* context */, hubInfo, new HandlerExecutor(Handler.getMain()), callback); } /** * Creates a ContextHubClient that will receive notifications based on Intent events. * * This method should be used instead of {@link #createClient(ContextHubInfo, * ContextHubClientCallback)} or {@link #createClient(ContextHubInfo, ContextHubClientCallback, * Executor)} if the caller wants to preserve the messaging endpoint of a ContextHubClient, even * after a process exits. If the PendingIntent with the provided nanoapp has already been * registered at the service, then the same ContextHubClient will be regenerated without * creating a new client connection at the service. Note that the PendingIntent, nanoapp, and * Context Hub must all match in identifying a previously registered ContextHubClient. * If a client is regenerated, the host endpoint identifier attached to messages sent to the * nanoapp remains consistent, even if the original process has exited. * * To avoid unintentionally spreading data from the Context Hub to external applications, it is * strongly recommended that the PendingIntent supplied to this API is an explicit intent. * * If registered successfully, intents will be delivered regarding events or messages from the * specified nanoapp from the attached Context Hub. The intent will have an extra * {@link ContextHubManager.EXTRA_CONTEXT_HUB_INFO} of type {@link ContextHubInfo}, which * describes the Context Hub the intent event was for. The intent will also have an extra * {@link ContextHubManager.EXTRA_EVENT_TYPE} of type {@link ContextHubManager.Event}, which * will contain the type of the event. See {@link ContextHubManager.Event} for description of * each event type, along with event-specific extra fields. The client can also use * {@link ContextHubIntentEvent.fromIntent(Intent)} to parse the Intent generated by the event. * * Intent events will be delivered until {@link ContextHubClient.close()} is called. Note that * the registration of this ContextHubClient at the Context Hub Service will be maintained until * {@link ContextHubClient.close()} is called. If {@link PendingIntent.cancel()} is called * on the provided PendingIntent, then the client will be automatically unregistered by the * service. * * Note that the {@link PendingIntent} supplied to this API must be mutable for Intent * notifications to work. * * @param context the context of the application. If a PendingIntent client is recreated, * the latest state in the context will be used and old state will be discarded * @param hubInfo the hub to attach this client to * @param pendingIntent the PendingIntent to register to the client * @param nanoAppId the ID of the nanoapp that Intent events will be generated for * @return the registered client object * * @throws IllegalArgumentException if hubInfo does not represent a valid hub, or an immutable * PendingIntent was supplied * @throws IllegalStateException if there were too many registered clients at the service * @throws NullPointerException if pendingIntent or hubInfo is null */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public ContextHubClient createClient( @Nullable Context context, @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) { Objects.requireNonNull(pendingIntent); Objects.requireNonNull(hubInfo); if (pendingIntent.isImmutable()) { throw new IllegalArgumentException("PendingIntent must be mutable"); } ContextHubClient client = new ContextHubClient(hubInfo, true /* persistent */); String attributionTag = null; if (context != null) { attributionTag = context.getAttributionTag(); } IContextHubClient clientProxy; try { clientProxy = mService.createPendingIntentClient( hubInfo.getId(), pendingIntent, nanoAppId, attributionTag); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } client.setClientProxy(clientProxy); return client; } /** * Equivalent to {@link #createClient(ContextHubInfo, PendingIntent, long, String)} * with {@link Context} being set to null. */ @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public ContextHubClient createClient( @NonNull ContextHubInfo hubInfo, @NonNull PendingIntent pendingIntent, long nanoAppId) { return createClient(null /* context */, hubInfo, pendingIntent, nanoAppId); } /** * Queries for the list of preloaded nanoapp IDs on the system. * * @param hubInfo The Context Hub to query a list of nanoapp IDs from. * * @return The list of 64-bit IDs of the preloaded nanoapps. * * @throws NullPointerException if hubInfo is null * @hide */ @TestApi @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public long[] getPreloadedNanoAppIds(@NonNull ContextHubInfo hubInfo) { Objects.requireNonNull(hubInfo, "hubInfo cannot be null"); long[] nanoappIds = null; try { nanoappIds = mService.getPreloadedNanoAppIds(hubInfo); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } if (nanoappIds == null) { nanoappIds = new long[0]; } return nanoappIds; } /** * Puts the Context Hub in test mode. * * The purpose of this API is to make testing CHRE/Context Hub more * predictable and robust. This temporarily unloads all * nanoapps. * * Note that this API must not cause CHRE/Context Hub to behave differently * in test compared to production. * * @return true if the enable test mode operation succeeded. * @hide */ @TestApi @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public boolean enableTestMode() { try { return mService.setTestMode(true); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Puts the Context Hub out of test mode. * * This API will undo any previously made enableTestMode() calls. * After this API is called, it should restore the state of the system * to the normal/production mode before any enableTestMode() call was * made. If the enableTestMode() call unloaded any nanoapps * to enter test mode, it should reload those nanoapps in this API call. * * @return true if the disable operation succeeded. * @hide */ @TestApi @RequiresPermission(android.Manifest.permission.ACCESS_CONTEXT_HUB) @NonNull public boolean disableTestMode() { try { return mService.setTestMode(false); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * Unregister a callback for receive messages from the context hub. * * @see Callback * * @param callback method to deregister * * @return int 0 on success, -1 otherwise * * @deprecated Use {@link android.hardware.location.ContextHubClient#close()} to unregister * a {@link android.hardware.location.ContextHubClientCallback}. */ @SuppressLint("RequiresPermission") @Deprecated public int unregisterCallback(@NonNull Callback callback) { synchronized(this) { if (callback != mCallback) { Log.w(TAG, "Cannot recognize callback!"); return -1; } mCallback = null; mCallbackHandler = null; } return 0; } /** * @deprecated Use {@link #unregisterCallback(Callback)} instead. * @hide */ @Deprecated public synchronized int unregisterCallback(ICallback callback) { if (callback != mLocalCallback) { Log.w(TAG, "Cannot recognize local callback!"); return -1; } mLocalCallback = null; return 0; } /** * Invokes the ContextHubManager.Callback callback registered with the ContextHubManager. * * @param hubId The ID of the Context Hub the message came from * @param nanoAppId The instance ID of the nanoapp the message came from * @param message The message to provide the callback */ private synchronized void invokeOnMessageReceiptCallback( int hubId, int nanoAppId, ContextHubMessage message) { if (mCallback != null) { mCallback.onMessageReceipt(hubId, nanoAppId, message); } } private final IContextHubCallback.Stub mClientCallback = new IContextHubCallback.Stub() { @Override public void onMessageReceipt( final int hubId, final int nanoAppId, final ContextHubMessage message) { synchronized (ContextHubManager.this) { if (mCallback != null) { mCallbackHandler.post( () -> invokeOnMessageReceiptCallback(hubId, nanoAppId, message)); } else if (mLocalCallback != null) { // We always ensure that mCallback takes precedence, because mLocalCallback is // only for internal compatibility mLocalCallback.onMessageReceipt(hubId, nanoAppId, message); } } } }; /** @hide */ public ContextHubManager(@NonNull IContextHubService service, @NonNull Looper mainLooper) { requireNonNull(service, "service cannot be null"); requireNonNull(mainLooper, "mainLooper cannot be null"); mService = service; mMainLooper = mainLooper; try { mService.registerCallback(mClientCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } }