Merge "Add utility function that takes Java Bundle, converts it and pushes it to Lua table." into sc-dev
diff --git a/cpp/telemetry/script_executor/Android.bp b/cpp/telemetry/script_executor/Android.bp
index 734d4dd..78b639a 100644
--- a/cpp/telemetry/script_executor/Android.bp
+++ b/cpp/telemetry/script_executor/Android.bp
@@ -16,13 +16,47 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-cc_library {
- name: "script_executor",
- srcs: [
- "src/LuaEngine.cpp",
- ],
- static_libs: [
- "liblua",
- ],
+cc_defaults {
+ name: "scriptexecutor_defaults",
+ cflags: [
+ "-Wno-unused-parameter",
+ ],
+ static_libs: [
+ "libbase",
+ "liblog",
+ "liblua",
+ ],
}
+cc_library {
+ name: "libscriptexecutor",
+ defaults: [
+ "scriptexecutor_defaults",
+ ],
+ srcs: [
+ "src/JniUtils.cpp",
+ "src/LuaEngine.cpp",
+ "src/ScriptExecutorListener.cpp",
+ ],
+ shared_libs: [
+ "libnativehelper",
+ ],
+ // Allow dependents to use the header files.
+ export_include_dirs: [
+ "src",
+ ],
+}
+
+cc_library_shared {
+ name: "libscriptexecutorjniutils-test",
+ defaults: [
+ "scriptexecutor_defaults",
+ ],
+ srcs: [
+ "src/tests/JniUtilsTestHelper.cpp",
+ ],
+ shared_libs: [
+ "libnativehelper",
+ "libscriptexecutor",
+ ],
+}
diff --git a/cpp/telemetry/script_executor/src/JniUtils.cpp b/cpp/telemetry/script_executor/src/JniUtils.cpp
new file mode 100644
index 0000000..93c1af8
--- /dev/null
+++ b/cpp/telemetry/script_executor/src/JniUtils.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2021, 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 "JniUtils.h"
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+namespace script_executor {
+
+void PushBundleToLuaTable(JNIEnv* env, LuaEngine* luaEngine, jobject bundle) {
+ lua_newtable(luaEngine->GetLuaState());
+ // null bundle object is allowed. We will treat it as an empty table.
+ if (bundle == nullptr) {
+ return;
+ }
+
+ // TODO(b/188832769): Consider caching some of these JNI references for
+ // performance reasons.
+ jclass bundleClass = env->FindClass("android/os/Bundle");
+ jmethodID getKeySetMethod = env->GetMethodID(bundleClass, "keySet", "()Ljava/util/Set;");
+ jobject keys = env->CallObjectMethod(bundle, getKeySetMethod);
+ jclass setClass = env->FindClass("java/util/Set");
+ jmethodID iteratorMethod = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
+ jobject keySetIteratorObject = env->CallObjectMethod(keys, iteratorMethod);
+
+ jclass iteratorClass = env->FindClass("java/util/Iterator");
+ jmethodID hasNextMethod = env->GetMethodID(iteratorClass, "hasNext", "()Z");
+ jmethodID nextMethod = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
+
+ jclass booleanClass = env->FindClass("java/lang/Boolean");
+ jclass integerClass = env->FindClass("java/lang/Integer");
+ jclass numberClass = env->FindClass("java/lang/Number");
+ jclass stringClass = env->FindClass("java/lang/String");
+ // TODO(b/188816922): Handle more types such as float and integer arrays,
+ // and perhaps nested Bundles.
+
+ jmethodID getMethod =
+ env->GetMethodID(bundleClass, "get", "(Ljava/lang/String;)Ljava/lang/Object;");
+
+ // Iterate over key set of the bundle one key at a time.
+ while (env->CallBooleanMethod(keySetIteratorObject, hasNextMethod)) {
+ // Read the value object that corresponds to this key.
+ jstring key = (jstring)env->CallObjectMethod(keySetIteratorObject, nextMethod);
+ jobject value = env->CallObjectMethod(bundle, getMethod, key);
+
+ // Get the value of the type, extract it accordingly from the bundle and
+ // push the extracted value and the key to the Lua table.
+ if (env->IsInstanceOf(value, booleanClass)) {
+ jmethodID boolMethod = env->GetMethodID(booleanClass, "booleanValue", "()Z");
+ bool boolValue = static_cast<bool>(env->CallBooleanMethod(value, boolMethod));
+ lua_pushboolean(luaEngine->GetLuaState(), boolValue);
+ } else if (env->IsInstanceOf(value, integerClass)) {
+ jmethodID intMethod = env->GetMethodID(integerClass, "intValue", "()I");
+ lua_pushinteger(luaEngine->GetLuaState(), env->CallIntMethod(value, intMethod));
+ } else if (env->IsInstanceOf(value, numberClass)) {
+ // Condense other numeric types using one class. Because lua supports only
+ // integer or double, and we handled integer in previous if clause.
+ jmethodID numberMethod = env->GetMethodID(numberClass, "doubleValue", "()D");
+ /* Pushes a double onto the stack */
+ lua_pushnumber(luaEngine->GetLuaState(), env->CallDoubleMethod(value, numberMethod));
+ } else if (env->IsInstanceOf(value, stringClass)) {
+ const char* rawStringValue = env->GetStringUTFChars((jstring)value, nullptr);
+ lua_pushstring(luaEngine->GetLuaState(), rawStringValue);
+ env->ReleaseStringUTFChars((jstring)value, rawStringValue);
+ } else {
+ // Other types are not implemented yet, skipping.
+ continue;
+ }
+
+ const char* rawKey = env->GetStringUTFChars(key, nullptr);
+ // table[rawKey] = value, where value is on top of the stack,
+ // and the table is the next element in the stack.
+ lua_setfield(luaEngine->GetLuaState(), /* idx= */ -2, rawKey);
+ env->ReleaseStringUTFChars(key, rawKey);
+ }
+}
+
+} // namespace script_executor
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/script_executor/src/JniUtils.h b/cpp/telemetry/script_executor/src/JniUtils.h
new file mode 100644
index 0000000..c3ef677
--- /dev/null
+++ b/cpp/telemetry/script_executor/src/JniUtils.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+#ifndef CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_JNIUTILS_H_
+#define CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_JNIUTILS_H_
+
+#include "LuaEngine.h"
+#include "jni.h"
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+namespace script_executor {
+
+// Helper function which takes android.os.Bundle object in "bundle" argument
+// and converts it to Lua table on top of Lua stack. All key-value pairs are
+// converted to the corresponding key-value pairs of the Lua table as long as
+// the Bundle value types are supported. At this point, we support boolean,
+// integer, double and String types in Java.
+void PushBundleToLuaTable(JNIEnv* env, LuaEngine* luaEngine, jobject bundle);
+
+} // namespace script_executor
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_JNIUTILS_H_
diff --git a/cpp/telemetry/script_executor/src/LuaEngine.cpp b/cpp/telemetry/script_executor/src/LuaEngine.cpp
index a8cace3..cc1d0b8 100644
--- a/cpp/telemetry/script_executor/src/LuaEngine.cpp
+++ b/cpp/telemetry/script_executor/src/LuaEngine.cpp
@@ -28,8 +28,7 @@
namespace telemetry {
namespace script_executor {
-LuaEngine::LuaEngine(std::unique_ptr<ScriptExecutorListener> listener) :
- mListener(std::move(listener)) {
+LuaEngine::LuaEngine() {
mLuaState = luaL_newstate();
luaL_openlibs(mLuaState);
}
@@ -38,6 +37,10 @@
lua_close(mLuaState);
}
+lua_State* LuaEngine::GetLuaState() {
+ return mLuaState;
+}
+
} // namespace script_executor
} // namespace telemetry
} // namespace automotive
diff --git a/cpp/telemetry/script_executor/src/LuaEngine.h b/cpp/telemetry/script_executor/src/LuaEngine.h
index 086dbfe..a0f3978 100644
--- a/cpp/telemetry/script_executor/src/LuaEngine.h
+++ b/cpp/telemetry/script_executor/src/LuaEngine.h
@@ -33,14 +33,15 @@
// Encapsulates Lua script execution environment.
class LuaEngine {
public:
- explicit LuaEngine(std::unique_ptr<ScriptExecutorListener> listener);
+ LuaEngine();
virtual ~LuaEngine();
+ // Returns pointer to Lua state object.
+ lua_State* GetLuaState();
+
private:
lua_State* mLuaState; // owned
-
- std::unique_ptr<ScriptExecutorListener> mListener;
};
} // namespace script_executor
diff --git a/cpp/telemetry/script_executor/src/tests/JniUtilsTestHelper.cpp b/cpp/telemetry/script_executor/src/tests/JniUtilsTestHelper.cpp
new file mode 100644
index 0000000..9e2c43a
--- /dev/null
+++ b/cpp/telemetry/script_executor/src/tests/JniUtilsTestHelper.cpp
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2021, 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 "JniUtils.h"
+#include "LuaEngine.h"
+#include "jni.h"
+
+#include <cstdint>
+#include <cstring>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+namespace script_executor {
+namespace {
+
+extern "C" {
+
+#include "lua.h"
+
+JNIEXPORT jlong JNICALL
+Java_com_android_car_telemetry_JniUtilsTest_nativeCreateLuaEngine(JNIEnv* env, jobject object) {
+ // Cast first to intptr_t to ensure int can hold the pointer without loss.
+ return static_cast<jlong>(reinterpret_cast<intptr_t>(new LuaEngine()));
+}
+
+JNIEXPORT void JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeDestroyLuaEngine(
+ JNIEnv* env, jobject object, jlong luaEnginePtr) {
+ delete reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+}
+
+JNIEXPORT void JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativePushBundleToLuaTableCaller(
+ JNIEnv* env, jobject object, jlong luaEnginePtr, jobject bundle) {
+ PushBundleToLuaTable(env, reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr)),
+ bundle);
+}
+
+JNIEXPORT jint JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeGetObjectSize(
+ JNIEnv* env, jobject object, jlong luaEnginePtr, jint index) {
+ LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ return lua_rawlen(engine->GetLuaState(), static_cast<int>(index));
+}
+
+JNIEXPORT bool JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeHasBooleanValue(
+ JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jboolean value) {
+ const char* rawKey = env->GetStringUTFChars(key, nullptr);
+ LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ auto* luaState = engine->GetLuaState();
+ lua_pushstring(luaState, rawKey);
+ env->ReleaseStringUTFChars(key, rawKey);
+ lua_gettable(luaState, -2);
+ bool result = false;
+ if (!lua_isboolean(luaState, -1))
+ result = false;
+ else
+ result = static_cast<bool>(lua_toboolean(luaState, -1)) == static_cast<bool>(value);
+ lua_pop(luaState, 1);
+ return result;
+}
+
+JNIEXPORT bool JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeHasIntValue(
+ JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jint value) {
+ const char* rawKey = env->GetStringUTFChars(key, nullptr);
+ LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ // Assumes the table is on top of the stack.
+ auto* luaState = engine->GetLuaState();
+ lua_pushstring(luaState, rawKey);
+ env->ReleaseStringUTFChars(key, rawKey);
+ lua_gettable(luaState, -2);
+ bool result = false;
+ if (!lua_isinteger(luaState, -1))
+ result = false;
+ else
+ result = lua_tointeger(luaState, -1) == static_cast<int>(value);
+ lua_pop(luaState, 1);
+ return result;
+}
+
+JNIEXPORT bool JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeHasDoubleValue(
+ JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jdouble value) {
+ const char* rawKey = env->GetStringUTFChars(key, nullptr);
+ LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ // Assumes the table is on top of the stack.
+ auto* luaState = engine->GetLuaState();
+ lua_pushstring(luaState, rawKey);
+ env->ReleaseStringUTFChars(key, rawKey);
+ lua_gettable(luaState, -2);
+ bool result = false;
+ if (!lua_isnumber(luaState, -1))
+ result = false;
+ else
+ result = static_cast<double>(lua_tonumber(luaState, -1)) == static_cast<double>(value);
+ lua_pop(luaState, 1);
+ return result;
+}
+
+JNIEXPORT bool JNICALL Java_com_android_car_telemetry_JniUtilsTest_nativeHasStringValue(
+ JNIEnv* env, jobject object, jlong luaEnginePtr, jstring key, jstring value) {
+ const char* rawKey = env->GetStringUTFChars(key, nullptr);
+ LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+ // Assumes the table is on top of the stack.
+ auto* luaState = engine->GetLuaState();
+ lua_pushstring(luaState, rawKey);
+ env->ReleaseStringUTFChars(key, rawKey);
+ lua_gettable(luaState, -2);
+ bool result = false;
+ if (!lua_isstring(luaState, -1)) {
+ result = false;
+ } else {
+ std::string s = lua_tostring(luaState, -1);
+ const char* rawValue = env->GetStringUTFChars(value, nullptr);
+ result = strcmp(lua_tostring(luaState, -1), rawValue) == 0;
+ env->ReleaseStringUTFChars(value, rawValue);
+ }
+ lua_pop(luaState, 1);
+ return result;
+}
+
+} // extern "C"
+
+} // namespace
+} // namespace script_executor
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/tests/carservice_unit_test/Android.bp b/tests/carservice_unit_test/Android.bp
index 57e14d9..cf9d238 100644
--- a/tests/carservice_unit_test/Android.bp
+++ b/tests/carservice_unit_test/Android.bp
@@ -69,6 +69,7 @@
// mockito-target-inline dependency
jni_libs: [
"libdexmakerjvmtiagent",
+ "libscriptexecutorjniutils-test",
"libstaticjvmtiagent",
],
}
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/JniUtilsTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/JniUtilsTest.java
new file mode 100644
index 0000000..67a5ac8
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/JniUtilsTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.telemetry;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Bundle;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class JniUtilsTest {
+
+ private static final String TAG = JniUtilsTest.class.getSimpleName();
+
+ private static final String BOOLEAN_KEY = "boolean_key";
+ private static final String INT_KEY = "int_key";
+ private static final String STRING_KEY = "string_key";
+ private static final String NUMBER_KEY = "number_key";
+
+ private static final boolean BOOLEAN_VALUE = true;
+ private static final double NUMBER_VALUE = 0.1;
+ private static final int INT_VALUE = 10;
+ private static final String STRING_VALUE = "test";
+
+ // Pointer to Lua Engine instantiated in native space.
+ private long mLuaEnginePtr = 0;
+
+ static {
+ System.loadLibrary("scriptexecutorjniutils-test");
+ }
+
+ @Before
+ public void setUp() {
+ mLuaEnginePtr = nativeCreateLuaEngine();
+ }
+
+ @After
+ public void tearDown() {
+ nativeDestroyLuaEngine(mLuaEnginePtr);
+ }
+
+ // Simply invokes PushBundleToLuaTable native method under test.
+ private native void nativePushBundleToLuaTableCaller(long luaEnginePtr, Bundle bundle);
+
+ // Creates an instance of LuaEngine on the heap and returns the pointer.
+ private native long nativeCreateLuaEngine();
+
+ // Destroys instance of LuaEngine on the native side at provided memory address.
+ private native void nativeDestroyLuaEngine(long luaEnginePtr);
+
+ // Returns size of a Lua object located at the specified position on the stack.
+ private native int nativeGetObjectSize(long luaEnginePtr, int index);
+
+ /*
+ * Family of methods to check if the table on top of the stack has
+ * the given value under provided key.
+ */
+ private native boolean nativeHasBooleanValue(long luaEnginePtr, String key, boolean value);
+ private native boolean nativeHasStringValue(long luaEnginePtr, String key, String value);
+ private native boolean nativeHasIntValue(long luaEnginePtr, String key, int value);
+ private native boolean nativeHasDoubleValue(long luaEnginePtr, String key, double value);
+
+ @Test
+ public void pushBundleToLuaTable_nullBundleMakesEmptyLuaTable() {
+ Log.d(TAG, "Using Lua Engine with address " + mLuaEnginePtr);
+ nativePushBundleToLuaTableCaller(mLuaEnginePtr, null);
+ // Get the size of the object on top of the stack,
+ // which is where our table is supposed to be.
+ assertThat(nativeGetObjectSize(mLuaEnginePtr, 1)).isEqualTo(0);
+ }
+
+ @Test
+ public void pushBundleToLuaTable_valuesOfDifferentTypes() {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE);
+ bundle.putInt(INT_KEY, INT_VALUE);
+ bundle.putDouble(NUMBER_KEY, NUMBER_VALUE);
+ bundle.putString(STRING_KEY, STRING_VALUE);
+
+ // Invokes the corresponding helper method to convert the bundle
+ // to Lua table on Lua stack.
+ nativePushBundleToLuaTableCaller(mLuaEnginePtr, bundle);
+
+ // Check contents of Lua table.
+ assertThat(nativeHasBooleanValue(mLuaEnginePtr, BOOLEAN_KEY, BOOLEAN_VALUE)).isTrue();
+ assertThat(nativeHasIntValue(mLuaEnginePtr, INT_KEY, INT_VALUE)).isTrue();
+ assertThat(nativeHasDoubleValue(mLuaEnginePtr, NUMBER_KEY, NUMBER_VALUE)).isTrue();
+ assertThat(nativeHasStringValue(mLuaEnginePtr, STRING_KEY, STRING_VALUE)).isTrue();
+ }
+
+
+ @Test
+ public void pushBundleToLuaTable_wrongKey() {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(BOOLEAN_KEY, BOOLEAN_VALUE);
+
+ // Invokes the corresponding helper method to convert the bundle
+ // to Lua table on Lua stack.
+ nativePushBundleToLuaTableCaller(mLuaEnginePtr, bundle);
+
+ // Check contents of Lua table.
+ assertThat(nativeHasBooleanValue(mLuaEnginePtr, "wrong key", BOOLEAN_VALUE)).isFalse();
+ }
+}