xref: /aosp_15_r20/frameworks/layoutlib/jni/LayoutlibLoader.cpp (revision fc3927be90a325f95c74a9043993a80ef388dc46)
1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <android-base/logging.h>
18 #include <android-base/properties.h>
19 #include <android_runtime/AndroidRuntime.h>
20 #include <android_view_InputDevice.h>
21 #include <jni_wrappers.h>
22 
23 #include <clocale>
24 #include <sstream>
25 #include <unordered_map>
26 #include <vector>
27 
28 using namespace std;
29 
30 static jclass bridge;
31 static jclass layoutLog;
32 static jmethodID getLogId;
33 static jmethodID logMethodId;
34 
35 namespace android {
36 
37 extern int register_android_view_LayoutlibRenderer(JNIEnv* env);
38 
39 #define REG_JNI(name) \
40     { name }
41 struct RegJNIRec {
42     int (*mProc)(JNIEnv*);
43 };
44 
45 static const RegJNIRec gRegJNI[] = {
46         REG_JNI(register_android_view_LayoutlibRenderer),
47 };
48 
register_jni_procs(JNIEnv * env)49 int register_jni_procs(JNIEnv* env) {
50     for (size_t i = 0; i < NELEM(android::gRegJNI); i++) {
51         if (android::gRegJNI[i].mProc(env) < 0) {
52             return -1;
53         }
54     }
55 
56     return 0;
57 }
58 
parseCsv(const string & csvString)59 static vector<string> parseCsv(const string& csvString) {
60     vector<string> result;
61     istringstream stream(csvString);
62     string segment;
63     while (getline(stream, segment, ',')) {
64         result.push_back(segment);
65     }
66     return result;
67 }
68 
69 // Creates an array of InputDevice from key character map files
init_keyboard(const vector<string> & keyboardPaths)70 static void init_keyboard(const vector<string>& keyboardPaths) {
71     JNIEnv* env = AndroidRuntime::getJNIEnv();
72     jclass inputDevice = FindClassOrDie(env, "android/view/InputDevice");
73     jobjectArray inputDevicesArray =
74             env->NewObjectArray(keyboardPaths.size(), inputDevice, nullptr);
75     int keyboardId = 1;
76 
77     for (const string& path : keyboardPaths) {
78         base::Result<std::unique_ptr<KeyCharacterMap>> charMap =
79                 KeyCharacterMap::load(path, KeyCharacterMap::Format::BASE);
80 
81         InputDeviceInfo info = InputDeviceInfo();
82         info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(),
83                         "keyboard " + std::to_string(keyboardId), true, false,
84                         ui::LogicalDisplayId::DEFAULT);
85         info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
86         info.setKeyCharacterMap(std::move(*charMap));
87 
88         jobject inputDeviceObj = android_view_InputDevice_create(env, info);
89         if (inputDeviceObj) {
90             env->SetObjectArrayElement(inputDevicesArray, keyboardId - 1, inputDeviceObj);
91             env->DeleteLocalRef(inputDeviceObj);
92         }
93         keyboardId++;
94     }
95 
96     if (bridge == nullptr) {
97         bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
98         bridge = MakeGlobalRefOrDie(env, bridge);
99     }
100     jmethodID setInputManager = GetStaticMethodIDOrDie(env, bridge, "setInputManager",
101                                                        "([Landroid/view/InputDevice;)V");
102     env->CallStaticVoidMethod(bridge, setInputManager, inputDevicesArray);
103     env->DeleteLocalRef(inputDevicesArray);
104 }
105 
LayoutlibLogger(base::LogId,base::LogSeverity severity,const char * tag,const char * file,unsigned int line,const char * message)106 void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file,
107                      unsigned int line, const char* message) {
108     JNIEnv* env = AndroidRuntime::getJNIEnv();
109     jint logPrio = severity;
110     jstring tagString = env->NewStringUTF(tag);
111     jstring messageString = env->NewStringUTF(message);
112 
113     jobject bridgeLog = env->CallStaticObjectMethod(bridge, getLogId);
114 
115     env->CallVoidMethod(bridgeLog, logMethodId, logPrio, tagString, messageString);
116 
117     env->DeleteLocalRef(tagString);
118     env->DeleteLocalRef(messageString);
119     env->DeleteLocalRef(bridgeLog);
120 }
121 
LayoutlibAborter(const char * abort_message)122 void LayoutlibAborter(const char* abort_message) {
123     // Layoutlib should not call abort() as it would terminate Studio.
124     // Throw an exception back to Java instead.
125     JNIEnv* env = AndroidRuntime::getJNIEnv();
126     jniThrowRuntimeException(env, "The Android framework has encountered a fatal error");
127 }
128 
129 class LayoutlibRuntime : public AndroidRuntime {
130 public:
LayoutlibRuntime()131     LayoutlibRuntime() : AndroidRuntime(nullptr, 0) {}
132 
onVmCreated(JNIEnv * env)133     void onVmCreated(JNIEnv* env) override {
134         AndroidRuntime::onVmCreated(env);
135         android::base::SetLogger(LayoutlibLogger);
136         android::base::SetAborter(LayoutlibAborter);
137     }
138 
onStarted()139     void onStarted() override {
140         JNIEnv* env = AndroidRuntime::getJNIEnv();
141         register_jni_procs(env);
142 
143         jmethodID setSystemPropertiesMethod =
144                 GetStaticMethodIDOrDie(env, bridge, "setSystemProperties", "()V");
145         env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod);
146 
147         string keyboard_paths = base::GetProperty("ro.keyboard.paths", "");
148         vector<string> keyboardPaths = parseCsv(keyboard_paths);
149         init_keyboard(keyboardPaths);
150 
151         AndroidRuntime::onStarted();
152     }
153 };
154 
155 } // namespace android
156 
157 using namespace android;
158 
JNI_OnLoad(JavaVM * vm,void *)159 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
160     JNIEnv* env = nullptr;
161     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
162         return JNI_ERR;
163     }
164 
165     layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog");
166     layoutLog = MakeGlobalRefOrDie(env, layoutLog);
167     logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework",
168                                    "(ILjava/lang/String;Ljava/lang/String;)V");
169     bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
170     bridge = MakeGlobalRefOrDie(env, bridge);
171     getLogId = GetStaticMethodIDOrDie(env, bridge, "getLog",
172                                       "()Lcom/android/ide/common/rendering/api/ILayoutLog;");
173 
174     Vector<String8> args;
175     LayoutlibRuntime runtime;
176 
177     runtime.onVmCreated(env);
178     runtime.start("LayoutlibRuntime", args, false);
179 
180     return JNI_VERSION_1_6;
181 }
182 
JNI_OnUnload(JavaVM * vm,void *)183 JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) {
184     JNIEnv* env = nullptr;
185     vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
186     env->DeleteGlobalRef(bridge);
187     env->DeleteGlobalRef(layoutLog);
188 }
189