/* * 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" #define LOG_NDEBUG 0 #define LOG_TAG "ContextHubService" #include <inttypes.h> #include <jni.h> #include <mutex> #include <string.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> // TOOD: On master, alphabetize these and move <mutex> into this // grouping. #include <chrono> #include <unordered_map> #include <queue> #include <cutils/log.h> #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(sizeof(header)); env->SetByteArrayRegion(jmsg, 0, msgLen, (jbyte *)msg); env->SetIntArrayRegion(jheader, 0, sizeof(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("android/hardware/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) { ALOGD("Asking HAL to remove app"); 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 } }; }//namespace android using namespace android; int register_android_hardware_location_ContextHubService(JNIEnv *env) { RegisterMethodsOrDie(env, "android/hardware/location/ContextHubService", gContextHubServiceMethods, NELEM(gContextHubServiceMethods)); return 0; }