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
parseCsv(const string & csvString)37 static vector<string> parseCsv(const string& csvString) {
38 vector<string> result;
39 istringstream stream(csvString);
40 string segment;
41 while (getline(stream, segment, ',')) {
42 result.push_back(segment);
43 }
44 return result;
45 }
46
47 // Creates an array of InputDevice from key character map files
init_keyboard(const vector<string> & keyboardPaths)48 static void init_keyboard(const vector<string>& keyboardPaths) {
49 JNIEnv* env = AndroidRuntime::getJNIEnv();
50 jclass inputDevice = FindClassOrDie(env, "android/view/InputDevice");
51 jobjectArray inputDevicesArray =
52 env->NewObjectArray(keyboardPaths.size(), inputDevice, nullptr);
53 int keyboardId = 1;
54
55 for (const string& path : keyboardPaths) {
56 base::Result<std::unique_ptr<KeyCharacterMap>> charMap =
57 KeyCharacterMap::load(path, KeyCharacterMap::Format::BASE);
58
59 InputDeviceInfo info = InputDeviceInfo();
60 info.initialize(keyboardId, 0, 0, InputDeviceIdentifier(),
61 "keyboard " + std::to_string(keyboardId), true, false,
62 ui::LogicalDisplayId::DEFAULT);
63 info.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
64 info.setKeyCharacterMap(std::move(*charMap));
65
66 jobject inputDeviceObj = android_view_InputDevice_create(env, info);
67 if (inputDeviceObj) {
68 env->SetObjectArrayElement(inputDevicesArray, keyboardId - 1, inputDeviceObj);
69 env->DeleteLocalRef(inputDeviceObj);
70 }
71 keyboardId++;
72 }
73
74 if (bridge == nullptr) {
75 bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
76 bridge = MakeGlobalRefOrDie(env, bridge);
77 }
78 jmethodID setInputManager = GetStaticMethodIDOrDie(env, bridge, "setInputManager",
79 "([Landroid/view/InputDevice;)V");
80 env->CallStaticVoidMethod(bridge, setInputManager, inputDevicesArray);
81 env->DeleteLocalRef(inputDevicesArray);
82 }
83
LayoutlibLogger(base::LogId,base::LogSeverity severity,const char * tag,const char * file,unsigned int line,const char * message)84 void LayoutlibLogger(base::LogId, base::LogSeverity severity, const char* tag, const char* file,
85 unsigned int line, const char* message) {
86 JNIEnv* env = AndroidRuntime::getJNIEnv();
87 jint logPrio = severity;
88 jstring tagString = env->NewStringUTF(tag);
89 jstring messageString = env->NewStringUTF(message);
90
91 jobject bridgeLog = env->CallStaticObjectMethod(bridge, getLogId);
92
93 env->CallVoidMethod(bridgeLog, logMethodId, logPrio, tagString, messageString);
94
95 env->DeleteLocalRef(tagString);
96 env->DeleteLocalRef(messageString);
97 env->DeleteLocalRef(bridgeLog);
98 }
99
LayoutlibAborter(const char * abort_message)100 void LayoutlibAborter(const char* abort_message) {
101 // Layoutlib should not call abort() as it would terminate Studio.
102 // Throw an exception back to Java instead.
103 JNIEnv* env = AndroidRuntime::getJNIEnv();
104 jniThrowRuntimeException(env, "The Android framework has encountered a fatal error");
105 }
106
107 class LayoutlibRuntime : public AndroidRuntime {
108 public:
LayoutlibRuntime()109 LayoutlibRuntime() : AndroidRuntime(nullptr, 0) {}
110
onVmCreated(JNIEnv * env)111 void onVmCreated(JNIEnv* env) override {
112 AndroidRuntime::onVmCreated(env);
113 android::base::SetLogger(LayoutlibLogger);
114 android::base::SetAborter(LayoutlibAborter);
115 }
116
onStarted()117 void onStarted() override {
118 JNIEnv* env = AndroidRuntime::getJNIEnv();
119
120 jmethodID setSystemPropertiesMethod =
121 GetStaticMethodIDOrDie(env, bridge, "setSystemProperties", "()V");
122 env->CallStaticVoidMethod(bridge, setSystemPropertiesMethod);
123
124 string keyboard_paths = base::GetProperty("ro.keyboard.paths", "");
125 vector<string> keyboardPaths = parseCsv(keyboard_paths);
126 init_keyboard(keyboardPaths);
127
128 AndroidRuntime::onStarted();
129 }
130 };
131
132 } // namespace android
133
134 using namespace android;
135
JNI_OnLoad(JavaVM * vm,void *)136 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
137 JNIEnv* env = nullptr;
138 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
139 return JNI_ERR;
140 }
141
142 layoutLog = FindClassOrDie(env, "com/android/ide/common/rendering/api/ILayoutLog");
143 layoutLog = MakeGlobalRefOrDie(env, layoutLog);
144 logMethodId = GetMethodIDOrDie(env, layoutLog, "logAndroidFramework",
145 "(ILjava/lang/String;Ljava/lang/String;)V");
146 bridge = FindClassOrDie(env, "com/android/layoutlib/bridge/Bridge");
147 bridge = MakeGlobalRefOrDie(env, bridge);
148 getLogId = GetStaticMethodIDOrDie(env, bridge, "getLog",
149 "()Lcom/android/ide/common/rendering/api/ILayoutLog;");
150
151 Vector<String8> args;
152 LayoutlibRuntime runtime;
153
154 runtime.onVmCreated(env);
155 runtime.start("LayoutlibRuntime", args, false);
156
157 return JNI_VERSION_1_6;
158 }
159
JNI_OnUnload(JavaVM * vm,void *)160 JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*) {
161 JNIEnv* env = nullptr;
162 vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
163 env->DeleteGlobalRef(bridge);
164 env->DeleteGlobalRef(layoutLog);
165 }
166