Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1d818336 authored by Jerome Gaillard's avatar Jerome Gaillard
Browse files

Implements a basic version of AndroidRuntime for host

This creates a simple implementation of AndroidRuntime that can be used
when running libandroid_runtime on host. It is automatically created and
run when the library is loaded through JNI.
This removes any specific code related to the way layoutlib is setup so
that this can be used and customized for different host running
environments.

Flag: NONE host-only change
Bug: 322360037
Test: load libandroid_runtime for host
Change-Id: I326d3d24a22437ad6df8d2e986087299d56c1d87
parent b564eb41
Loading
Loading
Loading
Loading
+109 −182
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android/graphics/jni_runtime.h>
#include <android_runtime/AndroidRuntime.h>
#include <jni_wrappers.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/jni_macros.h>
#include <unicode/putil.h>
@@ -27,9 +29,6 @@
#include <unordered_map>
#include <vector>

#include "android_view_InputDevice.h"
#include "core_jni_helpers.h"
#include "jni.h"
#ifdef _WIN32
#include <windows.h>
#else
@@ -38,8 +37,6 @@
#include <sys/stat.h>
#endif

#include <iostream>

using namespace std;

/*
@@ -49,12 +46,6 @@ using namespace std;
 * (see AndroidRuntime.cpp).
 */

static JavaVM* javaVM;
static jclass bridge;
static jclass layoutLog;
static jmethodID getLogId;
static jmethodID logMethodId;

extern int register_android_os_Binder(JNIEnv* env);
extern int register_libcore_util_NativeAllocationRegistry_Delegate(JNIEnv* env);

@@ -168,28 +159,9 @@ static int register_jni_procs(const std::unordered_map<std::string, RegJNIRec>&
        }
    }

    if (register_android_graphics_classes(env) < 0) {
        return -1;
    }

    return 0;
}

int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
                                          const JNINativeMethod* gMethods, int numMethods) {
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}

JNIEnv* AndroidRuntime::getJNIEnv() {
    JNIEnv* env;
    if (javaVM->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) return nullptr;
    return env;
}

JavaVM* AndroidRuntime::getJavaVM() {
    return javaVM;
}

static vector<string> parseCsv(const string& csvString) {
    vector<string> result;
    istringstream stream(csvString);
@@ -200,29 +172,6 @@ static vector<string> parseCsv(const string& csvString) {
    return result;
}

void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file,
                     unsigned int line, const char* message) {
    JNIEnv* env = AndroidRuntime::getJNIEnv();
    jint logPrio = severity;
    jstring tagString = env->NewStringUTF(tag);
    jstring messageString = env->NewStringUTF(message);

    jobject bridgeLog = env->CallStaticObjectMethod(bridge, getLogId);

    env->CallVoidMethod(bridgeLog, logMethodId, logPrio, tagString, messageString);

    env->DeleteLocalRef(tagString);
    env->DeleteLocalRef(messageString);
    env->DeleteLocalRef(bridgeLog);
}

void LayoutlibAborter(const char* abort_message) {
    // Layoutlib should not call abort() as it would terminate Studio.
    // Throw an exception back to Java instead.
    JNIEnv* env = AndroidRuntime::getJNIEnv();
    jniThrowRuntimeException(env, "The Android framework has encountered a fatal error");
}

// This method has been copied/adapted from system/core/init/property_service.cpp
// If the ro.product.cpu.abilist* properties have not been explicitly
// set, derive them from ro.system.product.cpu.abilist* properties.
@@ -311,62 +260,49 @@ static void* mmapFile(const char* dataFilePath) {
#endif
}

static bool init_icu(const char* dataPath) {
    void* addr = mmapFile(dataPath);
// Loads the ICU data file from the location specified in the system property ro.icu.data.path
static void loadIcuData() {
    string icuPath = base::GetProperty("ro.icu.data.path", "");
    if (!icuPath.empty()) {
        // Set the location of ICU data
        void* addr = mmapFile(icuPath.c_str());
        UErrorCode err = U_ZERO_ERROR;
        udata_setCommonData(addr, &err);
        if (err != U_ZERO_ERROR) {
        return false;
            ALOGE("Unable to load ICU data\n");
        }
    }
    return true;
}

// Creates an array of InputDevice from key character map files
static void init_keyboard(JNIEnv* env, const vector<string>& keyboardPaths) {
    jclass inputDevice = FindClassOrDie(env, "android/view/InputDevice");
    jobjectArray inputDevicesArray =
            env->NewObjectArray(keyboardPaths.size(), inputDevice, nullptr);
    int keyboardId = 1;

    for (const string& path : keyboardPaths) {
        base::Result<std::shared_ptr<KeyCharacterMap>> charMap =
                KeyCharacterMap::load(path, KeyCharacterMap::Format::BASE);
static int register_android_core_classes(JNIEnv* env) {
    jclass system = FindClassOrDie(env, "java/lang/System");
    jmethodID getPropertyMethod =
            GetStaticMethodIDOrDie(env, system, "getProperty",
                                   "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");

        InputDeviceInfo info = InputDeviceInfo();
        info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(),
                        "keyboard " + std::to_string(keyboardId), true, false,
                        ui::LogicalDisplayId::DEFAULT);
        info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
        info.setKeyCharacterMap(*charMap);
    // Get the names of classes that need to register their native methods
    auto nativesClassesJString =
            (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
                                                 env->NewStringUTF("core_native_classes"),
                                                 env->NewStringUTF(""));
    const char* nativesClassesArray = env->GetStringUTFChars(nativesClassesJString, nullptr);
    string nativesClassesString(nativesClassesArray);
    vector<string> classesToRegister = parseCsv(nativesClassesString);
    env->ReleaseStringUTFChars(nativesClassesJString, nativesClassesArray);

        jobject inputDeviceObj = android_view_InputDevice_create(env, info);
        if (inputDeviceObj) {
            env->SetObjectArrayElement(inputDevicesArray, keyboardId - 1, inputDeviceObj);
            env->DeleteLocalRef(inputDeviceObj);
        }
        keyboardId++;
    if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
        return JNI_ERR;
    }

    if (bridge == nullptr) {
        bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
        bridge = MakeGlobalRefOrDie(env, bridge);
    }
    jmethodID setInputManager = GetStaticMethodIDOrDie(env, bridge, "setInputManager",
                                                       "([Landroid/view/InputDevice;)V");
    env->CallStaticVoidMethod(bridge, setInputManager, inputDevicesArray);
    env->DeleteLocalRef(inputDevicesArray);
    return 0;
}

} // namespace android

using namespace android;

// Called right before aborting by LOG_ALWAYS_FATAL. Print the pending exception.
void abort_handler(const char* abort_message) {
    ALOGE("About to abort the process...");

    JNIEnv* env = NULL;
    if (javaVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
    JNIEnv* env = AndroidRuntime::getJNIEnv();
    if (env == nullptr) {
        ALOGE("vm->GetEnv() failed");
        return;
    }
@@ -377,107 +313,98 @@ void abort_handler(const char* abort_message) {
    ALOGE("Aborting because: %s", abort_message);
}

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
    javaVM = vm;
    JNIEnv* env = nullptr;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    __android_log_set_aborter(abort_handler);
// ------------------ Host implementation of AndroidRuntime ------------------

    init_android_graphics();
/*static*/ JavaVM* AndroidRuntime::mJavaVM;

    // Configuration is stored as java System properties.
    // Get a reference to System.getProperty
    jclass system = FindClassOrDie(env, "java/lang/System");
    jmethodID getPropertyMethod =
            GetStaticMethodIDOrDie(env, system, "getProperty",
                                   "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");

    // Java system properties that contain LayoutLib config. The initial values in the map
    // are the default values if the property is not specified.
    std::unordered_map<std::string, std::string> systemProperties =
            {{"core_native_classes", ""},
             {"register_properties_during_load", ""},
             {"icu.data.path", ""},
             {"use_bridge_for_logging", ""},
             {"keyboard_paths", ""}};

    for (auto& [name, defaultValue] : systemProperties) {
        jstring propertyString =
                (jstring)env->CallStaticObjectMethod(system, getPropertyMethod,
                                                     env->NewStringUTF(name.c_str()),
                                                     env->NewStringUTF(defaultValue.c_str()));
        const char* propertyChars = env->GetStringUTFChars(propertyString, 0);
        systemProperties[name] = string(propertyChars);
        env->ReleaseStringUTFChars(propertyString, propertyChars);
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env, const char* className,
                                                     const JNINativeMethod* gMethods,
                                                     int numMethods) {
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
    // Get the names of classes that need to register their native methods
    vector<string> classesToRegister = parseCsv(systemProperties["core_native_classes"]);

    if (systemProperties["register_properties_during_load"] == "true") {
        // Set the system properties first as they could be used in the static initialization of
        // other classes
        if (register_android_os_SystemProperties(env) < 0) {
            return JNI_ERR;
/*static*/ JNIEnv* AndroidRuntime::getJNIEnv() {
    JNIEnv* env;
    JavaVM* vm = AndroidRuntime::getJavaVM();
    if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        return nullptr;
    }
        classesToRegister.erase(find(classesToRegister.begin(), classesToRegister.end(),
                                     "android.os.SystemProperties"));
        bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
        bridge = MakeGlobalRefOrDie(env, bridge);
        jmethodID setSystemPropertiesMethod =
                GetStaticMethodIDOrDie(env, bridge, "setSystemProperties", "()V");
        env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod);
        property_initialize_ro_cpu_abilist();
    return env;
}

    if (register_jni_procs(gRegJNIMap, classesToRegister, env) < 0) {
        return JNI_ERR;
/*static*/ JavaVM* AndroidRuntime::getJavaVM() {
    return mJavaVM;
}

    if (!systemProperties["icu.data.path"].empty()) {
        // Set the location of ICU data
        bool icuInitialized = init_icu(systemProperties["icu.data.path"].c_str());
        if (!icuInitialized) {
/*static*/ int AndroidRuntime::startReg(JNIEnv* env) {
    if (register_android_core_classes(env) < 0) {
        return JNI_ERR;
    }
    if (register_android_graphics_classes(env) < 0) {
        return JNI_ERR;
    }

    if (systemProperties["use_bridge_for_logging"] == "true") {
        layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog");
        layoutLog = MakeGlobalRefOrDie(env, layoutLog);
        logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework",
                                       "(ILjava/lang/String;Ljava/lang/String;)V");
        if (bridge == nullptr) {
            bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
            bridge = MakeGlobalRefOrDie(env, bridge);
    return 0;
}
        getLogId = GetStaticMethodIDOrDie(env, bridge, "getLog",
                                          "()Lcom/android/ide/common/rendering/api/ILayoutLog;");
        android::base::SetLogger(LayoutlibLogger);
        android::base::SetAborter(LayoutlibAborter);
    } else {
        // initialize logging, so ANDROD_LOG_TAGS env variable is respected
        android::base::InitLogging(nullptr, android::base::StderrLogger);

void AndroidRuntime::onVmCreated(JNIEnv* env) {
    env->GetJavaVM(&mJavaVM);
}

void AndroidRuntime::onStarted() {
    property_initialize_ro_cpu_abilist();
    loadIcuData();

    // Use English locale for number format to ensure correct parsing of floats when using strtof
    setlocale(LC_NUMERIC, "en_US.UTF-8");
}

    if (!systemProperties["keyboard_paths"].empty()) {
        vector<string> keyboardPaths = parseCsv(systemProperties["keyboard_paths"]);
        init_keyboard(env, keyboardPaths);
    } else {
        fprintf(stderr, "Skip initializing keyboard\n");
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote) {
    JNIEnv* env = AndroidRuntime::getJNIEnv();
    // Register native functions.
    if (startReg(env) < 0) {
        ALOGE("Unable to register all android native methods\n");
    }
    onStarted();
}

    return JNI_VERSION_1_6;
AndroidRuntime::AndroidRuntime(char* argBlockStart, const size_t argBlockLength)
      : mExitWithoutCleanup(false), mArgBlockStart(argBlockStart), mArgBlockLength(argBlockLength) {
    init_android_graphics();
}

AndroidRuntime::~AndroidRuntime() {}

// Version of AndroidRuntime to run on host
class HostRuntime : public AndroidRuntime {
public:
    HostRuntime() : AndroidRuntime(nullptr, 0) {}

    void onVmCreated(JNIEnv* env) override {
        AndroidRuntime::onVmCreated(env);
        // initialize logging, so ANDROD_LOG_TAGS env variable is respected
        android::base::InitLogging(nullptr, android::base::StderrLogger, abort_handler);
    }

JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) {
    void onStarted() override {
        AndroidRuntime::onStarted();
    }
};

} // namespace android

using namespace android;

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
    JNIEnv* env = nullptr;
    vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
    env->DeleteGlobalRef(bridge);
    env->DeleteGlobalRef(layoutLog);
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    Vector<String8> args;
    HostRuntime runtime;

    runtime.onVmCreated(env);
    runtime.start("HostRuntime", args, false);

    return JNI_VERSION_1_6;
}