blob: 8eb39e1aac580e7651a6ccc87424ce868b900243 [file] [log] [blame]
/*
* 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;
}