1 module dlangui.platforms.android.imm;
2 
3 version(Android):
4 
5 import jni;
6 import android.android_native_app_glue;
7 import android.input;
8 
9 import dlangui.core.logger;
10 
11 
12 alias IMMResult = int;
13 // values from InputMethodManager.java
14 private enum : IMMResult
15 {
16     RESULT_UNCHANGED_SHOWN = 0,
17     RESULT_UNCHANGED_HIDDEN = 1,
18     RESULT_SHOWN = 2,
19     RESULT_HIDDEN = 3,
20 }
21 
22 
23 alias IMMFlags = int;
24 // values from InputMethodManager.java
25 private enum : IMMFlags
26 {
27     SHOW_IMPLICIT = 0x0001,
28     SHOW_FORCED = 0x0002,
29 
30     HIDE_IMPLICIT_ONLY = 0x0001,
31     HIDE_NOT_ALWAYS = 0x0002,
32 }
33 
34 
35 
36 /**
37 * JNI wrapper used with native actitiy to show/hide software keyboard
38 * It relies on java reflection and it might be slow.
39 */
40 void showSoftKeyboard(android_app* app, bool shouldShow)
41 {
42     // The code is based on https://stackoverflow.com/questions/5864790/how-to-show-the-soft-keyboard-on-native-activity
43     // Attaches the current thread to the JVM.
44     jint result;
45     IMMFlags flags;
46 
47     auto javaVM = app.activity.vm;
48     auto env = app.activity.env;
49 
50     JavaVMAttachArgs attachArgs;
51     attachArgs.version_ = JNI_VERSION_1_6;
52     attachArgs.name = "NativeThread";
53     attachArgs.group = null;
54 
55     if ((*javaVM).AttachCurrentThread(javaVM, &env, &attachArgs) == JNI_ERR)
56     {
57         Log.e("showSoftKeyboard Unable to attach to JVM");
58         return;
59     }
60 
61     // Retrieves NativeActivity.
62     jobject nativeActivity = app.activity.clazz;
63     jclass nativeActivityClass = (*env).GetObjectClass(env, nativeActivity);
64 
65     // Retrieves Context.INPUT_METHOD_SERVICE.
66     jclass contextClass = (*env).FindClass(env, "android/content/Context");
67     jfieldID FieldINPUT_METHOD_SERVICE =
68         (*env).GetStaticFieldID(env, contextClass,
69             "INPUT_METHOD_SERVICE", "Ljava/lang/String;");
70     jobject INPUT_METHOD_SERVICE =
71         (*env).GetStaticObjectField(env, contextClass, FieldINPUT_METHOD_SERVICE);
72     //jniCheck(INPUT_METHOD_SERVICE);
73 
74     // Runs getSystemService(Context.INPUT_METHOD_SERVICE).
75     jclass immClass = (*env).FindClass(
76         env, "android/view/inputmethod/InputMethodManager");
77     jmethodID MethodGetSystemService = (*env).GetMethodID(
78         env, nativeActivityClass, "getSystemService",
79         "(Ljava/lang/String;)Ljava/lang/Object;");
80     jobject imm = (*env).CallObjectMethod(
81         env, nativeActivity, MethodGetSystemService,
82         INPUT_METHOD_SERVICE);
83 
84     // Runs getWindow().getDecorView().
85     jmethodID MethodGetWindow = (*env).GetMethodID(
86         env, nativeActivityClass, "getWindow", "()Landroid/view/Window;");
87     jobject window = (*env).CallObjectMethod(
88         env, nativeActivity, MethodGetWindow);
89     jclass windowClass = (*env).FindClass(
90         env, "android/view/Window");
91     jmethodID MethodGetDecorView = (*env).GetMethodID(
92         env, windowClass, "getDecorView", "()Landroid/view/View;");
93     jobject decorView = (*env).CallObjectMethod(
94         env, window, MethodGetDecorView);
95 
96     if (shouldShow) {
97         // Runs imm.showSoftInput(...).
98         jmethodID MethodShowSoftInput = (*env).GetMethodID(
99             env, immClass, "showSoftInput", "(Landroid/view/View;I)Z");
100         jboolean res = (*env).CallBooleanMethod(
101             env, imm, MethodShowSoftInput, decorView, flags);
102     } else {
103         // Runs lWindow.getViewToken()
104         jclass viewClass = (*env).FindClass(
105             env, "android/view/View");
106         jmethodID MethodGetWindowToken = (*env).GetMethodID(
107             env, viewClass, "getWindowToken", "()Landroid/os/IBinder;");
108         jobject binder = (*env).CallObjectMethod(
109             env, decorView, MethodGetWindowToken);
110 
111         // lInputMethodManager.hideSoftInput(...).
112         jmethodID MethodHideSoftInput = (*env).GetMethodID(
113             env, immClass, "hideSoftInputFromWindow",
114             "(Landroid/os/IBinder;I)Z");
115         jboolean res = (*env).CallBooleanMethod(
116             env, imm, MethodHideSoftInput, binder, flags);
117     }
118 
119     // Finished with the JVM.
120     (*javaVM).DetachCurrentThread(javaVM);
121 }
122 
123 
124 int GetUnicodeChar(android_app* app, int eventType, int keyCode, int metaState)
125 {
126     auto javaVM = app.activity.vm;
127     auto env = app.activity.env;
128 
129     JavaVMAttachArgs attachArgs;
130     attachArgs.version_ = JNI_VERSION_1_6;
131     attachArgs.name = "NativeThread";
132     attachArgs.group = null;
133 
134     if ((*javaVM).AttachCurrentThread(javaVM, &env, &attachArgs) == JNI_ERR)
135         return 0;
136 
137     jclass class_key_event = (*env).FindClass(env, "android/view/KeyEvent");
138     int unicodeKey;
139 
140     if(metaState == 0)
141     {
142         jmethodID method_get_unicode_char = (*env).GetMethodID(env, class_key_event, "getUnicodeChar", "()I");
143         jmethodID eventConstructor = (*env).GetMethodID(env, class_key_event, "<init>", "(II)V");
144         jobject eventObj = (*env).NewObject(env, class_key_event, eventConstructor, eventType, keyCode);
145 
146         unicodeKey = (*env).CallIntMethod(env, eventObj, method_get_unicode_char);
147     }
148     else
149     {
150         jmethodID method_get_unicode_char = (*env).GetMethodID(env, class_key_event, "getUnicodeChar", "(I)I");
151         jmethodID eventConstructor = (*env).GetMethodID(env, class_key_event, "<init>", "(II)V");
152         jobject eventObj = (*env).NewObject(env, class_key_event, eventConstructor, eventType, keyCode);
153 
154         unicodeKey = (*env).CallIntMethod(env, eventObj, method_get_unicode_char, metaState);
155     }
156 
157     (*javaVM).DetachCurrentThread(javaVM);
158 
159     return unicodeKey;
160 }
161 
162 
163 // Issue: native app glue seems to mess up the input. 
164 // It is clearly seen in debugger that initally key event do have real input, 
165 //    but second time it is called it is all messed up
166 string GetUnicodeString(android_app* app, AInputEvent* event)
167 {
168     string str;
169     auto javaVM = app.activity.vm;
170     auto env = app.activity.env;
171 
172     JavaVMAttachArgs attachArgs;
173     attachArgs.version_ = JNI_VERSION_1_6;
174     attachArgs.name = "NativeThread";
175     attachArgs.group = null;
176 
177     if ((*javaVM).AttachCurrentThread(javaVM, &env, &attachArgs) == JNI_ERR)
178     {
179         Log.e("showSoftKeyboard Unable to attach to JVM");
180         return null;
181     }
182 
183 
184     jclass class_key_event = (*env).FindClass(env, "android/view/KeyEvent");
185 
186     jmethodID eventConstructor = (*env).GetMethodID(env, class_key_event, "<init>", "(JJIIIIIIII)V");
187     jobject eventObj = (*env).NewObject(env, class_key_event, eventConstructor, 
188         AKeyEvent_getDownTime(event), 
189         AKeyEvent_getEventTime(event), 
190         AKeyEvent_getAction(event), 
191         AKeyEvent_getKeyCode(event), 
192         AKeyEvent_getRepeatCount(event), 
193         AKeyEvent_getMetaState(event),
194         AInputEvent_getDeviceId(event),
195         AKeyEvent_getScanCode(event),
196         AKeyEvent_getFlags(event),
197         AInputEvent_getSource(event)
198     );
199     
200     // this won't work because characters is a member passed on construction and getCharacter() is just a getter
201     jmethodID method_get_characters = (*env).GetMethodID(env, class_key_event, "getCharacters", "()Ljava/lang/String;");
202     if (auto jstr = (*env).CallObjectMethod(env, eventObj, method_get_characters)) {
203         str.length = (*env).GetStringUTFLength(env, jstr);
204         (*env).GetStringUTFRegion(env, jstr, 0, str.length, cast(char*)str.ptr);
205     }
206 
207     {
208         jmethodID method_get_unicode_char = (*env).GetMethodID(env, class_key_event, "getUnicodeChar", "()I");
209         int unicodeKey = (*env).CallIntMethod(env, eventObj, method_get_unicode_char);
210         if (str.length == 0) {
211             import std.conv : to;
212             dchar[] tmp;
213             tmp ~= unicodeKey;
214             str = to!string(tmp);
215         }
216     }
217     
218     (*javaVM).DetachCurrentThread(javaVM);
219 
220     return str;
221 }