blob: d59bc10b72da9b4c79d10ffe61e1e961b4aaa2c5 [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.
*/
#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 <sys/endian.h>
#include <chrono>
#include <mutex>
#include <queue>
#include <unordered_map>
#include <utility>
#include <android-base/macros.h>
#include <android/hardware/contexthub/1.0/IContexthub.h>
#include <cutils/log.h>
#include "core_jni_helpers.h"
#include "JNIHelp.h"
using android::hardware::contexthub::V1_0::AsyncEventType;
using android::hardware::contexthub::V1_0::ContextHub;
using android::hardware::contexthub::V1_0::ContextHubMsg;
using android::hardware::contexthub::V1_0::HubAppInfo;
using android::hardware::contexthub::V1_0::IContexthub;
using android::hardware::contexthub::V1_0::IContexthubCallback;
using android::hardware::contexthub::V1_0::NanoAppBinary;
using android::hardware::contexthub::V1_0::Result;
using android::hardware::contexthub::V1_0::TransactionResult;
using android::hardware::Return;
using std::chrono::steady_clock;
// 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.
constexpr auto kMinTransactionCancelTime = std::chrono::seconds(29);
namespace android {
constexpr uint32_t kNanoAppBinaryHeaderVersion = 1;
// Important: this header is explicitly defined as little endian byte order, and
// therefore may not match host endianness
struct NanoAppBinaryHeader {
uint32_t headerVersion; // 0x1 for this version
uint32_t magic; // "NANO" (see NANOAPP_MAGIC in context_hub.h)
uint64_t appId; // App Id, contains vendor id
uint32_t appVersion; // Version of the app
uint32_t flags; // Signed, encrypted
uint64_t hwHubType; // Which hub type is this compiled for
uint8_t targetChreApiMajorVersion; // Which CHRE API version this is compiled for
uint8_t targetChreApiMinorVersion;
uint8_t reserved[6];
} __attribute__((packed));
enum HubMessageType {
CONTEXT_HUB_APPS_ENABLE = 1, // Enables loaded nano-app(s)
CONTEXT_HUB_APPS_DISABLE = 2, // Disables loaded nano-app(s)
CONTEXT_HUB_LOAD_APP = 3, // Load a supplied app
CONTEXT_HUB_UNLOAD_APP = 4, // Unload a specified app
CONTEXT_HUB_QUERY_APPS = 5, // Query for app(s) info on hub
CONTEXT_HUB_QUERY_MEMORY = 6, // Query for memory info
CONTEXT_HUB_OS_REBOOT = 7, // Request to reboot context HUB OS
};
constexpr jint OS_APP_ID = -1;
constexpr jint INVALID_APP_ID = -2;
constexpr jint MIN_APP_ID = 1;
constexpr jint MAX_APP_ID = 128;
constexpr size_t MSG_HEADER_SIZE = 4;
constexpr size_t HEADER_FIELD_MSG_TYPE = 0;
constexpr size_t HEADER_FIELD_MSG_VERSION = 1;
constexpr size_t HEADER_FIELD_HUB_HANDLE = 2;
constexpr size_t HEADER_FIELD_APP_INSTANCE = 3;
constexpr size_t HEADER_FIELD_LOAD_APP_ID_LO = MSG_HEADER_SIZE;
constexpr size_t HEADER_FIELD_LOAD_APP_ID_HI = MSG_HEADER_SIZE + 1;
constexpr size_t MSG_HEADER_SIZE_LOAD_APP = MSG_HEADER_SIZE + 2;
jint getAppInstanceForAppId(uint64_t app_id);
int onMessageReceipt(const uint32_t *header,
size_t headerLen,
const char *msg,
size_t msgLen);
void onHubReset(uint32_t hubId);
void queryHubForApps(uint32_t hubId);
void passOnOsResponse(uint32_t hubHandle,
uint32_t msgType,
TransactionResult result,
const int8_t *additionalData,
size_t additionalDataLen);
bool closeLoadTxn(bool success, jint *appInstanceHandle);
void closeUnloadTxn(bool success);
int handleQueryAppsResponse(const std::vector<HubAppInfo> apps,
uint32_t hubHandle);
struct JniInfo {
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;
};
class TxnManager {
public:
TxnManager() {
mData = nullptr;
mIsPending = false;
}
~TxnManager() {
closeTxn();
}
int addTxn(HubMessageType txnIdentifier, void *txnData) {
std::lock_guard<std::mutex>lock(mLock);
if (mIsPending) {
ALOGW("Transaction already found pending when trying to add a new one.");
return -1;
}
mIsPending = true;
mFirstTimeTxnCanBeCanceled = steady_clock::now() + kMinTransactionCancelTime;
mData = txnData;
mIdentifier = txnIdentifier;
return 0;
}
int closeTxn() {
std::lock_guard<std::mutex>lock(mLock);
closeTxnUnlocked();
return 0;
}
bool isTxnPending() {
std::lock_guard<std::mutex>lock(mLock);
return mIsPending;
}
void closeAnyStaleTxns() {
std::lock_guard<std::mutex>lock(mLock);
if (mIsPending && steady_clock::now() >= mFirstTimeTxnCanBeCanceled) {
ALOGW("Stale transaction canceled");
closeTxnUnlocked();
}
}
int fetchTxnData(HubMessageType *id, void **data) {
if (id == nullptr || data == nullptr) {
ALOGW("Null Params isNull{id, data} {%d, %d}",
id == nullptr ? 1 : 0,
data == nullptr ? 1 : 0);
return -1;
}
std::lock_guard<std::mutex>lock(mLock);
if (!mIsPending) {
ALOGW("No Transactions pending");
return -1;
}
*id = mIdentifier;
*data = mData;
return 0;
}
private:
bool mIsPending; // Is a transaction pending
std::mutex mLock; // mutex for manager
HubMessageType mIdentifier; // What are we doing
void *mData; // Details
steady_clock::time_point mFirstTimeTxnCanBeCanceled;
// Only call this if you hold the lock.
void closeTxnUnlocked() {
mIsPending = false;
free(mData);
mData = nullptr;
}
};
struct ContextHubServiceCallback : IContexthubCallback {
uint32_t mContextHubId;
ContextHubServiceCallback(uint32_t hubId) {
mContextHubId = hubId;
}
virtual Return<void> handleClientMsg(const ContextHubMsg &msg) {
jint appHandle = getAppInstanceForAppId(msg.appName);
if (appHandle < 0) {
ALOGE("Filtering out message due to invalid App Instance.");
} else {
uint32_t msgHeader[MSG_HEADER_SIZE] = {};
msgHeader[HEADER_FIELD_MSG_TYPE] = msg.msgType;
msgHeader[HEADER_FIELD_HUB_HANDLE] = mContextHubId;
msgHeader[HEADER_FIELD_APP_INSTANCE] = appHandle;
onMessageReceipt(msgHeader,
MSG_HEADER_SIZE,
reinterpret_cast<const char *>(msg.msg.data()),
msg.msg.size());
}
return android::hardware::Void();
}
virtual Return<void> handleHubEvent(AsyncEventType evt) {
if (evt == AsyncEventType::RESTARTED) {
ALOGW("Context Hub handle %d restarted", mContextHubId);
onHubReset(mContextHubId);
} else {
ALOGW("Cannot handle event %u from hub %d", evt, mContextHubId);
}
return android::hardware::Void();
}
virtual Return<void> handleTxnResult(uint32_t txnId,
TransactionResult result) {
ALOGI("Handle transaction result , hubId %" PRIu32 ", txnId %" PRIu32 ", result %" PRIu32,
mContextHubId,
txnId,
result);
switch(txnId) {
case CONTEXT_HUB_APPS_ENABLE:
case CONTEXT_HUB_APPS_DISABLE:
passOnOsResponse(mContextHubId, txnId, result, nullptr, 0);
break;
case CONTEXT_HUB_UNLOAD_APP:
closeUnloadTxn(result == TransactionResult::SUCCESS);
passOnOsResponse(mContextHubId, txnId, result, nullptr, 0);
break;
case CONTEXT_HUB_LOAD_APP:
{
jint appInstanceHandle = INVALID_APP_ID;
bool appRunningOnHub = (result == TransactionResult::SUCCESS);
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.
result = TransactionResult::FAILURE;
}
}
passOnOsResponse(mContextHubId,
txnId,
result,
reinterpret_cast<int8_t *>(&appInstanceHandle),
sizeof(appInstanceHandle));
break;
}
default:
ALOGI("unrecognized transction id %" PRIu32, txnId);
break;
}
return android::hardware::Void();
}
virtual Return<void> handleAppsInfo(
const android::hardware::hidl_vec<HubAppInfo>& apps) {
TransactionResult result = TransactionResult::SUCCESS;
handleQueryAppsResponse(apps,mContextHubId);
passOnOsResponse(mContextHubId, CONTEXT_HUB_QUERY_APPS, result, nullptr, 0);
return android::hardware::Void();
}
virtual Return<void> handleAppAbort(uint64_t appId, uint32_t abortCode) {
ALOGI("Handle app aport called from %" PRIx64 " with abort code %" PRIu32,
appId,
abortCode);
// TODO: Plumb this to the clients interested in this app
return android::hardware::Void();
}
void setContextHubId(uint32_t id) {
mContextHubId = id;
}
uint32_t getContextHubId() {
return(mContextHubId);
}
};
struct AppInstanceInfo {
HubAppInfo appInfo; // returned from the HAL
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 ContextHubInfo {
int numHubs;
Vector<ContextHub> hubs;
sp<IContexthub> contextHub;
};
struct ContextHubServiceDb {
int initialized;
ContextHubInfo hubInfo;
JniInfo jniInfo;
std::queue<jint> freeIds;
std::unordered_map<jint, AppInstanceInfo> appInstances;
TxnManager txnManager;
std::vector<ContextHubServiceCallback *> regCallBacks;
};
ContextHubServiceDb db;
bool getHubIdForHubHandle(int hubHandle, uint32_t *hubId) {
if (hubHandle < 0 || hubHandle >= db.hubInfo.numHubs || hubId == nullptr) {
return false;
} else {
*hubId = db.hubInfo.hubs[hubHandle].hubId;
return true;
}
}
int getHubHandleForAppInstance(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;
}
jint getAppInstanceForAppId(uint64_t app_id) {
auto end = db.appInstances.end();
for (auto current = db.appInstances.begin(); current != end; ++current) {
if (current->second.appInfo.appId == app_id) {
return current->first;
}
}
ALOGD("Cannot find app for app id %" PRIu64 ".", app_id);
return -1;
}
uint64_t getAppIdForAppInstance(jint id) {
if (!db.appInstances.count(id)) {
return INVALID_APP_ID;
}
return db.appInstances[id].appInfo.appId;
}
void queryHubForApps(uint32_t hubId) {
Result r = db.hubInfo.contextHub->queryApps(hubId);
ALOGD("Sent query for apps to hub %" PRIu32 " with result %" PRIu32, hubId, r);
}
void sendQueryForApps() {
for (int i = 0; i < db.hubInfo.numHubs; i++ ) {
queryHubForApps(db.hubInfo.hubs[i].hubId);
}
}
int returnId(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;
}
jint generateId() {
// Note : This method is not thread safe.
jint retVal = -1;
if (!db.freeIds.empty()) {
retVal = db.freeIds.front();
db.freeIds.pop();
}
return retVal;
}
jint addAppInstance(const HubAppInfo *appInfo, uint32_t hubHandle,
jint appInstanceHandle, JNIEnv *env) {
// Not checking if the apps are indeed distinct
AppInstanceInfo entry;
assert(appInfo);
entry.appInfo = *appInfo;
entry.instanceId = appInstanceHandle;
entry.truncName = appInfo->appId;
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);
const char *action = (db.appInstances.count(appInstanceHandle) == 0) ? "Added" : "Updated";
ALOGI("%s App 0x%" PRIx64 " on hub Handle %" PRId32
" as appInstance %" PRId32, action, entry.truncName,
entry.hubHandle, appInstanceHandle);
return appInstanceHandle;
}
int deleteAppInstance(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;
}
returnId(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;
}
int startLoadAppTxn(uint64_t appId, int hubHandle) {
AppInstanceInfo *txnInfo = new AppInstanceInfo();
jint instanceId = generateId();
if (!txnInfo || instanceId < 0) {
returnId(instanceId);
delete txnInfo;
return -1;
}
txnInfo->truncName = appId;
txnInfo->hubHandle = hubHandle;
txnInfo->instanceId = instanceId;
txnInfo->appInfo.appId = appId;
txnInfo->appInfo.version = -1; // Awaited
if (db.txnManager.addTxn(CONTEXT_HUB_LOAD_APP, txnInfo) != 0) {
returnId(instanceId);
delete txnInfo;
return -1;
}
return 0;
}
int startUnloadAppTxn(jint appInstanceHandle) {
jint *txnData = new(jint);
if (!txnData) {
ALOGW("Cannot allocate memory to start unload transaction");
return -1;
}
*txnData = appInstanceHandle;
if (db.txnManager.addTxn(CONTEXT_HUB_UNLOAD_APP, txnData) != 0) {
delete txnData;
ALOGW("Cannot start transaction to unload app");
return -1;
}
return 0;
}
void getHubsCb(const ::android::hardware::hidl_vec<ContextHub>& hubs) {
for (size_t i = 0; i < hubs.size(); i++) {
db.hubInfo.hubs.push_back(hubs[i]);
}
}
void initContextHubService() {
db.hubInfo.numHubs = 0;
db.hubInfo.contextHub = IContexthub::getService();
if (db.hubInfo.contextHub == nullptr) {
ALOGE("Could not load context hub hal");
} else {
ALOGI("Loaded context hub hal, isRemote %s", db.hubInfo.contextHub->isRemote() ? "TRUE" : "FALSE");
}
// Prep for storing app info
for (jint i = MIN_APP_ID; i <= MAX_APP_ID; i++) {
db.freeIds.push(i);
}
if (db.hubInfo.contextHub != nullptr) {
std::function<void(const ::android::hardware::hidl_vec<ContextHub>& hubs)> f = getHubsCb;
if(!db.hubInfo.contextHub->getHubs(f).isOk()) {
ALOGW("GetHubs Failed! transport error.");
return;
};
int retNumHubs = db.hubInfo.hubs.size();
ALOGD("ContextHubModule returned %d hubs ", retNumHubs);
db.hubInfo.numHubs = retNumHubs;
for (int i = 0; i < db.hubInfo.numHubs; i++) {
ALOGI("Subscribing to hubHandle %d", i);
ContextHubServiceCallback *callBackPtr =
new ContextHubServiceCallback(db.hubInfo.hubs[i].hubId);
db.hubInfo.contextHub->registerCallback(db.hubInfo.hubs[i].hubId,
callBackPtr);
db.regCallBacks.push_back(callBackPtr);
}
sendQueryForApps();
} else {
ALOGW("No Context Hub Module present");
}
}
void onHubReset(uint32_t hubId) {
TransactionResult result = TransactionResult::SUCCESS;
db.txnManager.closeTxn();
// TODO : Expose this through an api
passOnOsResponse(hubId, CONTEXT_HUB_OS_REBOOT, result, nullptr, 0);
queryHubForApps(hubId);
}
int onMessageReceipt(const uint32_t *header,
size_t headerLen,
const 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, reinterpret_cast<const jbyte *>(msg));
env->SetIntArrayRegion(jheader, 0, headerLen, reinterpret_cast<const jint *>(header));
int ret = (env->CallIntMethod(db.jniInfo.jContextHubService,
db.jniInfo.contextHubServiceMsgReceiptCallback,
jheader,
jmsg) != 0);
env->DeleteLocalRef(jmsg);
env->DeleteLocalRef(jheader);
return ret;
}
int handleQueryAppsResponse(const std::vector<HubAppInfo> apps,
uint32_t hubHandle) {
JNIEnv *env;
if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) != JNI_OK) {
return -1;
}
int numApps = apps.size();
// 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; ) {
AppInstanceInfo cacheEntry = 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++) {
if (apps[i].appId == cacheEntry.appInfo.appId) {
// We found a match; this entry is current.
entryIsStale = false;
break;
}
}
if (entryIsStale) {
deleteAppInstance(cacheEntry.instanceId, env);
}
}
// (2). Update our caches with the latest.
for (int i = 0; i < numApps; i++) {
// We will only have one instance of the app
// TODO : Change this logic once we support multiple instances of the same app
jint appInstance = getAppInstanceForAppId(apps[i].appId);
if (appInstance == -1) {
// This is a previously unknown app, let's allocate an "id" for it.
appInstance = generateId();
}
addAppInstance(&apps[i], hubHandle, appInstance, env);
}
return 0;
}
// TODO(b/30807327): Do not use raw bytes for additional data. Use the
// JNI interfaces for the appropriate types.
void passOnOsResponse(uint32_t hubHandle,
uint32_t msgType,
TransactionResult result,
const 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;
truncatedResult = static_cast<int8_t>(result);
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, reinterpret_cast<jbyte *>(msg));
env->SetIntArrayRegion(jheader, 0, arraysize(header), reinterpret_cast<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;
HubMessageType txnId;
if (success && db.txnManager.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);
deleteAppInstance(handle, env);
} else {
ALOGW("Could not unload the app successfully ! success %d, txnData %p",
success,
txnData);
}
db.txnManager.closeTxn();
}
bool closeLoadTxn(bool success, jint *appInstanceHandle) {
void *txnData;
HubMessageType txnId;
if (success && db.txnManager.fetchTxnData(&txnId, &txnData) == 0 &&
txnId == CONTEXT_HUB_LOAD_APP) {
AppInstanceInfo *info = static_cast<AppInstanceInfo *>(txnData);
*appInstanceHandle = info->instanceId;
JNIEnv *env;
if ((db.jniInfo.vm)->AttachCurrentThread(&env, nullptr) == JNI_OK) {
addAppInstance(&info->appInfo, info->hubHandle, info->instanceId, env);
} else {
ALOGW("Could not attach to JVM !");
success = false;
}
// While we just called addAppInstance 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;
}
db.txnManager.closeTxn();
return success;
}
int initJni(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;
}
jobject constructJContextHubInfo(JNIEnv *env, const ContextHub &hub) {
jstring jstrBuf;
jintArray jintBuf;
jobjectArray jmemBuf;
jobject jHub = env->NewObject(db.jniInfo.contextHubInfoClass,
db.jniInfo.contextHubInfoCtor);
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetId, hub.hubId);
jstrBuf = env->NewStringUTF(hub.name.c_str());
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetName, jstrBuf);
env->DeleteLocalRef(jstrBuf);
jstrBuf = env->NewStringUTF(hub.vendor.c_str());
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetVendor, jstrBuf);
env->DeleteLocalRef(jstrBuf);
jstrBuf = env->NewStringUTF(hub.toolchain.c_str());
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetToolchain, jstrBuf);
env->DeleteLocalRef(jstrBuf);
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPlatformVersion, hub.platformVersion);
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetToolchainVersion, hub.toolchainVersion);
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakMips, hub.peakMips);
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetStoppedPowerDrawMw,
hub.stoppedPowerDrawMw);
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetSleepPowerDrawMw,
hub.sleepPowerDrawMw);
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetPeakPowerDrawMw,
hub.peakPowerDrawMw);
env->CallVoidMethod(jHub, db.jniInfo.contextHubInfoSetMaxPacketLenBytes,
hub.maxSupportedMsgLen);
jintBuf = env->NewIntArray(hub.connectedSensors.size());
int *connectedSensors = new int[hub.connectedSensors.size()];
if (!connectedSensors) {
ALOGW("Cannot allocate memory! Unexpected");
assert(false);
} else {
for (unsigned int i = 0; i < hub.connectedSensors.size(); i++) {
// TODO :: Populate connected sensors.
//connectedSensors[i] = hub.connectedSensors[i].sensorType;
connectedSensors[i] = 0;
}
}
env->SetIntArrayRegion(jintBuf, 0, hub.connectedSensors.size(),
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;
}
jobjectArray nativeInitialize(JNIEnv *env, jobject instance) {
jobject hub;
jobjectArray retArray;
if (initJni(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;
}
Result sendLoadNanoAppRequest(uint32_t hubId,
jbyte *data,
size_t dataBufferLength) {
auto header = reinterpret_cast<const NanoAppBinaryHeader *>(data);
Result result;
if (dataBufferLength < sizeof(NanoAppBinaryHeader)) {
ALOGE("Got short NanoApp, length %zu", dataBufferLength);
result = Result::BAD_PARAMS;
} else if (header->headerVersion != htole32(kNanoAppBinaryHeaderVersion)) {
ALOGE("Got unexpected NanoApp header version %" PRIu32,
letoh32(header->headerVersion));
result = Result::BAD_PARAMS;
} else {
NanoAppBinary nanoapp;
// Data from the common nanoapp header goes into explicit fields
nanoapp.appId = letoh64(header->appId);
nanoapp.appVersion = letoh32(header->appVersion);
nanoapp.flags = letoh32(header->flags);
nanoapp.targetChreApiMajorVersion = header->targetChreApiMajorVersion;
nanoapp.targetChreApiMinorVersion = header->targetChreApiMinorVersion;
// Everything past the header goes in customBinary
auto dataBytes = reinterpret_cast<const uint8_t *>(data);
std::vector<uint8_t> customBinary(
dataBytes + sizeof(NanoAppBinaryHeader),
dataBytes + dataBufferLength);
nanoapp.customBinary = std::move(customBinary);
ALOGW("Calling Load NanoApp on hub %d", hubId);
result = db.hubInfo.contextHub->loadNanoApp(hubId,
nanoapp,
CONTEXT_HUB_LOAD_APP);
}
return result;
}
jint nativeSendMessage(JNIEnv *env,
jobject instance,
jintArray header_,
jbyteArray data_) {
// With the new binderized HAL definition, this function can be made much simpler.
// All the magic can be removed. This is not however needed for the default implementation
// TODO :: Change the JNI interface to conform to the new HAL interface and clean up this
// function
jint retVal = -1; // Default to failure
jint *header = env->GetIntArrayElements(header_, 0);
size_t numHeaderElements = env->GetArrayLength(header_);
jbyte *data = env->GetByteArrayElements(data_, 0);
size_t dataBufferLength = env->GetArrayLength(data_);
if (numHeaderElements < MSG_HEADER_SIZE) {
ALOGW("Malformed header len");
return -1;
}
jint appInstanceHandle = header[HEADER_FIELD_APP_INSTANCE];
uint32_t msgType = header[HEADER_FIELD_MSG_TYPE];
int hubHandle = -1;
uint64_t appId;
if (msgType == CONTEXT_HUB_UNLOAD_APP) {
hubHandle = getHubHandleForAppInstance(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];
}
uint32_t hubId = -1;
if (!getHubIdForHubHandle(hubHandle, &hubId)) {
ALOGD("Invalid hub Handle %d", hubHandle);
return -1;
}
if (msgType == CONTEXT_HUB_LOAD_APP ||
msgType == CONTEXT_HUB_UNLOAD_APP) {
db.txnManager.closeAnyStaleTxns();
if (db.txnManager.isTxnPending()) {
// TODO : There is a race conditio
ALOGW("Cannot load or unload app while a transaction is pending !");
return -1;
} else 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;
}
}
}
Result result;
if (msgType == CONTEXT_HUB_UNLOAD_APP) {
ALOGW("Calling UnLoad NanoApp for app %" PRIx64 " on hub %" PRIu32,
db.appInstances[appInstanceHandle].appInfo.appId,
hubId);
result = db.hubInfo.contextHub->unloadNanoApp(
hubId, db.appInstances[appInstanceHandle].appInfo.appId, CONTEXT_HUB_UNLOAD_APP);
} else {
if (appInstanceHandle == OS_APP_ID) {
if (msgType == CONTEXT_HUB_LOAD_APP) {
result = sendLoadNanoAppRequest(hubId, data, dataBufferLength);
} else if (msgType == CONTEXT_HUB_QUERY_APPS) {
result = db.hubInfo.contextHub->queryApps(hubId);
} else {
ALOGD("Dropping OS addresses message of type - %" PRIu32, msgType);
result = Result::BAD_PARAMS;
}
} else {
appId = getAppIdForAppInstance(appInstanceHandle);
if (appId == static_cast<uint64_t>(INVALID_APP_ID)) {
ALOGD("Cannot find application instance %d", appInstanceHandle);
result = Result::BAD_PARAMS;
} else if (hubHandle != getHubHandleForAppInstance(appInstanceHandle)) {
ALOGE("Given hubHandle (%d) doesn't match expected for app instance (%d)",
hubHandle,
getHubHandleForAppInstance(appInstanceHandle));
result = Result::BAD_PARAMS;
} else {
ContextHubMsg msg;
msg.appName = appId;
msg.msgType = msgType;
msg.msg.setToExternal((unsigned char *)data, dataBufferLength);
ALOGW("Sending msg of type %" PRIu32 " len %zu to app %" PRIx64 " on hub %" PRIu32,
msgType,
dataBufferLength,
appId,
hubId);
result = db.hubInfo.contextHub->sendMessageToHub(hubId, msg);
}
}
}
if (result != Result::OK) {
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);
}
} else {
retVal = 0;
}
env->ReleaseIntArrayElements(header_, header, 0);
env->ReleaseByteArrayElements(data_, data, 0);
return retVal;
}
//--------------------------------------------------------------------------------------------------
//
const JNINativeMethod gContextHubServiceMethods[] = {
{"nativeInitialize",
"()[Landroid/hardware/location/ContextHubInfo;",
reinterpret_cast<void*>(nativeInitialize)},
{"nativeSendMessage",
"([I[B)I",
reinterpret_cast<void*>(nativeSendMessage)}
};
int register_android_server_location_ContextHubService(JNIEnv *env)
{
RegisterMethodsOrDie(env, "com/android/server/location/ContextHubService",
gContextHubServiceMethods, NELEM(gContextHubServiceMethods));
return 0;
}
}//namespace android