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);