diff --git a/Android.mk b/Android.mk index 7a3c46dc8e419..06f64f1f1601d 100644 --- a/Android.mk +++ b/Android.mk @@ -219,6 +219,8 @@ LOCAL_SRC_FILES += \ core/java/android/hardware/location/IGeofenceHardwareCallback.aidl \ core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \ core/java/android/hardware/location/IContextHubCallback.aidl \ + core/java/android/hardware/location/IContextHubClient.aidl \ + core/java/android/hardware/location/IContextHubClientCallback.aidl \ core/java/android/hardware/location/IContextHubService.aidl \ core/java/android/hardware/location/IContextHubTransactionCallback.aidl \ core/java/android/hardware/radio/IRadioService.aidl \ diff --git a/core/java/android/hardware/location/ContextHubClient.java b/core/java/android/hardware/location/ContextHubClient.java index b7e353a4860c3..52527ed67ae4d 100644 --- a/core/java/android/hardware/location/ContextHubClient.java +++ b/core/java/android/hardware/location/ContextHubClient.java @@ -16,43 +16,48 @@ package android.hardware.location; import android.annotation.RequiresPermission; -import android.os.Handler; +import android.os.RemoteException; + +import dalvik.system.CloseGuard; import java.io.Closeable; +import java.util.concurrent.atomic.AtomicBoolean; /** * A class describing a client of the Context Hub Service. * - * Clients can send messages to nanoapps at a Context Hub through this object. + * Clients can send messages to nanoapps at a Context Hub through this object. The APIs supported + * by this object are thread-safe and can be used without external synchronization. * * @hide */ public class ContextHubClient implements Closeable { /* - * The ContextHubClient interface associated with this client. + * The proxy to the client interface at the service. */ - // TODO: Implement this interface and associate with ContextHubClient object - // private final IContextHubClient mClientInterface; + private final IContextHubClient mClientProxy; /* - * The listening callback associated with this client. + * The callback interface associated with this client. */ - private ContextHubClientCallback mCallback; + private final IContextHubClientCallback mCallbackInterface; /* * The Context Hub that this client is attached to. */ - private ContextHubInfo mAttachedHub; + private final ContextHubInfo mAttachedHub; - /* - * The handler to invoke mCallback. - */ - private Handler mCallbackHandler; + private final CloseGuard mCloseGuard = CloseGuard.get(); - ContextHubClient(ContextHubClientCallback callback, Handler handler, ContextHubInfo hubInfo) { - mCallback = callback; - mCallbackHandler = handler; + private final AtomicBoolean mIsClosed = new AtomicBoolean(false); + + /* package */ ContextHubClient( + IContextHubClient clientProxy, IContextHubClientCallback callback, + ContextHubInfo hubInfo) { + mClientProxy = clientProxy; + mCallbackInterface = callback; mAttachedHub = hubInfo; + mCloseGuard.open("close"); } /** @@ -71,7 +76,14 @@ public class ContextHubClient implements Closeable { * All futures messages targeted for this client are dropped at the service. */ public void close() { - throw new UnsupportedOperationException("TODO: Implement this"); + if (!mIsClosed.getAndSet(true)) { + mCloseGuard.close(); + try { + mClientProxy.close(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } /** @@ -90,6 +102,22 @@ public class ContextHubClient implements Closeable { @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) @ContextHubTransaction.Result public int sendMessageToNanoApp(NanoAppMessage message) { - throw new UnsupportedOperationException("TODO: Implement this"); + try { + return mClientProxy.sendMessageToNanoApp(message); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Override + protected void finalize() throws Throwable { + try { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } finally { + super.finalize(); + } } } diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java index 241172784262e..b31c7bcdb31e5 100644 --- a/core/java/android/hardware/location/ContextHubManager.java +++ b/core/java/android/hardware/location/ContextHubManager.java @@ -455,6 +455,54 @@ public final class ContextHubManager { return 0; } + /** + * Creates an interface to the ContextHubClient to send down to the service. + * + * @param callback the callback to invoke at the client process + * @param handler the handler to post callbacks for this client + * + * @return the callback interface + */ + private IContextHubClientCallback createClientCallback( + ContextHubClientCallback callback, Handler handler) { + return new IContextHubClientCallback.Stub() { + @Override + public void onMessageFromNanoApp(NanoAppMessage message) { + handler.post(() -> callback.onMessageFromNanoApp(message)); + } + + @Override + public void onHubReset() { + handler.post(() -> callback.onHubReset()); + } + + @Override + public void onNanoAppAborted(long nanoAppId, int abortCode) { + handler.post(() -> callback.onNanoAppAborted(nanoAppId, abortCode)); + } + + @Override + public void onNanoAppLoaded(long nanoAppId) { + handler.post(() -> callback.onNanoAppLoaded(nanoAppId)); + } + + @Override + public void onNanoAppUnloaded(long nanoAppId) { + handler.post(() -> callback.onNanoAppUnloaded(nanoAppId)); + } + + @Override + public void onNanoAppEnabled(long nanoAppId) { + handler.post(() -> callback.onNanoAppEnabled(nanoAppId)); + } + + @Override + public void onNanoAppDisabled(long nanoAppId) { + handler.post(() -> callback.onNanoAppDisabled(nanoAppId)); + } + }; + } + /** * Creates and registers a client and its callback with the Context Hub Service. * @@ -463,19 +511,37 @@ public final class ContextHubManager { * {@link ContextHubClient} object, and receive notifications through the provided callback. * * @param callback the notification callback to register - * @param hubInfo the hub to attach this client to - * @param handler the handler to invoke the callback, if null uses the main thread's Looper - * + * @param hubInfo the hub to attach this client to + * @param handler the handler to invoke the callback, if null uses the main thread's Looper * @return the registered client object * - * @see ContextHubClientCallback + * @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 or hubInfo is null * * @hide + * @see ContextHubClientCallback */ public ContextHubClient createClient( ContextHubClientCallback callback, ContextHubInfo hubInfo, @Nullable Handler handler) { - throw new UnsupportedOperationException( - "TODO: Implement this, and throw an exception on error"); + if (callback == null) { + throw new NullPointerException("Callback cannot be null"); + } + if (hubInfo == null) { + throw new NullPointerException("Hub info cannot be null"); + } + + Handler realHandler = (handler == null) ? new Handler(mMainLooper) : handler; + IContextHubClientCallback clientInterface = createClientCallback(callback, realHandler); + + IContextHubClient client; + try { + client = mService.createClient(clientInterface, hubInfo.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + return new ContextHubClient(client, clientInterface, hubInfo); } /** diff --git a/core/java/android/hardware/location/IContextHubClient.aidl b/core/java/android/hardware/location/IContextHubClient.aidl new file mode 100644 index 0000000000000..d81126a0ac543 --- /dev/null +++ b/core/java/android/hardware/location/IContextHubClient.aidl @@ -0,0 +1,31 @@ +/* + * Copyright 2017 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 android.hardware.location.NanoAppMessage; + +/** + * @hide + */ +interface IContextHubClient { + + // Sends a message to a nanoapp + int sendMessageToNanoApp(in NanoAppMessage message); + + // Closes the connection with the Context Hub + void close(); +} diff --git a/core/java/android/hardware/location/IContextHubClientCallback.aidl b/core/java/android/hardware/location/IContextHubClientCallback.aidl new file mode 100644 index 0000000000000..1c76bcbe18ce4 --- /dev/null +++ b/core/java/android/hardware/location/IContextHubClientCallback.aidl @@ -0,0 +1,49 @@ +/* + * Copyright 2017 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 android.hardware.location.NanoAppMessage; + +/** + * An interface used by the Context Hub Service to invoke callbacks for lifecycle notifications of a + * Context Hub and nanoapps, as well as for nanoapp messaging. + * + * @hide + */ +oneway interface IContextHubClientCallback { + + // Callback invoked when receiving a message from a nanoapp. + void onMessageFromNanoApp(in NanoAppMessage message); + + // Callback invoked when the attached Context Hub has reset. + void onHubReset(); + + // Callback invoked when a nanoapp aborts at the attached Context Hub. + void onNanoAppAborted(long nanoAppId, int abortCode); + + // Callback invoked when a nanoapp is loaded at the attached Context Hub. + void onNanoAppLoaded(long nanoAppId); + + // Callback invoked when a nanoapp is unloaded from the attached Context Hub. + void onNanoAppUnloaded(long nanoAppId); + + // Callback invoked when a nanoapp is enabled at the attached Context Hub. + void onNanoAppEnabled(long nanoAppId); + + // Callback invoked when a nanoapp is disabled at the attached Context Hub. + void onNanoAppDisabled(long nanoAppId); +} diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl index ff8c1d07ce2c1..1bb7c8fbb6e72 100644 --- a/core/java/android/hardware/location/IContextHubService.aidl +++ b/core/java/android/hardware/location/IContextHubService.aidl @@ -17,12 +17,14 @@ package android.hardware.location; // Declare any non-default types here with import statements -import android.hardware.location.ContextHubMessage; import android.hardware.location.ContextHubInfo; +import android.hardware.location.ContextHubMessage; import android.hardware.location.NanoApp; -import android.hardware.location.NanoAppInstanceInfo; import android.hardware.location.NanoAppFilter; +import android.hardware.location.NanoAppInstanceInfo; import android.hardware.location.IContextHubCallback; +import android.hardware.location.IContextHubClient; +import android.hardware.location.IContextHubClientCallback; /** * @hide @@ -52,4 +54,7 @@ interface IContextHubService { // send a message to a nanoApp int sendMessage(int hubHandle, int nanoAppHandle, in ContextHubMessage msg); + + // Creates a client to send and receive messages + IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId); } diff --git a/services/core/java/com/android/server/location/ContextHubClientBroker.java b/services/core/java/com/android/server/location/ContextHubClientBroker.java new file mode 100644 index 0000000000000..41d9feb97c4e2 --- /dev/null +++ b/services/core/java/com/android/server/location/ContextHubClientBroker.java @@ -0,0 +1,226 @@ +/* + * Copyright 2017 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 com.android.server.location; + +import android.content.Context; +import android.hardware.contexthub.V1_0.ContextHubMsg; +import android.hardware.contexthub.V1_0.IContexthub; +import android.hardware.contexthub.V1_0.Result; +import android.hardware.location.ContextHubTransaction; +import android.hardware.location.IContextHubClient; +import android.hardware.location.IContextHubClientCallback; +import android.hardware.location.NanoAppMessage; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle + * notification callbacks. This class implements the IContextHubClient object, and the implemented + * APIs must be thread-safe. + * + * @hide + */ +public class ContextHubClientBroker extends IContextHubClient.Stub + implements IBinder.DeathRecipient { + private static final String TAG = "ContextHubClientBroker"; + + /* + * The context of the service. + */ + private final Context mContext; + + /* + * The proxy to talk to the Context Hub HAL. + */ + private final IContexthub mContextHubProxy; + + /* + * The manager that registered this client. + */ + private final ContextHubClientManager mClientManager; + + /* + * The ID of the hub that this client is attached to. + */ + private final int mAttachedContextHubId; + + /* + * The host end point ID of this client. + */ + private final short mHostEndPointId; + + /* + * The remote callback interface for this client. + */ + private final IContextHubClientCallback mCallbackInterface; + + /* + * false if the connection has been closed by the client, true otherwise. + */ + private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true); + + /* package */ ContextHubClientBroker( + Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager, + int contextHubId, short hostEndPointId, IContextHubClientCallback callback) { + mContext = context; + mContextHubProxy = contextHubProxy; + mClientManager = clientManager; + mAttachedContextHubId = contextHubId; + mHostEndPointId = hostEndPointId; + mCallbackInterface = callback; + } + + /** + * Attaches a death recipient for this client + * + * @throws RemoteException if the client has already died + */ + /* package */ void attachDeathRecipient() throws RemoteException { + mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */); + } + + /** + * Sends from this client to a nanoapp. + * + * @param message the message to send + * @return the error code of sending the message + */ + @ContextHubTransaction.Result + @Override + public int sendMessageToNanoApp(NanoAppMessage message) { + ContextHubServiceUtil.checkPermissions(mContext); + + int result; + if (mConnectionOpen.get()) { + ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage( + mHostEndPointId, message); + + try { + result = mContextHubProxy.sendMessageToHub(mAttachedContextHubId, messageToNanoApp); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = " + + mAttachedContextHubId + ")", e); + result = Result.UNKNOWN_FAILURE; + } + } else { + Log.e(TAG, "Failed to send message to nanoapp: client connection is closed"); + result = Result.UNKNOWN_FAILURE; + } + + return ContextHubServiceUtil.toTransactionResult(result); + } + + /** + * Closes the connection for this client with the service. + */ + @Override + public void close() { + if (mConnectionOpen.getAndSet(false)) { + mClientManager.unregisterClient(mHostEndPointId); + } + } + + /** + * Invoked when the underlying binder of this broker has died at the client process. + */ + public void binderDied() { + try { + IContextHubClient.Stub.asInterface(this).close(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while closing client on death", e); + } + } + + /** + * @return the ID of the context hub this client is attached to + */ + /* package */ int getAttachedContextHubId() { + return mAttachedContextHubId; + } + + /** + * @return the host endpoint ID of this client + */ + /* package */ short getHostEndPointId() { + return mHostEndPointId; + } + + /** + * Sends a message to the client associated with this object. + * + * @param message the message that came from a nanoapp + */ + /* package */ void sendMessageToClient(NanoAppMessage message) { + if (mConnectionOpen.get()) { + try { + mCallbackInterface.onMessageFromNanoApp(message); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while sending message to client (host endpoint ID = " + + mHostEndPointId + ")", e); + } + } + } + + /** + * Handles a nanoapp load event. + * + * @param nanoAppId the ID of the nanoapp that was loaded. + */ + /* package */ void onNanoAppLoaded(long nanoAppId) { + if (mConnectionOpen.get()) { + try { + mCallbackInterface.onNanoAppLoaded(nanoAppId); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling onNanoAppLoaded on client" + + " (host endpoint ID = " + mHostEndPointId + ")", e); + } + } + } + + /** + * Handles a nanoapp unload event. + * + * @param nanoAppId the ID of the nanoapp that was unloaded. + */ + /* package */ void onNanoAppUnloaded(long nanoAppId) { + if (mConnectionOpen.get()) { + try { + mCallbackInterface.onNanoAppUnloaded(nanoAppId); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling onNanoAppUnloaded on client" + + " (host endpoint ID = " + mHostEndPointId + ")", e); + } + } + } + + /** + * Handles a hub reset for this client. + */ + /* package */ void onHubReset() { + if (mConnectionOpen.get()) { + try { + mCallbackInterface.onHubReset(); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException while calling onHubReset on client" + + " (host endpoint ID = " + mHostEndPointId + ")", e); + } + } + } +} diff --git a/services/core/java/com/android/server/location/ContextHubClientManager.java b/services/core/java/com/android/server/location/ContextHubClientManager.java new file mode 100644 index 0000000000000..d58a7460b23fa --- /dev/null +++ b/services/core/java/com/android/server/location/ContextHubClientManager.java @@ -0,0 +1,237 @@ +/* + * Copyright 2017 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 com.android.server.location; + +import android.content.Context; +import android.hardware.contexthub.V1_0.ContextHubMsg; +import android.hardware.contexthub.V1_0.IContexthub; +import android.hardware.location.IContextHubClient; +import android.hardware.location.IContextHubClientCallback; +import android.hardware.location.NanoAppMessage; +import android.os.RemoteException; +import android.util.Log; + +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +/** + * A class that manages registration/unregistration of clients and manages messages to/from clients. + * + * @hide + */ +/* package */ class ContextHubClientManager { + private static final String TAG = "ContextHubClientManager"; + + /* + * The maximum host endpoint ID value that a client can be assigned. + */ + private static final int MAX_CLIENT_ID = 0x7fff; + + /* + * Local flag to enable debug logging. + */ + private static final boolean DEBUG_LOG_ENABLED = true; + + /* + * The context of the service. + */ + private final Context mContext; + + /* + * The proxy to talk to the Context Hub. + */ + private final IContexthub mContextHubProxy; + + /* + * A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients. + * A concurrent data structure is used since the registration/unregistration can occur in + * multiple threads. + */ + private final ConcurrentHashMap mHostEndPointIdToClientMap = + new ConcurrentHashMap<>(); + + /* + * The next host endpoint ID to start iterating for the next available host endpoint ID. + */ + private int mNextHostEndpointId = 0; + + /* package */ ContextHubClientManager( + Context context, IContexthub contextHubProxy) { + mContext = context; + mContextHubProxy = contextHubProxy; + } + + /** + * Registers a new client with the service. + * + * @param clientCallback the callback interface of the client to register + * @param contextHubId the ID of the hub this client is attached to + * + * @return the client interface + * + * @throws IllegalStateException if max number of clients have already registered + */ + /* package */ IContextHubClient registerClient( + IContextHubClientCallback clientCallback, int contextHubId) { + ContextHubClientBroker broker = createNewClientBroker(clientCallback, contextHubId); + + try { + broker.attachDeathRecipient(); + } catch (RemoteException e) { + // The client process has died, so we close the connection and return null. + Log.e(TAG, "Failed to attach death recipient to client"); + broker.close(); + return null; + } + + Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId()); + return IContextHubClient.Stub.asInterface(broker); + } + + /** + * Handles a message sent from a nanoapp. + * + * @param contextHubId the ID of the hub where the nanoapp sent the message from + * @param message the message send by a nanoapp + */ + /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) { + NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message); + + if (DEBUG_LOG_ENABLED) { + String targetAudience = clientMessage.isBroadcastMessage() ? "broadcast" : "unicast"; + Log.v(TAG, "Received a " + targetAudience + " message from nanoapp 0x" + + Long.toHexString(clientMessage.getNanoAppId())); + } + + if (clientMessage.isBroadcastMessage()) { + broadcastMessage(contextHubId, clientMessage); + } else { + ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint); + if (proxy != null) { + proxy.sendMessageToClient(clientMessage); + } else { + Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = " + + message.hostEndPoint + ")"); + } + } + } + + /** + * Unregisters a client from the service. + * + * This method should be invoked as a result of a client calling the ContextHubClient.close(), + * or if the client process has died. + * + * @param hostEndPointId the host endpoint ID of the client that has died + */ + /* package */ void unregisterClient(short hostEndPointId) { + if (mHostEndPointIdToClientMap.remove(hostEndPointId) != null) { + Log.d(TAG, "Unregistered client with host endpoint ID " + hostEndPointId); + } else { + Log.e(TAG, "Cannot unregister non-existing client with host endpoint ID " + + hostEndPointId); + } + } + + /** + * Handles a nanoapp load event. + * + * @param contextHubId the ID of the hub where the nanoapp was loaded. + * @param nanoAppId the ID of the nanoapp that was loaded. + */ + /* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) { + forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId)); + } + + /** + * Handles a nanoapp unload event. + * + * @param contextHubId the ID of the hub where the nanoapp was unloaded. + * @param nanoAppId the ID of the nanoapp that was unloaded. + */ + /* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) { + forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId)); + } + + /** + * Handles a hub reset. + * + * @param contextHubId the ID of the hub that has reset. + */ + /* package */ void onHubReset(int contextHubId) { + forEachClientOfHub(contextHubId, client -> client.onHubReset()); + } + + /** + * Creates a new ContextHubClientBroker object for a client and registers it with the client + * manager. + * + * @param clientCallback the callback interface of the client to register + * @param contextHubId the ID of the hub this client is attached to + * + * @return the ContextHubClientBroker object + * + * @throws IllegalStateException if max number of clients have already registered + */ + private synchronized ContextHubClientBroker createNewClientBroker( + IContextHubClientCallback clientCallback, int contextHubId) { + if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) { + throw new IllegalStateException("Could not register client - max limit exceeded"); + } + + ContextHubClientBroker broker = null; + int id = mNextHostEndpointId; + for (int i = 0; i <= MAX_CLIENT_ID; i++) { + if (!mHostEndPointIdToClientMap.containsKey(id)) { + broker = new ContextHubClientBroker( + mContext, mContextHubProxy, this, contextHubId, (short)id, clientCallback); + mHostEndPointIdToClientMap.put((short)id, broker); + mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1; + break; + } + + id = (id == MAX_CLIENT_ID) ? 0 : id + 1; + } + + return broker; + } + + /** + * Broadcasts a message from a nanoapp to all clients attached to the associated hub. + * + * @param contextHubId the ID of the hub where the nanoapp sent the message from + * @param message the message send by a nanoapp + */ + private void broadcastMessage(int contextHubId, NanoAppMessage message) { + forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message)); + } + + /** + * Runs a command for each client that is attached to a hub with the given ID. + * + * @param contextHubId the ID of the hub + * @param callback the command to invoke for the client + */ + private void forEachClientOfHub(int contextHubId, Consumer callback) { + for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) { + if (broker.getAttachedContextHubId() == contextHubId) { + callback.accept(broker); + } + } + } +} diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java index da481a8c2b474..e08c65967b32a 100644 --- a/services/core/java/com/android/server/location/ContextHubService.java +++ b/services/core/java/com/android/server/location/ContextHubService.java @@ -16,12 +16,10 @@ package com.android.server.location; -import android.Manifest; import android.content.Context; import android.hardware.contexthub.V1_0.AsyncEventType; import android.hardware.contexthub.V1_0.ContextHub; import android.hardware.contexthub.V1_0.ContextHubMsg; -import android.hardware.contexthub.V1_0.HostEndPoint; import android.hardware.contexthub.V1_0.HubAppInfo; import android.hardware.contexthub.V1_0.IContexthub; import android.hardware.contexthub.V1_0.IContexthubCallback; @@ -29,13 +27,17 @@ import android.hardware.contexthub.V1_0.Result; import android.hardware.contexthub.V1_0.TransactionResult; import android.hardware.location.ContextHubInfo; import android.hardware.location.ContextHubMessage; +import android.hardware.location.ContextHubTransaction; import android.hardware.location.IContextHubCallback; +import android.hardware.location.IContextHubClient; +import android.hardware.location.IContextHubClientCallback; import android.hardware.location.IContextHubService; import android.hardware.location.IContextHubTransactionCallback; import android.hardware.location.NanoApp; import android.hardware.location.NanoAppBinary; import android.hardware.location.NanoAppFilter; import android.hardware.location.NanoAppInstanceInfo; +import android.hardware.location.NanoAppMessage; import android.hardware.location.NanoAppState; import android.os.RemoteCallbackList; import android.os.RemoteException; @@ -49,7 +51,9 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; @@ -58,9 +62,6 @@ import java.util.concurrent.ConcurrentHashMap; */ public class ContextHubService extends IContextHubService.Stub { private static final String TAG = "ContextHubService"; - private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE; - private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '" - + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware"; /* * Constants for the type of transaction that is defined by ContextHubService. @@ -104,6 +105,12 @@ public class ContextHubService extends IContextHubService.Stub { // The manager for transaction queue private final ContextHubTransactionManager mTransactionManager; + // The manager for sending messages to/from clients + private final ContextHubClientManager mClientManager; + + // The default client for old API clients + private final Map mDefaultClientMap; + /** * Class extending the callback to register with a Context Hub. */ @@ -146,21 +153,34 @@ public class ContextHubService extends IContextHubService.Stub { mContextHubProxy = getContextHubProxy(); if (mContextHubProxy == null) { mTransactionManager = null; + mClientManager = null; + mDefaultClientMap = Collections.EMPTY_MAP; mContextHubInfo = new ContextHubInfo[0]; return; } - mTransactionManager = new ContextHubTransactionManager(mContextHubProxy); + mClientManager = new ContextHubClientManager(mContext, mContextHubProxy); + mTransactionManager = new ContextHubTransactionManager(mContextHubProxy, mClientManager); List hubList; try { hubList = mContextHubProxy.getHubs(); } catch (RemoteException e) { - Log.e(TAG, "RemoteException while getting Context Hub info"); + Log.e(TAG, "RemoteException while getting Context Hub info", e); hubList = Collections.emptyList(); } mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList); + HashMap defaultClientMap = new HashMap<>(); + for (ContextHubInfo contextHubInfo : mContextHubInfo) { + int contextHubId = contextHubInfo.getId(); + + IContextHubClient client = mClientManager.registerClient( + createDefaultClientCallback(contextHubId), contextHubId); + defaultClientMap.put(contextHubId, client); + } + mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap); + for (ContextHubInfo contextHubInfo : mContextHubInfo) { int contextHubId = contextHubInfo.getId(); try { @@ -168,7 +188,7 @@ public class ContextHubService extends IContextHubService.Stub { contextHubId, new ContextHubServiceCallback(contextHubId)); } catch (RemoteException e) { Log.e(TAG, "RemoteException while registering service callback for hub (ID = " - + contextHubId + ")"); + + contextHubId + ")", e); } } @@ -184,6 +204,53 @@ public class ContextHubService extends IContextHubService.Stub { } } + /** + * Creates a default client callback for old API clients. + * + * @param contextHubId the ID of the hub to attach this client to + * @return the internal callback interface + */ + private IContextHubClientCallback createDefaultClientCallback(int contextHubId) { + return new IContextHubClientCallback.Stub() { + @Override + public void onMessageFromNanoApp(NanoAppMessage message) { + int nanoAppInstanceId = + mNanoAppIdToInstanceMap.containsKey(message.getNanoAppId()) ? + mNanoAppIdToInstanceMap.get(message.getNanoAppId()) : -1; + + onMessageReceiptOldApi( + message.getMessageType(), contextHubId, nanoAppInstanceId, + message.getMessageBody()); + } + + @Override + public void onHubReset() { + byte[] data = {TransactionResult.SUCCESS}; + onMessageReceiptOldApi(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data); + } + + @Override + public void onNanoAppAborted(long nanoAppId, int abortCode) { + } + + @Override + public void onNanoAppLoaded(long nanoAppId) { + } + + @Override + public void onNanoAppUnloaded(long nanoAppId) { + } + + @Override + public void onNanoAppEnabled(long nanoAppId) { + } + + @Override + public void onNanoAppDisabled(long nanoAppId) { + } + }; + } + /** * @return the IContexthub proxy interface */ @@ -192,7 +259,7 @@ public class ContextHubService extends IContextHubService.Stub { try { proxy = IContexthub.getService(true /* retry */); } catch (RemoteException e) { - Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy"); + Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy", e); } catch (NoSuchElementException e) { Log.i(TAG, "Context Hub HAL service not found"); } @@ -204,6 +271,7 @@ public class ContextHubService extends IContextHubService.Stub { public int registerCallback(IContextHubCallback callback) throws RemoteException { checkPermissions(); mCallbacksList.register(callback); + Log.d(TAG, "Added callback, total callbacks " + mCallbacksList.getRegisteredCallbackCount()); return 0; @@ -292,7 +360,7 @@ public class ContextHubService extends IContextHubService.Stub { @Override public void onQueryResponse(int result, List nanoAppStateList) { byte[] data = {(byte) result}; - onMessageReceipt(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data); + onMessageReceiptOldApi(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data); } }; } @@ -449,41 +517,37 @@ public class ContextHubService extends IContextHubService.Stub { return -1; } if (msg.getData() == null) { - Log.w(TAG, "ContextHubMessage message body cannot be null"); + Log.e(TAG, "ContextHubMessage message body cannot be null"); + return -1; + } + if (!mDefaultClientMap.containsKey(hubHandle)) { + Log.e(TAG, "Hub with ID " + hubHandle + " does not exist"); return -1; } - int result; + boolean success = false; if (nanoAppHandle == OS_APP_INSTANCE) { if (msg.getMsgType() == MSG_QUERY_NANO_APPS) { - result = queryNanoAppsInternal(hubHandle); + success = (queryNanoAppsInternal(hubHandle) == Result.OK); } else { Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType()); - result = Result.BAD_PARAMS; } } else { NanoAppInstanceInfo info = getNanoAppInstanceInfo(nanoAppHandle); if (info != null) { - ContextHubMsg hubMessage = new ContextHubMsg(); - hubMessage.appName = info.getAppId(); - hubMessage.msgType = msg.getMsgType(); - hubMessage.hostEndPoint = HostEndPoint.UNSPECIFIED; - ContextHubServiceUtil.copyToByteArrayList(msg.getData(), hubMessage.msg); + NanoAppMessage message = NanoAppMessage.createMessageToNanoApp( + info.getAppId(), msg.getMsgType(), msg.getData()); - try { - result = mContextHubProxy.sendMessageToHub(hubHandle, hubMessage); - } catch (RemoteException e) { - Log.e(TAG, "Failed to send nanoapp message - RemoteException"); - result = Result.UNKNOWN_FAILURE; - } + IContextHubClient client = mDefaultClientMap.get(hubHandle); + success = (client.sendMessageToNanoApp(message) == + ContextHubTransaction.TRANSACTION_SUCCESS); } else { Log.e(TAG, "Failed to send nanoapp message - nanoapp with instance ID " + nanoAppHandle + " does not exist."); - result = Result.BAD_PARAMS; } } - return (result == Result.OK ? 0 : -1); + return success ? 0 : -1; } /** @@ -493,12 +557,7 @@ public class ContextHubService extends IContextHubService.Stub { * @param message the message contents */ private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) { - // TODO(b/67734082): Send to new API clients - byte[] data = ContextHubServiceUtil.createPrimitiveByteArray(message.msg); - - int nanoAppInstanceId = mNanoAppIdToInstanceMap.containsKey(message.appName) ? - mNanoAppIdToInstanceMap.get(message.appName) : -1; - onMessageReceipt(message.msgType, contextHubId, nanoAppInstanceId, data); + mClientManager.onMessageFromNanoApp(contextHubId, message); } /** @@ -534,7 +593,7 @@ public class ContextHubService extends IContextHubService.Stub { data[0] = (byte) result; ByteBuffer.wrap(data, 1, 4).order(ByteOrder.nativeOrder()).putInt(instanceId); - onMessageReceipt(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data); + onMessageReceiptOldApi(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data); } /** @@ -552,7 +611,7 @@ public class ContextHubService extends IContextHubService.Stub { byte[] data = new byte[1]; data[0] = (byte) result; - onMessageReceipt(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data); + onMessageReceiptOldApi(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data); } /** @@ -577,8 +636,7 @@ public class ContextHubService extends IContextHubService.Stub { mTransactionManager.onHubReset(); queryNanoAppsInternal(contextHubId); - byte[] data = {TransactionResult.SUCCESS}; - onMessageReceipt(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data); + mClientManager.onHubReset(contextHubId); } else { Log.i(TAG, "Received unknown hub event (hub ID = " + contextHubId + ", type = " + eventType + ")"); @@ -641,6 +699,45 @@ public class ContextHubService extends IContextHubService.Stub { } } + /** + * @param contextHubId the hub ID to validate + * @return {@code true} if the ID represents that of an available hub, {@code false} otherwise + */ + private boolean isValidContextHubId(int contextHubId) { + for (ContextHubInfo hubInfo : mContextHubInfo) { + if (hubInfo.getId() == contextHubId) { + return true; + } + } + + return false; + } + + /** + * Creates and registers a client at the service for the specified Context Hub. + * + * @param clientCallback the client interface to register with the service + * @param contextHubId the ID of the hub this client is attached to + * @return the generated client interface, null if registration was unsuccessful + * + * @throws IllegalArgumentException if contextHubId is not a valid ID + * @throws IllegalStateException if max number of clients have already registered + * @throws NullPointerException if clientCallback is null + */ + @Override + public IContextHubClient createClient( + IContextHubClientCallback clientCallback, int contextHubId) throws RemoteException { + checkPermissions(); + if (!isValidContextHubId(contextHubId)) { + throw new IllegalArgumentException("Invalid context hub ID " + contextHubId); + } + if (clientCallback == null) { + throw new NullPointerException("Cannot register client with null callback"); + } + + return mClientManager.registerClient(clientCallback, contextHubId); + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; @@ -664,10 +761,10 @@ public class ContextHubService extends IContextHubService.Stub { } private void checkPermissions() { - mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE); + ContextHubServiceUtil.checkPermissions(mContext); } - private int onMessageReceipt(int msgType, int hubHandle, int appInstance, byte[] data) { + private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) { if (data == null) { return -1; } diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java index ddbaf86beff23..6faeb72e2a4d9 100644 --- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java +++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java @@ -16,11 +16,15 @@ package com.android.server.location; +import android.Manifest; +import android.content.Context; import android.hardware.contexthub.V1_0.ContextHub; import android.hardware.contexthub.V1_0.ContextHubMsg; import android.hardware.contexthub.V1_0.HostEndPoint; import android.hardware.contexthub.V1_0.HubAppInfo; +import android.hardware.contexthub.V1_0.Result; import android.hardware.location.ContextHubInfo; +import android.hardware.location.ContextHubTransaction; import android.hardware.location.NanoAppBinary; import android.hardware.location.NanoAppMessage; import android.hardware.location.NanoAppState; @@ -34,6 +38,9 @@ import java.util.ArrayList; */ /* package */ class ContextHubServiceUtil { private static final String TAG = "ContextHubServiceUtil"; + private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE; + private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '" + + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware"; /** * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects. @@ -165,4 +172,40 @@ import java.util.ArrayList; message.appName, message.msgType, messageArray, message.hostEndPoint == HostEndPoint.BROADCAST); } + + /** + * Checks for location hardware permissions. + * + * @param context the context of the service + */ + /* package */ + static void checkPermissions(Context context) { + context.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE); + } + + /** + * Helper function to convert from the HAL Result enum error code to the + * ContextHubTransaction.Result type. + * + * @param halResult the Result enum error code + * @return the ContextHubTransaction.Result equivalent + */ + @ContextHubTransaction.Result + /* package */ + static int toTransactionResult(int halResult) { + switch (halResult) { + case Result.OK: + return ContextHubTransaction.TRANSACTION_SUCCESS; + case Result.BAD_PARAMS: + return ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS; + case Result.NOT_INIT: + return ContextHubTransaction.TRANSACTION_FAILED_UNINITIALIZED; + case Result.TRANSACTION_PENDING: + return ContextHubTransaction.TRANSACTION_FAILED_PENDING; + case Result.TRANSACTION_FAILED: + case Result.UNKNOWN_FAILURE: + default: /* fall through */ + return ContextHubTransaction.TRANSACTION_FAILED_UNKNOWN; + } + } } diff --git a/services/core/java/com/android/server/location/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/ContextHubTransactionManager.java index 898b76c9d8208..47d9d5686140a 100644 --- a/services/core/java/com/android/server/location/ContextHubTransactionManager.java +++ b/services/core/java/com/android/server/location/ContextHubTransactionManager.java @@ -57,6 +57,11 @@ import java.util.concurrent.atomic.AtomicInteger; */ private final IContexthub mContextHubProxy; + /* + * The manager for all clients for the service. + */ + private final ContextHubClientManager mClientManager; + /* * A queue containing the current transactions */ @@ -73,8 +78,10 @@ import java.util.concurrent.atomic.AtomicInteger; private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1); private ScheduledFuture mTimeoutFuture = null; - /* package */ ContextHubTransactionManager(IContexthub contextHubProxy) { + /* package */ ContextHubTransactionManager( + IContexthub contextHubProxy, ContextHubClientManager clientManager) { mContextHubProxy = contextHubProxy; + mClientManager = clientManager; } /** @@ -113,6 +120,9 @@ import java.util.concurrent.atomic.AtomicInteger; /* package */ void onTransactionComplete(int result) { try { onCompleteCallback.onTransactionComplete(result); + if (result == Result.OK) { + mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId()); + } } catch (RemoteException e) { Log.e(TAG, "RemoteException while calling client onTransactionComplete"); } @@ -153,6 +163,9 @@ import java.util.concurrent.atomic.AtomicInteger; /* package */ void onTransactionComplete(int result) { try { onCompleteCallback.onTransactionComplete(result); + if (result == Result.OK) { + mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId); + } } catch (RemoteException e) { Log.e(TAG, "RemoteException while calling client onTransactionComplete"); }