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