Move ContextHubService to a better location.
Move ContextHub service from system core to a more appropriate place
for a service.
Test: GTS tests pass.
Change-Id: Ie0f25414fc472a0214c0dd94e7ad4564cd38f842
diff --git a/services/core/java/com/android/server/ContextHubSystemService.java b/services/core/java/com/android/server/ContextHubSystemService.java
index 1b85632..5d4317c 100644
--- a/services/core/java/com/android/server/ContextHubSystemService.java
+++ b/services/core/java/com/android/server/ContextHubSystemService.java
@@ -16,7 +16,7 @@
package com.android.server;
-import android.hardware.location.ContextHubService;
+import com.android.server.location.ContextHubService;
import android.content.Context;
import android.util.Log;
@@ -37,7 +37,7 @@
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
Log.d(TAG, "onBootPhase: PHASE_SYSTEM_SERVICES_READY");
- publishBinderService(ContextHubService.CONTEXTHUB_SERVICE, mContextHubService);
+ publishBinderService(Context.CONTEXTHUB_SERVICE, mContextHubService);
}
}
}
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
new file mode 100644
index 0000000..30c4f3b
--- /dev/null
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -0,0 +1,416 @@
+/*
+ * 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 com.android.server.location;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubManager;
+import android.hardware.location.ContextHubMessage;
+import android.hardware.location.IContextHubService;
+import android.hardware.location.IContextHubCallback;
+import android.hardware.location.NanoAppFilter;
+import android.hardware.location.NanoApp;
+import android.hardware.location.NanoAppInstanceInfo;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @hide
+ */
+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";
+
+
+ public static final int ANY_HUB = -1;
+ public static final int MSG_LOAD_NANO_APP = 3;
+ public static final int MSG_UNLOAD_NANO_APP = 4;
+
+ private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown";
+ private static final String PRE_LOADED_APP_NAME = PRE_LOADED_GENERIC_UNKNOWN;
+ private static final String PRE_LOADED_APP_PUBLISHER = PRE_LOADED_GENERIC_UNKNOWN;
+ private static final int PRE_LOADED_APP_MEM_REQ = 0;
+
+ private static final int MSG_HEADER_SIZE = 4;
+ private static final int HEADER_FIELD_MSG_TYPE = 0;
+ private static final int HEADER_FIELD_MSG_VERSION = 1;
+ private static final int HEADER_FIELD_HUB_HANDLE = 2;
+ private static final int HEADER_FIELD_APP_INSTANCE = 3;
+
+ private static final int HEADER_FIELD_LOAD_APP_ID_LO = MSG_HEADER_SIZE;
+ private static final int HEADER_FIELD_LOAD_APP_ID_HI = MSG_HEADER_SIZE + 1;
+ private static final int MSG_LOAD_APP_HEADER_SIZE = MSG_HEADER_SIZE + 2;
+
+ private static final int OS_APP_INSTANCE = -1;
+
+ private static final long APP_ID_ACTIVITY_RECOGNITION = 0x476f6f676c001000L;
+
+ private final Context mContext;
+ private final ConcurrentHashMap<Integer, NanoAppInstanceInfo> mNanoAppHash =
+ new ConcurrentHashMap<>();
+ private final ContextHubInfo[] mContextHubInfo;
+ private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
+ new RemoteCallbackList<>();
+
+ private native int nativeSendMessage(int[] header, byte[] data);
+ private native ContextHubInfo[] nativeInitialize();
+
+ private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+ @Override
+ public void onVrStateChanged(boolean enabled) {
+ for (NanoAppInstanceInfo app : mNanoAppHash.values()) {
+ if (app.getAppId() == APP_ID_ACTIVITY_RECOGNITION) {
+ sendVrStateChangeMessageToApp(app, enabled);
+ break;
+ }
+ }
+ }
+ };
+
+ public ContextHubService(Context context) {
+ mContext = context;
+ mContextHubInfo = nativeInitialize();
+
+ for (int i = 0; i < mContextHubInfo.length; i++) {
+ Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
+ + ", name: " + mContextHubInfo[i].getName());
+ }
+
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_VR_MODE)) {
+ IVrManager vrManager =
+ IVrManager.Stub.asInterface(ServiceManager.getService("vrmanager"));
+ if (vrManager != null) {
+ try {
+ vrManager.registerListener(mVrStateCallbacks);
+ } catch (RemoteException e) {
+ Log.e(TAG, "VR state listener registration failed", e);
+ }
+ }
+ }
+ }
+
+ @Override
+ public int registerCallback(IContextHubCallback callback) throws RemoteException {
+ checkPermissions();
+ mCallbacksList.register(callback);
+ return 0;
+ }
+
+ @Override
+ public int[] getContextHubHandles() throws RemoteException {
+ checkPermissions();
+ int[] returnArray = new int[mContextHubInfo.length];
+
+ for (int i = 0; i < returnArray.length; ++i) {
+ returnArray[i] = i;
+ Log.d(TAG, String.format("Hub %s is mapped to %d",
+ mContextHubInfo[i].getName(), returnArray[i]));
+ }
+
+ return returnArray;
+ }
+
+ @Override
+ public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
+ checkPermissions();
+ if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
+ return null; // null means fail
+ }
+
+ return mContextHubInfo[contextHubHandle];
+ }
+
+ // TODO(b/30808791): Remove this when NanoApp's API is correctly treating
+ // app IDs as 64-bits.
+ private static long parseAppId(NanoApp app) {
+ // NOTE: If this shifting seems odd (since it's actually "ONAN"), note
+ // that it matches how this is defined in context_hub.h.
+ final int HEADER_MAGIC =
+ (((int)'N' << 0) |
+ ((int)'A' << 8) |
+ ((int)'N' << 16) |
+ ((int)'O' << 24));
+ final int HEADER_MAGIC_OFFSET = 4;
+ final int HEADER_APP_ID_OFFSET = 8;
+
+ ByteBuffer header = ByteBuffer.wrap(app.getAppBinary())
+ .order(ByteOrder.LITTLE_ENDIAN);
+
+ try {
+ if (header.getInt(HEADER_MAGIC_OFFSET) == HEADER_MAGIC) {
+ // This is a legitimate nanoapp header. Let's grab the app ID.
+ return header.getLong(HEADER_APP_ID_OFFSET);
+ }
+ } catch (IndexOutOfBoundsException e) {
+ // The header is undersized. We'll fall through to our code
+ // path below, which handles being unable to parse the header.
+ }
+ // We failed to parse the header. Even through it's probably wrong,
+ // let's give NanoApp's idea of our ID. This is at least consistent.
+ return app.getAppId();
+ }
+
+ @Override
+ public int loadNanoApp(int contextHubHandle, NanoApp app) throws RemoteException {
+ checkPermissions();
+
+ if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
+ Log.e(TAG, "Invalid contextHubhandle " + contextHubHandle);
+ return -1;
+ }
+ if (app == null) {
+ return -1;
+ }
+
+ int[] msgHeader = new int[MSG_LOAD_APP_HEADER_SIZE];
+ msgHeader[HEADER_FIELD_HUB_HANDLE] = contextHubHandle;
+ msgHeader[HEADER_FIELD_APP_INSTANCE] = OS_APP_INSTANCE;
+ msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
+ msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_LOAD_NANO_APP;
+
+ long appId = app.getAppId();
+ // TODO(b/30808791): Remove this hack when the NanoApp API is fixed,
+ // and getAppId() returns a 'long' instead of an 'int'.
+ if ((appId >> 32) != 0) {
+ // We're unlikely to notice this warning, but at least
+ // we can avoid running our hack logic.
+ Log.w(TAG, "Code has not been updated since API fix.");
+ } else {
+ appId = parseAppId(app);
+ }
+
+ msgHeader[HEADER_FIELD_LOAD_APP_ID_LO] = (int)(appId & 0xFFFFFFFF);
+ msgHeader[HEADER_FIELD_LOAD_APP_ID_HI] = (int)((appId >> 32) & 0xFFFFFFFF);
+
+ int errVal = nativeSendMessage(msgHeader, app.getAppBinary());
+ if (errVal != 0) {
+ Log.e(TAG, "Send Message returns error" + contextHubHandle);
+ return -1;
+ }
+
+ // Do not add an entry to mNanoAppInstance Hash yet. The HAL may reject the app
+ return 0;
+ }
+
+ @Override
+ public int unloadNanoApp(int nanoAppInstanceHandle) throws RemoteException {
+ checkPermissions();
+ NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstanceHandle);
+ if (info == null) {
+ return -1; //means failed
+ }
+
+ // Call Native interface here
+ int[] msgHeader = new int[MSG_HEADER_SIZE];
+ msgHeader[HEADER_FIELD_HUB_HANDLE] = ANY_HUB;
+ msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppInstanceHandle;
+ msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
+ msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_UNLOAD_NANO_APP;
+
+ byte msg[] = new byte[0];
+
+ if (nativeSendMessage(msgHeader, msg) != 0) {
+ return -1;
+ }
+
+ // Do not add an entry to mNanoAppInstance Hash yet. The HAL may reject the app
+ return 0;
+ }
+
+ @Override
+ public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle)
+ throws RemoteException {
+ checkPermissions();
+ // This assumes that all the nanoAppInfo is current. This is reasonable
+ // for the use cases for tightly controlled nanoApps.
+ if (mNanoAppHash.containsKey(nanoAppInstanceHandle)) {
+ return mNanoAppHash.get(nanoAppInstanceHandle);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public int[] findNanoAppOnHub(int hubHandle, NanoAppFilter filter) throws RemoteException {
+ checkPermissions();
+ ArrayList<Integer> foundInstances = new ArrayList<Integer>();
+
+ for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+ NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstance);
+
+ if (filter.testMatch(info)) {
+ foundInstances.add(nanoAppInstance);
+ }
+ }
+
+ int[] retArray = new int[foundInstances.size()];
+ for (int i = 0; i < foundInstances.size(); i++) {
+ retArray[i] = foundInstances.get(i).intValue();
+ }
+
+ return retArray;
+ }
+
+ @Override
+ public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage msg)
+ throws RemoteException {
+ checkPermissions();
+
+ if (msg == null || msg.getData() == null) {
+ Log.w(TAG, "null ptr");
+ return -1;
+ }
+
+ int[] msgHeader = new int[MSG_HEADER_SIZE];
+ msgHeader[HEADER_FIELD_HUB_HANDLE] = hubHandle;
+ msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppHandle;
+ msgHeader[HEADER_FIELD_MSG_VERSION] = msg.getVersion();
+ msgHeader[HEADER_FIELD_MSG_TYPE] = msg.getMsgType();
+
+ return nativeSendMessage(msgHeader, msg.getData());
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission("android.permission.DUMP")
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump contexthub service");
+ return;
+ }
+
+ pw.println("Dumping ContextHub Service");
+
+ pw.println("");
+ // dump ContextHubInfo
+ pw.println("=================== CONTEXT HUBS ====================");
+ for (int i = 0; i < mContextHubInfo.length; i++) {
+ pw.println("Handle " + i + " : " + mContextHubInfo[i].toString());
+ }
+ pw.println("");
+ pw.println("=================== NANOAPPS ====================");
+ // Dump nanoAppHash
+ for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+ pw.println(nanoAppInstance + " : " + mNanoAppHash.get(nanoAppInstance).toString());
+ }
+
+ // dump eventLog
+ }
+
+ private void checkPermissions() {
+ mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+ }
+
+ private int onMessageReceipt(int[] header, byte[] data) {
+ if (header == null || data == null || header.length < MSG_HEADER_SIZE) {
+ return -1;
+ }
+ int callbacksCount = mCallbacksList.beginBroadcast();
+ if (callbacksCount < 1) {
+ Log.v(TAG, "No message callbacks registered.");
+ return 0;
+ }
+
+ ContextHubMessage msg = new ContextHubMessage(header[HEADER_FIELD_MSG_TYPE],
+ header[HEADER_FIELD_MSG_VERSION],
+ data);
+ for (int i = 0; i < callbacksCount; ++i) {
+ IContextHubCallback callback = mCallbacksList.getBroadcastItem(i);
+ try {
+ callback.onMessageReceipt(
+ header[HEADER_FIELD_HUB_HANDLE],
+ header[HEADER_FIELD_APP_INSTANCE],
+ msg);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Exception (" + e + ") calling remote callback (" + callback + ").");
+ continue;
+ }
+ }
+ mCallbacksList.finishBroadcast();
+ return 0;
+ }
+
+ private int addAppInstance(int hubHandle, int appInstanceHandle, long appId, int appVersion) {
+ // App Id encodes vendor & version
+ NanoAppInstanceInfo appInfo = new NanoAppInstanceInfo();
+
+ appInfo.setAppId(appId);
+ appInfo.setAppVersion(appVersion);
+ appInfo.setName(PRE_LOADED_APP_NAME);
+ appInfo.setContexthubId(hubHandle);
+ appInfo.setHandle(appInstanceHandle);
+ appInfo.setPublisher(PRE_LOADED_APP_PUBLISHER);
+ appInfo.setNeededExecMemBytes(PRE_LOADED_APP_MEM_REQ);
+ appInfo.setNeededReadMemBytes(PRE_LOADED_APP_MEM_REQ);
+ appInfo.setNeededWriteMemBytes(PRE_LOADED_APP_MEM_REQ);
+
+ String action;
+ if (mNanoAppHash.containsKey(appInstanceHandle)) {
+ action = "Updated";
+ } else {
+ action = "Added";
+ }
+
+ mNanoAppHash.put(appInstanceHandle, appInfo);
+ Log.d(TAG, action + " app instance " + appInstanceHandle + " with id "
+ + appId + " version " + appVersion);
+
+ return 0;
+ }
+
+ private int deleteAppInstance(int appInstanceHandle) {
+ if (mNanoAppHash.remove(appInstanceHandle) == null) {
+ return -1;
+ }
+
+ return 0;
+ }
+
+ private void sendVrStateChangeMessageToApp(NanoAppInstanceInfo app, boolean vrModeEnabled) {
+ int[] msgHeader = new int[MSG_HEADER_SIZE];
+ msgHeader[HEADER_FIELD_MSG_TYPE] = 0;
+ msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
+ msgHeader[HEADER_FIELD_HUB_HANDLE] = ANY_HUB;
+ msgHeader[HEADER_FIELD_APP_INSTANCE] = app.getHandle();
+
+ byte[] data = new byte[1];
+ data[0] = (byte) ((vrModeEnabled) ? 1 : 0);
+ int ret = nativeSendMessage(msgHeader, data);
+ if (ret != 0) {
+ Log.e(TAG, "Couldn't send VR state change notification (" + ret + ")!");
+ }
+ }
+}
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 3e8e420..b340213 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -15,6 +15,7 @@
$(LOCAL_REL_DIR)/com_android_server_input_InputManagerService.cpp \
$(LOCAL_REL_DIR)/com_android_server_input_InputWindowHandle.cpp \
$(LOCAL_REL_DIR)/com_android_server_lights_LightsService.cpp \
+ $(LOCAL_REL_DIR)/com_android_server_location_ContextHubService.cpp \
$(LOCAL_REL_DIR)/com_android_server_location_GnssLocationProvider.cpp \
$(LOCAL_REL_DIR)/com_android_server_location_FlpHardwareProvider.cpp \
$(LOCAL_REL_DIR)/com_android_server_power_PowerManagerService.cpp \
diff --git a/services/core/jni/com_android_server_location_ContextHubService.cpp b/services/core/jni/com_android_server_location_ContextHubService.cpp
new file mode 100644
index 0000000..762aec8
--- /dev/null
+++ b/services/core/jni/com_android_server_location_ContextHubService.cpp
@@ -0,0 +1,1227 @@
+/*
+ * Copyright 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.
+ */
+
+#include "context_hub.h"
+
+#undef LOG_NDEBUG
+#undef LOG_TAG
+#define LOG_NDEBUG 0
+#define LOG_TAG "ContextHubService"
+
+#include <inttypes.h>
+#include <jni.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <android-base/macros.h>
+#include <cutils/log.h>
+
+#include <chrono>
+#include <mutex>
+#include <queue>
+#include <unordered_map>
+
+#include "JNIHelp.h"
+#include "core_jni_helpers.h"
+
+static constexpr jint OS_APP_ID = -1;
+static constexpr jint INVALID_APP_ID = -2;
+static constexpr uint64_t ALL_APPS = UINT64_C(0xFFFFFFFFFFFFFFFF);
+
+static constexpr jint MIN_APP_ID = 1;
+static constexpr jint MAX_APP_ID = 128;
+
+static constexpr size_t MSG_HEADER_SIZE = 4;
+static constexpr size_t HEADER_FIELD_MSG_TYPE = 0;
+static constexpr size_t HEADER_FIELD_MSG_VERSION = 1;
+static constexpr size_t HEADER_FIELD_HUB_HANDLE = 2;
+static constexpr size_t HEADER_FIELD_APP_INSTANCE = 3;
+
+static constexpr size_t HEADER_FIELD_LOAD_APP_ID_LO = MSG_HEADER_SIZE;
+static constexpr size_t HEADER_FIELD_LOAD_APP_ID_HI = MSG_HEADER_SIZE + 1;
+static constexpr size_t MSG_HEADER_SIZE_LOAD_APP = MSG_HEADER_SIZE + 2;
+
+// Monotonically increasing clock we use to determine if we can cancel
+// a transaction.
+using std::chrono::steady_clock;
+
+namespace android {
+
+namespace {
+
+/*
+ * Finds the length of a statically-sized array using template trickery that
+ * also prevents it from being applied to the wrong type.
+ */
+template <typename T, size_t N>
+constexpr size_t array_length(T (&)[N]) { return N; }
+
+struct jniInfo_s {
+ JavaVM *vm;
+ jclass contextHubInfoClass;
+ jclass contextHubServiceClass;
+ jclass memoryRegionsClass;
+
+ jobject jContextHubService;
+
+ jmethodID msgReceiptCallBack;
+
+ jmethodID contextHubInfoCtor;
+ jmethodID contextHubInfoSetId;
+ jmethodID contextHubInfoSetName;
+ jmethodID contextHubInfoSetVendor;
+ jmethodID contextHubInfoSetToolchain;
+ jmethodID contextHubInfoSetPlatformVersion;
+ jmethodID contextHubInfoSetStaticSwVersion;
+ jmethodID contextHubInfoSetToolchainVersion;
+ jmethodID contextHubInfoSetPeakMips;
+ jmethodID contextHubInfoSetStoppedPowerDrawMw;
+ jmethodID contextHubInfoSetSleepPowerDrawMw;
+ jmethodID contextHubInfoSetPeakPowerDrawMw;
+ jmethodID contextHubInfoSetSupportedSensors;
+ jmethodID contextHubInfoSetMemoryRegions;
+ jmethodID contextHubInfoSetMaxPacketLenBytes;
+
+ jmethodID contextHubServiceMsgReceiptCallback;
+ jmethodID contextHubServiceAddAppInstance;
+ jmethodID contextHubServiceDeleteAppInstance;
+};
+
+struct context_hub_info_s {
+ uint32_t *cookies;
+ int numHubs;
+ const struct context_hub_t *hubs;
+ struct context_hub_module_t *contextHubModule;
+};
+
+struct app_instance_info_s {
+ uint64_t truncName; // Possibly truncated name for logging
+ uint32_t hubHandle; // Id of the hub this app is on
+ jint instanceId; // system wide unique instance id - assigned
+ struct hub_app_info appInfo; // returned from the HAL
+};
+
+
+// If a transaction takes longer than this, we'll allow it to be
+// canceled by a new transaction. Note we do _not_ automatically
+// cancel a transaction after this much time. We can have a
+// legal transaction which takes longer than this amount of time,
+// as long as no other new transactions are attempted after this
+// time has expired.
+// TODO(b/31105001): Establish a clean timing approach for all
+// of our HAL interactions.
+constexpr auto kMinTransactionCancelTime = std::chrono::seconds(29);
+
+/*
+ * TODO(ashutoshj): From original code review:
+ *
+ * So, I feel like we could possible do a better job of organizing this code,
+ * and being more C++-y. Consider something like this:
+ * class TxnManager {
+ * public:
+ * TxnManager();
+ * ~TxnManager();
+ * int add(hub_message_e identifier, void *data);
+ * int close();
+ * bool isPending() const;
+ * int fetchData(hub_message_e *identifier, void **data) const;
+ *
+ * private:
+ * bool mPending;
+ * mutable std::mutex mLock;
+ * hub_message_e mIdentifier;
+ * void *mData;
+ * };
+ *
+ * And then, for example, we'd have things like:
+ * TxnManager::TxnManager() : mPending(false), mLock(), mIdentifier(), mData(nullptr) {}
+ * int TxnManager::add(hub_message_e identifier, void *data) {
+ * std::lock_guard<std::mutex> lock(mLock);
+ * mPending = true;
+ * mData = txnData;
+ * mIdentifier = txnIdentifier;
+ * return 0;
+ * }
+ * And then calling code would look like:
+ * if (!db.txnManager.add(CONTEXT_HUB_LOAD_APP, txnInfo)) {
+ *
+ * This would make it clearer the nothing is manipulating any state within TxnManager
+ * unsafely and outside of these couple of calls.
+ */
+struct txnManager_s {
+ bool txnPending; // Is a transaction pending
+ std::mutex m; // mutex for manager
+ hub_messages_e txnIdentifier; // What are we doing
+ void *txnData; // Details
+ steady_clock::time_point firstTimeTxnCanBeCanceled;
+};
+
+struct contextHubServiceDb_s {
+ int initialized;
+ context_hub_info_s hubInfo;
+ jniInfo_s jniInfo;
+ std::queue<jint> freeIds;
+ std::unordered_map<jint, app_instance_info_s> appInstances;
+ txnManager_s txnManager;
+};
+
+} // unnamed namespace
+
+static contextHubServiceDb_s db;
+
+static bool initTxnManager() {
+ txnManager_s *mgr = &db.txnManager;
+
+ mgr->txnData = nullptr;
+ mgr->txnPending = false;
+ return true;
+}
+
+static int addTxn(hub_messages_e txnIdentifier, void *txnData) {
+ txnManager_s *mgr = &db.txnManager;
+
+ std::lock_guard<std::mutex>lock(mgr->m);
+
+ mgr->txnPending = true;
+ mgr->firstTimeTxnCanBeCanceled = steady_clock::now() +
+ kMinTransactionCancelTime;
+ mgr->txnData = txnData;
+ mgr->txnIdentifier = txnIdentifier;
+
+ return 0;
+}
+
+// Only call this if you hold the db.txnManager.m lock.
+static void closeTxnUnlocked() {
+ txnManager_s *mgr = &db.txnManager;
+ mgr->txnPending = false;
+ free(mgr->txnData);
+ mgr->txnData = nullptr;
+}
+
+static int closeTxn() {
+ std::lock_guard<std::mutex>lock(db.txnManager.m);
+ closeTxnUnlocked();
+ return 0;
+}
+
+// If a transaction has been pending for longer than
+// kMinTransactionCancelTime, this call will "cancel" that
+// transaction and return that there are none pending.
+static bool isTxnPending() {
+ txnManager_s *mgr = &db.txnManager;
+ std::lock_guard<std::mutex>lock(mgr->m);
+ if (mgr->txnPending) {
+ if (steady_clock::now() >= mgr->firstTimeTxnCanBeCanceled) {
+ ALOGW("Transaction canceled");
+ closeTxnUnlocked();
+ }
+ }
+ return mgr->txnPending;
+}
+
+static int fetchTxnData(hub_messages_e *id, void **data) {
+ txnManager_s *mgr = &db.txnManager;
+
+ if (!id || !data) {
+ ALOGW("Null params id %p, data %p", id, data);
+ return -1;
+ }
+
+ std::lock_guard<std::mutex>lock(mgr->m);
+ if (!mgr->txnPending) {
+ ALOGW("No Transactions pending");
+ return -1;
+ }
+
+ // else
+ *id = mgr->txnIdentifier;
+ *data = mgr->txnData;
+ return 0;
+}
+
+int context_hub_callback(uint32_t hubId, const struct hub_message_t *msg,
+ void *cookie);
+
+const context_hub_t *get_hub_info(int hubHandle) {
+ if (hubHandle >= 0 && hubHandle < db.hubInfo.numHubs) {
+ return &db.hubInfo.hubs[hubHandle];
+ }
+ return nullptr;
+}
+
+static int send_msg_to_hub(const hub_message_t *msg, int hubHandle) {
+ const context_hub_t *info = get_hub_info(hubHandle);
+
+ if (info) {
+ return db.hubInfo.contextHubModule->send_message(info->hub_id, msg);
+ } else {
+ ALOGD("%s: Hub information is null for hubHandle %d", __FUNCTION__, hubHandle);
+ return -1;
+ }
+}
+
+static int set_os_app_as_destination(hub_message_t *msg, int hubHandle) {
+ const context_hub_t *info = get_hub_info(hubHandle);
+
+ if (info) {
+ msg->app_name = info->os_app_name;
+ return 0;
+ } else {
+ ALOGD("%s: Hub information is null for hubHandle %d", __FUNCTION__, hubHandle);
+ return -1;
+ }
+}
+
+static int get_hub_id_for_hub_handle(int hubHandle) {
+ if (hubHandle < 0 || hubHandle >= db.hubInfo.numHubs) {
+ return -1;
+ } else {
+ return db.hubInfo.hubs[hubHandle].hub_id;
+ }
+}
+
+static int get_hub_handle_for_app_instance(jint id) {
+ if (!db.appInstances.count(id)) {
+ ALOGD("%s: Cannot find app for app instance %" PRId32,
+ __FUNCTION__, id);
+ return -1;
+ }
+
+ return db.appInstances[id].hubHandle;
+}
+
+static int get_hub_id_for_app_instance(jint id) {
+ int hubHandle = get_hub_handle_for_app_instance(id);
+
+ if (hubHandle < 0) {
+ return -1;
+ }
+
+ return db.hubInfo.hubs[hubHandle].hub_id;
+}
+
+static jint get_app_instance_for_app_id(uint64_t app_id) {
+ auto end = db.appInstances.end();
+ for (auto current = db.appInstances.begin(); current != end; ++current) {
+ if (current->second.appInfo.app_name.id == app_id) {
+ return current->first;
+ }
+ }
+ ALOGD("Cannot find app for app instance %" PRIu64 ".", app_id);
+ return -1;
+}
+
+static int set_dest_app(hub_message_t *msg, jint id) {
+ if (!db.appInstances.count(id)) {
+ ALOGD("%s: Cannot find app for app instance %" PRId32,
+ __FUNCTION__, id);
+ return -1;
+ }
+
+ msg->app_name = db.appInstances[id].appInfo.app_name;
+ return 0;
+}
+
+static void query_hub_for_apps(uint32_t hubHandle) {
+ hub_message_t msg;
+ query_apps_request_t queryMsg;
+
+ // TODO(b/30835598): When we're able to tell which request our
+ // response matches, then we should allow this to be more
+ // targetted, instead of always being every app in the
+ // system.
+ queryMsg.app_name.id = ALL_APPS;
+
+ msg.message_type = CONTEXT_HUB_QUERY_APPS;
+ msg.message_len = sizeof(queryMsg);
+ msg.message = &queryMsg;
+
+ ALOGD("Sending query for apps to hub %" PRIu32, hubHandle);
+ set_os_app_as_destination(&msg, hubHandle);
+ if (send_msg_to_hub(&msg, hubHandle) != 0) {
+ ALOGW("Could not query hub %" PRIu32 " for apps", hubHandle);
+ }
+}
+
+static void sendQueryForApps() {
+ for (int i = 0; i < db.hubInfo.numHubs; i++ ) {
+ query_hub_for_apps(i);
+ }
+}
+
+static int return_id(jint id) {
+ // Note : This method is not thread safe.
+ // id returned is guaranteed to be in use
+ if (id >= 0) {
+ db.freeIds.push(id);
+ return 0;
+ }
+
+ return -1;
+}
+
+static jint generate_id() {
+ // Note : This method is not thread safe.
+ jint retVal = -1;
+
+ if (!db.freeIds.empty()) {
+ retVal = db.freeIds.front();
+ db.freeIds.pop();
+ }
+
+ return retVal;
+}
+
+
+static jint add_app_instance(const hub_app_info *appInfo, uint32_t hubHandle,
+ jint appInstanceHandle, JNIEnv *env) {
+ // Not checking if the apps are indeed distinct
+ app_instance_info_s entry;
+ assert(appInfo);
+
+ const char *action =
+ (db.appInstances.count(appInstanceHandle) == 0) ? "Added" : "Updated";
+
+ entry.appInfo = *appInfo;
+
+ entry.instanceId = appInstanceHandle;
+ entry.truncName = appInfo->app_name.id;
+ entry.hubHandle = hubHandle;
+
+ db.appInstances[appInstanceHandle] = entry;
+
+ // Finally - let the service know of this app instance, to populate
+ // the Java cache.
+ env->CallIntMethod(db.jniInfo.jContextHubService,
+ db.jniInfo.contextHubServiceAddAppInstance,
+ hubHandle, entry.instanceId, entry.truncName,
+ entry.appInfo.version);
+
+ ALOGI("%s App 0x%" PRIx64 " on hub Handle %" PRId32
+ " as appInstance %" PRId32, action, entry.truncName,
+ entry.hubHandle, appInstanceHandle);
+
+ return appInstanceHandle;
+}
+
+int delete_app_instance(jint id, JNIEnv *env) {
+ bool fullyDeleted = true;
+
+ if (db.appInstances.count(id)) {
+ db.appInstances.erase(id);
+ } else {
+ ALOGW("Cannot delete App id (%" PRId32 ") from the JNI C++ cache", id);
+ fullyDeleted = false;
+ }
+ return_id(id);
+
+ if ((env == nullptr) ||
+ (env->CallIntMethod(db.jniInfo.jContextHubService,
+ db.jniInfo.contextHubServiceDeleteAppInstance,
+ id) != 0)) {
+ ALOGW("Cannot delete App id (%" PRId32 ") from Java cache", id);
+ fullyDeleted = false;
+ }
+
+ if (fullyDeleted) {
+ ALOGI("Deleted App id : %" PRId32, id);
+ return 0;
+ }
+ return -1;
+}
+
+static int startLoadAppTxn(uint64_t appId, int hubHandle) {
+ app_instance_info_s *txnInfo = (app_instance_info_s *)malloc(sizeof(app_instance_info_s));
+ jint instanceId = generate_id();
+
+ if (!txnInfo || instanceId < 0) {
+ return_id(instanceId);
+ free(txnInfo);
+ return -1;
+ }
+
+ txnInfo->truncName = appId;
+ txnInfo->hubHandle = hubHandle;
+ txnInfo->instanceId = instanceId;
+
+ txnInfo->appInfo.app_name.id = appId;
+ txnInfo->appInfo.num_mem_ranges = 0;
+ txnInfo->appInfo.version = -1; // Awaited
+
+ if (addTxn(CONTEXT_HUB_LOAD_APP, txnInfo) != 0) {
+ return_id(instanceId);
+ free(txnInfo);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int startUnloadAppTxn(jint appInstanceHandle) {
+ jint *txnData = (jint *) malloc(sizeof(jint));
+ if (!txnData) {
+ ALOGW("Cannot allocate memory to start unload transaction");
+ return -1;
+ }
+
+ *txnData = appInstanceHandle;
+
+ if (addTxn(CONTEXT_HUB_UNLOAD_APP, txnData) != 0) {
+ free(txnData);
+ ALOGW("Cannot start transaction to unload app");
+ return -1;
+ }
+
+ return 0;
+}
+
+static void initContextHubService() {
+ int err = 0;
+ db.hubInfo.hubs = nullptr;
+ db.hubInfo.numHubs = 0;
+
+ err = hw_get_module(CONTEXT_HUB_MODULE_ID,
+ (hw_module_t const**)(&db.hubInfo.contextHubModule));
+
+ if (err) {
+ ALOGE("** Could not load %s module : err %s", CONTEXT_HUB_MODULE_ID,
+ strerror(-err));
+ }
+
+ // Prep for storing app info
+ for (jint i = MIN_APP_ID; i <= MAX_APP_ID; i++) {
+ db.freeIds.push(i);
+ }
+
+ initTxnManager();
+ if (db.hubInfo.contextHubModule) {
+ int retNumHubs = db.hubInfo.contextHubModule->get_hubs(db.hubInfo.contextHubModule,
+ &db.hubInfo.hubs);
+ ALOGD("ContextHubModule returned %d hubs ", retNumHubs);
+ db.hubInfo.numHubs = retNumHubs;
+
+ if (db.hubInfo.numHubs > 0) {
+ db.hubInfo.numHubs = retNumHubs;
+ db.hubInfo.cookies = (uint32_t *)malloc(sizeof(uint32_t) * db.hubInfo.numHubs);
+
+ if (!db.hubInfo.cookies) {
+ ALOGW("Ran out of memory allocating cookies, bailing");
+ return;
+ }
+
+ for (int i = 0; i < db.hubInfo.numHubs; i++) {
+ db.hubInfo.cookies[i] = db.hubInfo.hubs[i].hub_id;
+ ALOGI("Subscribing to hubHandle %d with OS App name %" PRIu64, i, db.hubInfo.hubs[i].os_app_name.id);
+ if (db.hubInfo.contextHubModule->subscribe_messages(db.hubInfo.hubs[i].hub_id,
+ context_hub_callback,
+ &db.hubInfo.cookies[i]) == 0) {
+ }
+ }
+ }
+
+ sendQueryForApps();
+ } else {
+ ALOGW("No Context Hub Module present");
+ }
+}
+
+static int onMessageReceipt(uint32_t *header, size_t headerLen, char *msg, size_t msgLen) {
+ JNIEnv *env;
+
+ if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
+ return -1;
+ }
+
+ jbyteArray jmsg = env->NewByteArray(msgLen);
+ if (jmsg == nullptr) {
+ ALOGW("Can't allocate %zu byte array", msgLen);
+ return -1;
+ }
+ jintArray jheader = env->NewIntArray(headerLen);
+ if (jheader == nullptr) {
+ env->DeleteLocalRef(jmsg);
+ ALOGW("Can't allocate %zu int array", headerLen);
+ return -1;
+ }
+
+ env->SetByteArrayRegion(jmsg, 0, msgLen, (jbyte *)msg);
+ env->SetIntArrayRegion(jheader, 0, headerLen, (jint *)header);
+
+ int ret = (env->CallIntMethod(db.jniInfo.jContextHubService,
+ db.jniInfo.contextHubServiceMsgReceiptCallback,
+ jheader, jmsg) != 0);
+ env->DeleteLocalRef(jmsg);
+ env->DeleteLocalRef(jheader);
+
+ return ret;
+}
+
+int handle_query_apps_response(const uint8_t *msg, int msgLen,
+ uint32_t hubHandle) {
+ JNIEnv *env;
+ if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
+ return -1;
+ }
+
+ int numApps = msgLen / sizeof(hub_app_info);
+ const hub_app_info *unalignedInfoAddr = (const hub_app_info*)msg;
+
+ // We use this information to sync our JNI and Java caches of nanoapp info.
+ // We want to accomplish two things here:
+ // 1) Remove entries from our caches which are stale, and pertained to
+ // apps no longer running on Context Hub.
+ // 2) Populate our caches with the latest information of all these apps.
+
+ // We make a couple of assumptions here:
+ // A) The JNI and Java caches are in sync with each other (this isn't
+ // necessarily true; any failure of a single call into Java land to
+ // update its cache will leave that cache in a bad state. For NYC,
+ // we're willing to tolerate this for now).
+ // B) The total number of apps is relatively small, so horribly inefficent
+ // algorithms aren't too painful.
+ // C) We're going to call this relatively infrequently, so its inefficency
+ // isn't a big impact.
+
+
+ // (1). Looking for stale cache entries. Yes, this is O(N^2). See
+ // assumption (B). Per assumption (A), it is sufficient to iterate
+ // over just the JNI cache.
+ auto end = db.appInstances.end();
+ for (auto current = db.appInstances.begin(); current != end; ) {
+ app_instance_info_s cache_entry = current->second;
+ // We perform our iteration here because if we call
+ // delete_app_instance() below, it will erase() this entry.
+ current++;
+ bool entryIsStale = true;
+ for (int i = 0; i < numApps; i++) {
+ // We use memcmp since this could be unaligned.
+ if (memcmp(&unalignedInfoAddr[i].app_name.id,
+ &cache_entry.appInfo.app_name.id,
+ sizeof(cache_entry.appInfo.app_name.id)) == 0) {
+ // We found a match; this entry is current.
+ entryIsStale = false;
+ break;
+ }
+ }
+ if (entryIsStale) {
+ delete_app_instance(cache_entry.instanceId, env);
+ }
+ }
+
+ // (2). Update our caches with the latest.
+ for (int i = 0; i < numApps; i++) {
+ hub_app_info query_info;
+ memcpy(&query_info, &unalignedInfoAddr[i], sizeof(query_info));
+ // We will only have one instance of the app
+ // TODO : Change this logic once we support multiple instances of the same app
+ jint appInstance = get_app_instance_for_app_id(query_info.app_name.id);
+ if (appInstance == -1) {
+ // This is a previously unknown app, let's allocate an "id" for it.
+ appInstance = generate_id();
+ }
+ add_app_instance(&query_info, hubHandle, appInstance, env);
+ }
+
+ return 0;
+}
+
+// TODO(b/30807327): Do not use raw bytes for additional data. Use the
+// JNI interfaces for the appropriate types.
+static void passOnOsResponse(uint32_t hubHandle, uint32_t msgType,
+ status_response_t *rsp, int8_t *additionalData,
+ size_t additionalDataLen) {
+ JNIEnv *env;
+
+ if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
+ ALOGW("Cannot latch to JNI env, dropping OS response %" PRIu32, msgType);
+ return;
+ }
+
+ uint32_t header[MSG_HEADER_SIZE];
+ memset(header, 0, sizeof(header));
+
+ if (!additionalData) {
+ additionalDataLen = 0; // clamp
+ }
+ int msgLen = 1 + additionalDataLen;
+
+ int8_t *msg = new int8_t[msgLen];
+
+ if (!msg) {
+ ALOGW("Unexpected : Ran out of memory, cannot send response");
+ return;
+ }
+
+ header[HEADER_FIELD_MSG_TYPE] = msgType;
+ header[HEADER_FIELD_MSG_VERSION] = 0;
+ header[HEADER_FIELD_HUB_HANDLE] = hubHandle;
+ header[HEADER_FIELD_APP_INSTANCE] = OS_APP_ID;
+
+ // Due to API constraints, at the moment we can't change the fact that
+ // we're changing our 4-byte response to a 1-byte value. But we can prevent
+ // the possible change in sign (and thus meaning) that would happen from
+ // a naive cast. Further, we can log when we're losing part of the value.
+ // TODO(b/30918279): Don't truncate this result.
+ int8_t truncatedResult;
+ bool neededToTruncate;
+ if (rsp->result < INT8_MIN) {
+ neededToTruncate = true;
+ truncatedResult = INT8_MIN;
+ } else if (rsp->result > INT8_MAX) {
+ neededToTruncate = true;
+ truncatedResult = INT8_MAX;
+ } else {
+ neededToTruncate = false;
+ // Since this value fits within an int8_t, this is a safe cast which
+ // won't change the value or sign.
+ truncatedResult = static_cast<int8_t>(rsp->result);
+ }
+ if (neededToTruncate) {
+ ALOGW("Response from Context Hub truncated. Value was %" PRId32
+ ", but giving Java layer %" PRId8,
+ rsp->result, (int)truncatedResult);
+ }
+
+ msg[0] = truncatedResult;
+
+ if (additionalData) {
+ memcpy(&msg[1], additionalData, additionalDataLen);
+ }
+
+ jbyteArray jmsg = env->NewByteArray(msgLen);
+ jintArray jheader = env->NewIntArray(arraysize(header));
+
+ env->SetByteArrayRegion(jmsg, 0, msgLen, (jbyte *)msg);
+ env->SetIntArrayRegion(jheader, 0, arraysize(header), (jint *)header);
+
+ ALOGI("Passing msg type %" PRIu32 " from app %" PRIu32 " from hub %" PRIu32,
+ header[HEADER_FIELD_MSG_TYPE], header[HEADER_FIELD_APP_INSTANCE],
+ header[HEADER_FIELD_HUB_HANDLE]);
+
+ env->CallIntMethod(db.jniInfo.jContextHubService,
+ db.jniInfo.contextHubServiceMsgReceiptCallback,
+ jheader, jmsg);
+ env->DeleteLocalRef(jmsg);
+ env->DeleteLocalRef(jheader);
+
+ delete[] msg;
+}
+
+void closeUnloadTxn(bool success) {
+ void *txnData = nullptr;
+ hub_messages_e txnId;
+
+ if (success && fetchTxnData(&txnId, &txnData) == 0 &&
+ txnId == CONTEXT_HUB_UNLOAD_APP) {
+ JNIEnv *env;
+ if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
+ ALOGW("Could not attach to JVM !");
+ env = nullptr;
+ }
+ jint handle = *reinterpret_cast<jint *>(txnData);
+ delete_app_instance(handle, env);
+ } else {
+ ALOGW("Could not unload the app successfully ! success %d, txnData %p", success, txnData);
+ }
+
+ closeTxn();
+}
+
+static bool closeLoadTxn(bool success, jint *appInstanceHandle) {
+ void *txnData;
+ hub_messages_e txnId;
+
+ if (success && fetchTxnData(&txnId, &txnData) == 0 &&
+ txnId == CONTEXT_HUB_LOAD_APP) {
+ app_instance_info_s *info = (app_instance_info_s *)txnData;
+ *appInstanceHandle = info->instanceId;
+
+ JNIEnv *env;
+ if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) == JNI_OK) {
+ add_app_instance(&info->appInfo, info->hubHandle, info->instanceId, env);
+ } else {
+ ALOGW("Could not attach to JVM !");
+ success = false;
+ }
+ // While we just called add_app_instance above, our info->appInfo was
+ // incomplete (for example, the 'version' is hardcoded to -1). So we
+ // trigger an additional query to the CHRE, so we'll be able to get
+ // all the app "info", and have our JNI and Java caches with the
+ // full information.
+ sendQueryForApps();
+ } else {
+ ALOGW("Could not load the app successfully ! Unexpected failure");
+ *appInstanceHandle = INVALID_APP_ID;
+ success = false;
+ }
+
+ closeTxn();
+ return success;
+}
+
+static bool isValidOsStatus(const uint8_t *msg, size_t msgLen,
+ status_response_t *rsp) {
+ // Workaround a bug in some HALs
+ if (msgLen == 1) {
+ rsp->result = msg[0];
+ return true;
+ }
+
+ if (!msg || msgLen != sizeof(*rsp)) {
+ ALOGW("Received invalid response %p of size %zu", msg, msgLen);
+ return false;
+ }
+
+ memcpy(rsp, msg, sizeof(*rsp));
+
+ // No sanity checks on return values
+ return true;
+}
+
+static int handle_os_message(uint32_t msgType, uint32_t hubHandle,
+ const uint8_t *msg, int msgLen) {
+ int retVal = -1;
+
+ ALOGD("Rcd OS message from hubHandle %" PRIu32 " type %" PRIu32 " length %d",
+ hubHandle, msgType, msgLen);
+
+ struct status_response_t rsp;
+
+ switch(msgType) {
+
+ case CONTEXT_HUB_APPS_ENABLE:
+ case CONTEXT_HUB_APPS_DISABLE:
+ case CONTEXT_HUB_LOAD_APP:
+ case CONTEXT_HUB_UNLOAD_APP:
+ if (isValidOsStatus(msg, msgLen, &rsp)) {
+ if (msgType == CONTEXT_HUB_LOAD_APP) {
+ jint appInstanceHandle = INVALID_APP_ID;
+ bool appRunningOnHub = (rsp.result == 0);
+ if (!(closeLoadTxn(appRunningOnHub, &appInstanceHandle))) {
+ if (appRunningOnHub) {
+ // Now we're in an odd situation. Our nanoapp
+ // is up and running on the Context Hub. However,
+ // something went wrong in our Service code so that
+ // we're not able to properly track this nanoapp
+ // in our Service code. If we tell the Java layer
+ // things are good, it's a lie because the handle
+ // we give them will fail when used with the Service.
+ // If we tell the Java layer this failed, it's kind
+ // of a lie as well, since this nanoapp is running.
+ //
+ // We leave a more robust fix for later, and for
+ // now just tell the user things have failed.
+ //
+ // TODO(b/30835981): Make this situation better.
+ rsp.result = -1;
+ }
+ }
+ passOnOsResponse(hubHandle, msgType, &rsp, (int8_t *)(&appInstanceHandle),
+ sizeof(appInstanceHandle));
+ } else if (msgType == CONTEXT_HUB_UNLOAD_APP) {
+ closeUnloadTxn(rsp.result == 0);
+ passOnOsResponse(hubHandle, msgType, &rsp, nullptr, 0);
+ } else {
+ passOnOsResponse(hubHandle, msgType, &rsp, nullptr, 0);
+ }
+ retVal = 0;
+ }
+ break;
+
+ case CONTEXT_HUB_QUERY_APPS:
+ rsp.result = 0;
+ retVal = handle_query_apps_response(msg, msgLen, hubHandle);
+ passOnOsResponse(hubHandle, msgType, &rsp, nullptr, 0);
+ break;
+
+ case CONTEXT_HUB_QUERY_MEMORY:
+ // Deferring this use
+ retVal = 0;
+ break;
+
+ case CONTEXT_HUB_OS_REBOOT:
+ if (isValidOsStatus(msg, msgLen, &rsp)) {
+ rsp.result = 0;
+ ALOGW("Context Hub handle %d restarted", hubHandle);
+ closeTxn();
+ passOnOsResponse(hubHandle, msgType, &rsp, nullptr, 0);
+ query_hub_for_apps(hubHandle);
+ retVal = 0;
+ }
+ break;
+
+ default:
+ retVal = -1;
+ break;
+ }
+
+ return retVal;
+}
+
+static bool sanity_check_cookie(void *cookie, uint32_t hub_id) {
+ int *ptr = (int *)cookie;
+
+ if (!ptr || *ptr >= db.hubInfo.numHubs) {
+ return false;
+ }
+
+ if (db.hubInfo.hubs[*ptr].hub_id != hub_id) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+
+int context_hub_callback(uint32_t hubId,
+ const struct hub_message_t *msg,
+ void *cookie) {
+ if (!msg) {
+ ALOGW("NULL message");
+ return -1;
+ }
+ if (!sanity_check_cookie(cookie, hubId)) {
+ ALOGW("Incorrect cookie %" PRId32 " for cookie %p! Bailing",
+ hubId, cookie);
+
+ return -1;
+ }
+
+
+ uint32_t messageType = msg->message_type;
+ uint32_t hubHandle = *(uint32_t*) cookie;
+
+ if (messageType < CONTEXT_HUB_TYPE_PRIVATE_MSG_BASE) {
+ handle_os_message(messageType, hubHandle, (uint8_t*) msg->message, msg->message_len);
+ } else {
+ jint appHandle = get_app_instance_for_app_id(msg->app_name.id);
+ if (appHandle < 0) {
+ ALOGE("Filtering out message due to invalid App Instance.");
+ } else {
+ uint32_t msgHeader[MSG_HEADER_SIZE] = {};
+ msgHeader[HEADER_FIELD_MSG_TYPE] = messageType;
+ msgHeader[HEADER_FIELD_HUB_HANDLE] = hubHandle;
+ msgHeader[HEADER_FIELD_APP_INSTANCE] = appHandle;
+ onMessageReceipt(msgHeader, MSG_HEADER_SIZE, (char*) msg->message, msg->message_len);
+ }
+ }
+
+ return 0;
+}
+
+static int init_jni(JNIEnv *env, jobject instance) {
+
+ if (env->GetJavaVM(&db.jniInfo.vm) != JNI_OK) {
+ return -1;
+ }
+
+ db.jniInfo.jContextHubService = env->NewGlobalRef(instance);
+
+ db.jniInfo.contextHubInfoClass =
+ env->FindClass("android/hardware/location/ContextHubInfo");
+
+ db.jniInfo.contextHubServiceClass =
+ env->FindClass("com/android/server/location/ContextHubService");
+
+ db.jniInfo.memoryRegionsClass =
+ env->FindClass("android/hardware/location/MemoryRegion");
+
+ db.jniInfo.contextHubInfoCtor =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass, "<init>", "()V");
+ db.jniInfo.contextHubInfoSetId =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass, "setId", "(I)V");
+ db.jniInfo.contextHubInfoSetName =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass, "setName",
+ "(Ljava/lang/String;)V");
+
+ db.jniInfo.contextHubInfoSetVendor =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setVendor", "(Ljava/lang/String;)V");
+ db.jniInfo.contextHubInfoSetToolchain =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setToolchain", "(Ljava/lang/String;)V");
+ db.jniInfo.contextHubInfoSetPlatformVersion =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setPlatformVersion", "(I)V");
+ db.jniInfo.contextHubInfoSetStaticSwVersion =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setStaticSwVersion", "(I)V");
+ db.jniInfo.contextHubInfoSetToolchainVersion =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setToolchainVersion", "(I)V");
+ db.jniInfo.contextHubInfoSetPeakMips =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setPeakMips", "(F)V");
+ db.jniInfo.contextHubInfoSetStoppedPowerDrawMw =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setStoppedPowerDrawMw", "(F)V");
+ db.jniInfo.contextHubInfoSetSleepPowerDrawMw =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setSleepPowerDrawMw", "(F)V");
+ db.jniInfo.contextHubInfoSetPeakPowerDrawMw =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setPeakPowerDrawMw", "(F)V");
+ db.jniInfo.contextHubInfoSetSupportedSensors =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setSupportedSensors", "([I)V");
+ db.jniInfo.contextHubInfoSetMemoryRegions =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setMemoryRegions", "([Landroid/hardware/location/MemoryRegion;)V");
+ db.jniInfo.contextHubInfoSetMaxPacketLenBytes =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass,
+ "setMaxPacketLenBytes", "(I)V");
+
+
+ db.jniInfo.contextHubServiceMsgReceiptCallback =
+ env->GetMethodID(db.jniInfo.contextHubServiceClass, "onMessageReceipt",
+ "([I[B)I");
+ db.jniInfo.contextHubInfoSetName =
+ env->GetMethodID(db.jniInfo.contextHubInfoClass, "setName",
+ "(Ljava/lang/String;)V");
+
+ db.jniInfo.contextHubServiceAddAppInstance =
+ env->GetMethodID(db.jniInfo.contextHubServiceClass,
+ "addAppInstance", "(IIJI)I");
+
+ db.jniInfo.contextHubServiceDeleteAppInstance =
+ env->GetMethodID(db.jniInfo.contextHubServiceClass,
+ "deleteAppInstance", "(I)I");
+
+ return 0;
+}
+
+static jobject constructJContextHubInfo(JNIEnv *env, const struct context_hub_t *hub) {
+ jstring jstrBuf;
+ jintArray jintBuf;
+ jobjectArray jmemBuf;
+
+ jobject jHub = env->NewObject(db.jniInfo.contextHubInfoClass,
+ db.jniInfo.contextHubInfoCtor);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetId, hub->hub_id);
+
+ jstrBuf = env->NewStringUTF(hub->name);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetName, jstrBuf);
+ env->DeleteLocalRef(jstrBuf);
+
+ jstrBuf = env->NewStringUTF(hub->vendor);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetVendor, jstrBuf);
+ env->DeleteLocalRef(jstrBuf);
+
+ jstrBuf = env->NewStringUTF(hub->toolchain);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetToolchain, jstrBuf);
+ env->DeleteLocalRef(jstrBuf);
+
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPlatformVersion, hub->platform_version);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetToolchainVersion, hub->toolchain_version);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakMips, hub->peak_mips);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetStoppedPowerDrawMw,
+ hub->stopped_power_draw_mw);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSleepPowerDrawMw,
+ hub->sleep_power_draw_mw);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakPowerDrawMw,
+ hub->peak_power_draw_mw);
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetMaxPacketLenBytes,
+ hub->max_supported_msg_len);
+
+
+ jintBuf = env->NewIntArray(hub->num_connected_sensors);
+ int *connectedSensors = new int[hub->num_connected_sensors];
+
+ if (!connectedSensors) {
+ ALOGW("Cannot allocate memory! Unexpected");
+ assert(false);
+ } else {
+ for (unsigned int i = 0; i < hub->num_connected_sensors; i++) {
+ connectedSensors[i] = hub->connected_sensors[i].sensor_id;
+ }
+ }
+
+ env->SetIntArrayRegion(jintBuf, 0, hub->num_connected_sensors,
+ connectedSensors);
+
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSupportedSensors, jintBuf);
+ env->DeleteLocalRef(jintBuf);
+
+ // We are not getting the memory regions from the CH Hal - change this when it is available
+ jmemBuf = env->NewObjectArray(0, db.jniInfo.memoryRegionsClass, nullptr);
+ // Note the zero size above. We do not need to set any elements
+ env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetMemoryRegions, jmemBuf);
+ env->DeleteLocalRef(jmemBuf);
+
+
+ delete[] connectedSensors;
+ return jHub;
+}
+
+static jobjectArray nativeInitialize(JNIEnv *env, jobject instance)
+{
+ jobject hub;
+ jobjectArray retArray;
+
+ if (init_jni(env, instance) < 0) {
+ return nullptr;
+ }
+
+ initContextHubService();
+
+ if (db.hubInfo.numHubs > 1) {
+ ALOGW("Clamping the number of hubs to 1");
+ db.hubInfo.numHubs = 1;
+ }
+
+ retArray = env->NewObjectArray(db.hubInfo.numHubs, db.jniInfo.contextHubInfoClass, nullptr);
+
+ for(int i = 0; i < db.hubInfo.numHubs; i++) {
+ hub = constructJContextHubInfo(env, &db.hubInfo.hubs[i]);
+ env->SetObjectArrayElement(retArray, i, hub);
+ }
+
+ return retArray;
+}
+
+static jint nativeSendMessage(JNIEnv *env, jobject instance, jintArray header_,
+ jbyteArray data_) {
+ jint retVal = -1; // Default to failure
+
+ jint *header = env->GetIntArrayElements(header_, 0);
+ unsigned int numHeaderElements = env->GetArrayLength(header_);
+ jbyte *data = env->GetByteArrayElements(data_, 0);
+ int dataBufferLength = env->GetArrayLength(data_);
+
+ if (numHeaderElements < MSG_HEADER_SIZE) {
+ ALOGW("Malformed header len");
+ return -1;
+ }
+
+ uint32_t appInstanceHandle = header[HEADER_FIELD_APP_INSTANCE];
+ uint32_t msgType = header[HEADER_FIELD_MSG_TYPE];
+ int hubHandle = -1;
+ int hubId;
+ uint64_t appId;
+
+ if (msgType == CONTEXT_HUB_UNLOAD_APP) {
+ hubHandle = get_hub_handle_for_app_instance(appInstanceHandle);
+ } else if (msgType == CONTEXT_HUB_LOAD_APP) {
+ if (numHeaderElements < MSG_HEADER_SIZE_LOAD_APP) {
+ return -1;
+ }
+ uint64_t appIdLo = header[HEADER_FIELD_LOAD_APP_ID_LO];
+ uint64_t appIdHi = header[HEADER_FIELD_LOAD_APP_ID_HI];
+ appId = appIdHi << 32 | appIdLo;
+
+ hubHandle = header[HEADER_FIELD_HUB_HANDLE];
+ } else {
+ hubHandle = header[HEADER_FIELD_HUB_HANDLE];
+ }
+
+ if (hubHandle < 0) {
+ ALOGD("Invalid hub Handle %d", hubHandle);
+ return -1;
+ }
+
+ if (msgType == CONTEXT_HUB_LOAD_APP ||
+ msgType == CONTEXT_HUB_UNLOAD_APP) {
+
+ if (isTxnPending()) {
+ ALOGW("Cannot load or unload app while a transaction is pending !");
+ return -1;
+ }
+
+ if (msgType == CONTEXT_HUB_LOAD_APP) {
+ if (startLoadAppTxn(appId, hubHandle) != 0) {
+ ALOGW("Cannot Start Load Transaction");
+ return -1;
+ }
+ } else if (msgType == CONTEXT_HUB_UNLOAD_APP) {
+ if (startUnloadAppTxn(appInstanceHandle) != 0) {
+ ALOGW("Cannot Start UnLoad Transaction");
+ return -1;
+ }
+ }
+ }
+
+ bool setAddressSuccess = false;
+ hub_message_t msg;
+
+ msg.message_type = msgType;
+
+ if (msgType == CONTEXT_HUB_UNLOAD_APP) {
+ msg.message_len = sizeof(db.appInstances[appInstanceHandle].appInfo.app_name);
+ msg.message = &db.appInstances[appInstanceHandle].appInfo.app_name;
+ setAddressSuccess = (set_os_app_as_destination(&msg, hubHandle) == 0);
+ hubId = get_hub_id_for_hub_handle(hubHandle);
+ } else {
+ msg.message_len = dataBufferLength;
+ msg.message = data;
+
+ if (header[HEADER_FIELD_APP_INSTANCE] == OS_APP_ID) {
+ setAddressSuccess = (set_os_app_as_destination(&msg, hubHandle) == 0);
+ hubId = get_hub_id_for_hub_handle(hubHandle);
+ } else {
+ setAddressSuccess = (set_dest_app(&msg, header[HEADER_FIELD_APP_INSTANCE]) == 0);
+ hubId = get_hub_id_for_app_instance(header[HEADER_FIELD_APP_INSTANCE]);
+ }
+ }
+
+ if (setAddressSuccess && hubId >= 0) {
+ retVal = db.hubInfo.contextHubModule->send_message(hubId, &msg);
+ } else {
+ ALOGD("Could not find app instance %" PRId32 " on hubHandle %" PRId32
+ ", setAddress %d",
+ header[HEADER_FIELD_APP_INSTANCE],
+ header[HEADER_FIELD_HUB_HANDLE],
+ (int)setAddressSuccess);
+ }
+
+ if (retVal != 0) {
+ ALOGD("Send Message failure - %d", retVal);
+ if (msgType == CONTEXT_HUB_LOAD_APP) {
+ jint ignored;
+ closeLoadTxn(false, &ignored);
+ } else if (msgType == CONTEXT_HUB_UNLOAD_APP) {
+ closeUnloadTxn(false);
+ }
+ }
+
+ env->ReleaseIntArrayElements(header_, header, 0);
+ env->ReleaseByteArrayElements(data_, data, 0);
+
+ return retVal;
+}
+
+//--------------------------------------------------------------------------------------------------
+//
+static const JNINativeMethod gContextHubServiceMethods[] = {
+ {"nativeInitialize",
+ "()[Landroid/hardware/location/ContextHubInfo;",
+ (void*)nativeInitialize },
+ {"nativeSendMessage",
+ "([I[B)I",
+ (void*)nativeSendMessage }
+};
+
+int register_android_server_location_ContextHubService(JNIEnv *env)
+{
+ RegisterMethodsOrDie(env, "com/android/server/location/ContextHubService",
+ gContextHubServiceMethods, NELEM(gContextHubServiceMethods));
+
+ return 0;
+}
+
+}//namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c291ba0..9758678 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -36,6 +36,7 @@
int register_android_server_UsbHostManager(JNIEnv* env);
int register_android_server_vr_VrManagerService(JNIEnv* env);
int register_android_server_VibratorService(JNIEnv* env);
+int register_android_server_location_ContextHubService(JNIEnv* env);
int register_android_server_location_GnssLocationProvider(JNIEnv* env);
int register_android_server_location_FlpHardwareProvider(JNIEnv* env);
int register_android_server_connectivity_Vpn(JNIEnv* env);
@@ -73,6 +74,7 @@
register_android_server_vr_VrManagerService(env);
register_android_server_VibratorService(env);
register_android_server_SystemServer(env);
+ register_android_server_location_ContextHubService(env);
register_android_server_location_GnssLocationProvider(env);
register_android_server_location_FlpHardwareProvider(env);
register_android_server_connectivity_Vpn(env);