Receive complete android unicode input in C/C++ - android

(Android, NDK, C++, OpenGL ES)
I need a way to reliably receive the text input from a (soft)keyboard.
The solution can be through Java using a NativeActivity subclass, or anything which works.
At the end I need whatever text is being typed, so I can render it myself with OpenGL
Some background:
Up until now I was triggering the soft keyboard by calling showSoftInput or hideSoftInputFromWindow thought JNI. This never failed so far.
However, the problem is the native activity will not send all characters. Especially some unicode characters outside of ASCII range, or some motion soft keyboard won't work (AKeyEvent_getKeyCode)
It used to be possible to get some of those other unicode characters why checking for KeyEvent.ACTION_MULTIPLE and reading a string of characters.
But even this won't work reliably anymore.
So far I failed to find an alternative method.
I experimented with programmatically adding a EditText, but never got it to work. Even trying to add a simple Button resulted in the OpenGL view to no longer being rendered.
On iOS I worked around it by having a hiding edit box, which I simply activated to make the keyboard show up. I would then read out the edit box and use the string to render myself in OpenGL.

I have the same issues, and I have solved it using a 'Character' event that I process separately from the InputEvent.
The problem is this: AKeyEvent_getKeyCode doesn't return the KeyCode for some softkey events, notably the expanded 'unicode/latin' characters when you hold down a key. This prevents the methods #Shammi and #eozgonul from working because the KeyEvent reconstructed on the Java side doesn't have enough information to get a unicode character.
Another issue is that the InputQueue is drained on the C++/Native side before the dispatchKeyEvent event(s) are fired. This means that the KEYDOWN/KEYUP events all fired before the Java code can process the events. (They are not interleaved).
My solution is to capture the unicode characters on the Java side by overriding dispatchKeyEvent and sending the characters to a Queue<Integer> queueLastInputCharacter = new ConcurrentLinkedQueue<Integer>();
// [JAVA]
#Override
public boolean dispatchKeyEvent (KeyEvent event)
{
int metaState = event.getMetaState();
int unichar = event.getUnicodeChar(metaState);
// We are queuing the Unicode version of the characters for
// sending to the app during processEvents() call.
// We Queue the KeyDown and ActionMultiple Event UnicodeCharacters
if(event.getAction()==KeyEvent.ACTION_DOWN){
if(unichar != 0){
queueLastInputCharacter.offer(Integer.valueOf(unichar));
}
else{
unichar = event.getUnicodeChar();
if(unichar != 0){
queueLastInputCharacter.offer(Integer.valueOf(unichar));
}
else if (event.getDisplayLabel() != 0){
String aText = new String();
aText = "";
aText += event.getDisplayLabel();
queueLastInputCharacter.offer(Integer.valueOf(Character.codePointAt(aText, 0)));
}
else
queueLastInputCharacter.offer(Integer.valueOf(0));
}
}
else if(event.getAction()==KeyEvent.ACTION_MULTIPLE){
unichar = (Character.codePointAt(event.getCharacters(), 0));
queueLastInputCharacter.offer(Integer.valueOf(unichar));
}
return super.dispatchKeyEvent(event);
}
The concurrent queue is going to let the threads play nice together.
I have a Java side method that returns the last input character:
// [JAVA]
public int getLastUnicodeChar(){
if(!queueLastInputCharacter.isEmpty())
return queueLastInputCharacter.poll().intValue();
return 0;
}
At the end of my looper code, I tacked on an extra check to see if the queue retained any unicode characters:
// [C++]
int ident;
int events;
struct android_poll_source* source;
// If not rendering, we will block 250ms waiting for events.
// If animating, we loop until all events are read, then continue
// to draw the next frame of animation.
while ((ident = ALooper_pollAll(((nv_app_status_focused(_lpApp)) ? 1 : 250),
NULL,
&events,
(void**)&source)) >= 0)
{
// Process this event.
if (source != NULL)
source->process(_lpApp, source);
// Check if we are exiting. If so, dump out
if (!nv_app_status_running(_lpApp))
return;
}
static int modtime = 10; // let's not run on every call
if(--modtime == 0) {
long uniChar = androidUnicodeCharFromKeyEvent();
while (uniChar != 0) {
KEvent kCharEvent; // Game engine event
kCharEvent.ptkKey = K_VK_ERROR;
kCharEvent.unicodeChar = uniChar;
kCharEvent.character = uniChar;
/* Send unicode char */
kCharEvent.type = K_EVENT_UNICHAR;
_lpPortableHandler(&kCharEvent);
if (kCharEvent.character < 127) {
/* Send ascii char for source compatibility as well */
kCharEvent.type = K_EVENT_CHAR;
_lpPortableHandler(&kCharEvent);
}
uniChar = androidUnicodeCharFromKeyEvent();
}
modtime = 10;
}
The androidUnicodeCharFromKeyEvent function is very similar to #Shammi 's GetStringFromAInputEvent method, only use CallIntMethod to return the jint.
Notes
This does require modifying your engine to process character events separate from Key events. Android still has key codes like AKEYCODE_BACK or AKEYCODE_ENTER that are not character events and still need to be handled (and can be handled on the main input looper).
Editboxes, consoles, etc... Things that are expecting user input can be modified to receive a separate character event that builds the string. If you are working on multiple platforms, then you will need to generate these new character events in addition to the normal key input events.

I hope this works for you, worked for me so far.
int GetUnicodeChar(struct android_app* app, int eventType, int keyCode, int metaState)
{
JavaVM* javaVM = app->activity->vm;
JNIEnv* jniEnv = app->activity->env;
JavaVMAttachArgs attachArgs;
attachArgs.version = JNI_VERSION_1_6;
attachArgs.name = "NativeThread";
attachArgs.group = NULL;
jint result = javaVM->AttachCurrentThread(&jniEnv, &attachArgs);
if(result == JNI_ERR)
{
return 0;
}
jclass class_key_event = jniEnv->FindClass("android/view/KeyEvent");
int unicodeKey;
if(metaState == 0)
{
jmethodID method_get_unicode_char = jniEnv->GetMethodID(class_key_event, "getUnicodeChar", "()I");
jmethodID eventConstructor = jniEnv->GetMethodID(class_key_event, "<init>", "(II)V");
jobject eventObj = jniEnv->NewObject(class_key_event, eventConstructor, eventType, keyCode);
unicodeKey = jniEnv->CallIntMethod(eventObj, method_get_unicode_char);
}
else
{
jmethodID method_get_unicode_char = jniEnv->GetMethodID(class_key_event, "getUnicodeChar", "(I)I");
jmethodID eventConstructor = jniEnv->GetMethodID(class_key_event, "<init>", "(II)V");
jobject eventObj = jniEnv->NewObject(class_key_event, eventConstructor, eventType, keyCode);
unicodeKey = jniEnv->CallIntMethod(eventObj, method_get_unicode_char, metaState);
}
javaVM->DetachCurrentThread();
LOGI("Unicode key is: %d", unicodeKey);
return unicodeKey;
}
Just call it from your input handler, my structure is approximately as follows:
switch (AInputEvent_getType(event))
{
case AINPUT_EVENT_TYPE_KEY:
switch (AKeyEvent_getAction(event))
{
case AKEY_EVENT_ACTION_DOWN:
int key = AKeyEvent_getKeyCode(event);
int metaState = AKeyEvent_getMetaState(event);
int uniValue;
if(metaState != 0)
uniValue = GetUnicodeChar(app, AKEY_EVENT_ACTION_DOWN, key, metaState);
else
uniValue = GetUnicodeChar(app, AKEY_EVENT_ACTION_DOWN, key, 0);
Since you stated that you already open the soft keyboard, I don't go into that part but the code is kind of straight forward. I basically use the Java function of class KeyEvent which has GetUnicodeChar function.

Eozgonul's solution worked for me. I adopted it and modified it to split the work between Java and the native side. Basically I extend NativeActivity to derive my own class which allows me to move as much as possible to Java. I also ended up passing all the data from the input event. I wanted to make sure I captured as much as possible in the created KeyEvent object.
package com.MyCompany.MyApp;
import android.os.Bundle;
import android.view.inputmethod.InputMethodManager;
import android.content.Context;
import android.view.KeyEvent;
public class MyNativeActivity extends android.app.NativeActivity
{
// Need this for screen rotation to send configuration changed callbacks to native
#Override
public void onConfigurationChanged( android.content.res.Configuration newConfig )
{
super.onConfigurationChanged( newConfig );
}
public void showKeyboard()
{
InputMethodManager imm = ( InputMethodManager )getSystemService( Context.INPUT_METHOD_SERVICE );
imm.showSoftInput( this.getWindow().getDecorView(), InputMethodManager.SHOW_FORCED );
}
public void hideKeyboard()
{
InputMethodManager imm = ( InputMethodManager )getSystemService( Context.INPUT_METHOD_SERVICE );
imm.hideSoftInputFromWindow( this.getWindow().getDecorView().getWindowToken(), 0 );
}
public String stringFromKeyCode( long downTime, long eventTime,
int eventAction, int keyCode, int repeatCount, int metaState,
int deviceId, int scanCode, int flags, int source )
{
String strReturn;
KeyEvent keyEvent = new KeyEvent( downTime, eventTime, eventAction, keyCode, repeatCount, metaState, deviceId, scanCode, flags, source );
if ( metaState == 0 )
{
int unicodeChar = keyEvent.getUnicodeChar();
if ( eventAction == KeyEvent.ACTION_MULTIPLE && unicodeChar == keyEvent.KEYCODE_UNKNOWN )
{
strReturn = keyEvent.getCharacters();
}
else
{
strReturn = Character.toString( ( char )unicodeChar );
}
}
else
{
strReturn = Character.toString( ( char )( keyEvent.getUnicodeChar( metaState ) ) );
}
return strReturn;
}
}
On the native side...
std::string GetStringFromAInputEvent( android_app* pApp, AInputEvent* pInputEvent )
{
std::string strReturn;
JavaVM* pJavaVM = pApp->activity->vm;
JNIEnv* pJNIEnv = pApp->activity->env;
JavaVMAttachArgs javaVMAttachArgs;
javaVMAttachArgs.version = JNI_VERSION_1_6;
javaVMAttachArgs.name = "NativeThread";
javaVMAttachArgs.group = NULL;
jint jResult;
jResult = pJavaVM->AttachCurrentThread( &pJNIEnv, &javaVMAttachArgs );
if ( jResult != JNI_ERR )
{
// Retrieves NativeActivity.
jobject nativeActivity = pNativeActivity->clazz;
jclass ClassNativeActivity = pJNIEnv->GetObjectClass( nativeActivity );
jmethodID MethodStringFromKeyCode = pJNIEnv->GetMethodID( ClassNativeActivity, "stringFromKeyCode", "(JJIIIIIIII)Ljava/lang/String;" );
jlong jDownTime = AKeyEvent_getDownTime( pInputEvent );
jlong jEventTime = AKeyEvent_getEventTime( pInputEvent );
jint jEventAction = AKeyEvent_getAction( pInputEvent );
jint jKeyCode = AKeyEvent_getKeyCode( pInputEvent );
jint jRepeatCount = AKeyEvent_getRepeatCount( pInputEvent );
jint jMetaState = AKeyEvent_getMetaState( pInputEvent );
jint jDeviceID = AInputEvent_getDeviceId( pInputEvent );
jint jScanCode = AKeyEvent_getScanCode( pInputEvent );
jint jFlags = AKeyEvent_getFlags( pInputEvent );
jint jSource = AInputEvent_getSource( pInputEvent );
jstring jKeyCodeString = ( jstring )pJNIEnv->CallObjectMethod( nativeActivity, MethodStringFromKeyCode,
jDownTime, jEventTime, jEventAction,
jKeyCode, jRepeatCount, jMetaState,
jDeviceID, jScanCode, jFlags, jSource );
const char* keyCodeString = pJNIEnv->GetStringUTFChars( keyCodeString, nullptr );
strReturn = std::string( keyCodeString );
pJNIEnv->ReleaseStringUTFChars( jKeyCodeString, keyCodeString );
// Finished with the JVM.
pJavaVM->DetachCurrentThread();
}
return strReturn;
}
The 2 reasons I went with this approach..
Reduces code syntax complexity by moving code to java and only having you to call one jni wrapper method on the native side.
Java is the preferred Android language and this allows me to quickly iterate on java based solutions. Moreover most existing solutions are in java.

Basically this will solve the issue.
NativeActivity override onKeyDown()
But you'll have to implement some other way than the NDK key input to get the onKeyMultiple's event.getCharacters() string into your code.

Related

c inotify not always triggering event for specific file

int main(int argc, char* argv[]) {
char* p;
struct inotify_event *event;
int sd, gd, fd;
char buffer[BUF_LEN];
fd = inotify_init();
if( fd < 0) {
return 1;
}
gd = inotify_add_watch(fd, "/sys/devices/system/cpu/cpu2/cpufreq/interactive", IN_MODIFY);
sd = inotify_add_watch(fd, "/sys/class/graphics/fb0/show_blank_event", IN_MODIFY);
for(;;) {
read(fd, buffer, BUF_LEN); //Thread block here. Waits for event triggering
event = (struct inotify_event *) buffer; //Get the event
if(event->mask & IN_MODIFY) {
if(event->wd == sd) {
printf("Handle screen event\n");
//pthread_create(&handle_screen_event, NULL, handleScreenEvent, (void*)NULL);
}
if(event->wd == gd) {
printf("Handle gov event\n"); //This is not triggered after some time of being in standby (screen off) mode. Yet the screen event trigger is always fired
//pthread_create(&handle_gov_event, NULL, handleGovEvent, (void*)NULL);
}
}
}
return 0;
}
So here is the code. I call inotify_add_watch() but after a few minutes with the screen off the "handle gov event" never triggers again. "Handle screen event" always triggers though. I have no idea why its behaving this way. I did add a check for IN_IGNORE to see if the file was getting deleted but it never triggered. I just don't understand how one is working but not the other.
I am running this on an android device. Galaxy S7.
Thanks all.

Getting error as libOpenSLES Leaving BufferQueue::Enqueue (SL_RESULT_BUFFER_INSUFFICIENT)

Hi I am working on Android JNI Audio part, below jni code I am calling from android 12 times as per my logic. As soon as 1st clip played I send callback event to android and I am again calling selectClip() to play audio clip. I am able to play 2 times but on third call application throwing error as libOpenSLES Leaving BufferQueue : SL_RESULT_BUFFER_INSUFFICIENT.
Am I missing something in this?
Any pointer on the same to resolve this?
jboolean flag=JNI_TRUE;
jint clipNote = 0;
// select the desired clip and play count, and enqueue the first buffer if idle
JNIEXPORT jboolean JNICALL Java_com_example_nativeaudio_NativeAudio_selectClip(JNIEnv * env, jobject obj,
jint count)
{
// sleep logic
while((clock() - t)/1000 < 2000) {
usleep(10000); // sleep for 10ms
}
if(clipNote < 12) {
if(flag == JNI_TRUE) {
__android_log_print(ANDROID_LOG_DEBUG , "CustomTag", " flag = true : ClipNote : %d",clipNote);
clipNote = clipNote + 1;
nextBuffer = (short *) audio1;
nextSize = sizeof(audio1);
nextCount = count/2000;
if (nextSize > 0) {
// here we only enqueue one buffer because it is a long clip,
// but for streaming playback we would typically enqueue at least 2 buffers to start
SLresult result;
result = (*bqPlayerBufferQueue)->Enqueue(bqPlayerBufferQueue, nextBuffer, nextSize);
if (SL_RESULT_SUCCESS != result) {
return JNI_FALSE;
}
}
// callback to android
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, cls, "callBackStart", "(I)V");
if (mid == 0) {
return;
}
flag=JNI_FALSE;
(*env)->CallVoidMethod(env, obj, mid, clipNote);
} else {
// callback to android
__android_log_print(ANDROID_LOG_DEBUG , "CustomTag", " flag = false");
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid = (*env)->GetMethodID(env, cls, "callBackRelease", "(I)V");
if (mid == 0) {
return;
}
flag = JNI_TRUE;
(*env)->CallVoidMethod(env, obj, mid, count);
}
t = clock();
} else {
SLresult result;
// make sure the asset audio player was created
if (NULL != fdPlayerPlay) {
result = (*fdPlayerPlay)>SetPlayState(fdPlayerPlay,SL_PLAYSTATE_PAUSED);
assert(SL_RESULT_SUCCESS == result);
(void)result;
}
}
return JNI_TRUE;
}
SL_RESULT_BUFFER_INSUFFICIENT error was occurring due to the issue in timing.
Second thread gets started before the first is complete.
The third thread gets started when the first two are in progress. This cannot be handled so it was throwing an error.
The timing was increased sufficient enough to complete the first thread, the issue was solved.
Set numbuffers
Chances are your numbuffers on your SLDataLocator_AndroidSimpleBufferQueue is inappropriately low.
SLDataLocator_AndroidSimpleBufferQueue in_loc;
in_loc.numBuffers = 5; // what number do you have here?
From the OpenSL ES spec:
If the maximum number of buffers specified in the SLDataLocator_BufferQueue structure used as the data source when creating the media object using the CreateAudioPlayer or CreateMidiPlayer method has been reached, the buffer is not added to the buffer queue and SL_RESULT_BUFFER_INSUFFICIENT is returned. At this point the client should wait until it receives a callback notification for a buffer completion at which time it can enqueue the buffer.
Alternatives
If changing your numbuffers to even a very high number doesn't work, ensure that you set your state to playing:
(*player)->SetPlayState( player, SL_PLAYSTATE_PLAYING ); // begin playback
You can call (*bqPlayerBufferQueue)->Clear(bqPlayerBufferQueue); when you stop the player, that will allow the previous thread to shutdown without having to wait on a timer.

Enabling MediaPlayerBuffering event to be fired in android-vlc

I would like to subscribe for the MediaPlayer buffering event in the android vlc app.
I edited the EventHandler class and uncommented the event constant.
public static final int MediaPlayerBuffering = 0x103; // ** uncommented this**
public static final int MediaPlayerPlaying = 0x104;
I then added the the variable in libvlcjni.c
libvlc_event_manager_t *ev = libvlc_media_player_event_manager(mp);
static const libvlc_event_type_t mp_events[] = {
libvlc_MediaPlayerPlaying,
libvlc_MediaPlayerPaused,
libvlc_MediaPlayerEndReached,
libvlc_MediaPlayerStopped,
libvlc_MediaPlayerVout,
libvlc_MediaPlayerPositionChanged,
libvlc_MediaPlayerEncounteredError,
libvlc_MediaPlayerBuffering // **added this here**
};
recompiled the jni to get the so file and then built the vlc app but the event never seems to fire off.
Where else do I have to link to get the event fired when there is a buffering event due to lack of bandwidth.
I can see in logcat that it prints 1001 ms buffered in 6ms. But that is coming from the lower layer and not the java layer
had to add this in the libvlcjni.c file
else if(ev->type == libvlc_MediaPlayeBuffering) {
/* For determining the vout/ES track change */
jstring sData = (*env)->NewStringUTF(env, "data");
(*env)->CallVoidMethod(env, bundle, putFloat, sData, ev->u.media_player_buffer.new_cache);
(*env)->DeleteLocalRef(env, sData);
}
hope this helps someone
else if(ev->type == libvlc_MediaPlayerBuffering) {
/* For determining the vout/ES track change */
jstring sData = (*env)->NewStringUTF(env, "data");
(*env)->CallVoidMethod(env, bundle, putFloat, sData, ev->u.media_player_buffering.new_cache);
(*env)->DeleteLocalRef(env, sData);
}
The answer is ev->u.media_player_buffering.new_cache
In the VLC file mediaPlayer.c
I found this code:
`else if( newval.i_int == INPUT_EVENT_CACHE )
{
event.type = libvlc_MediaPlayerBuffering;
event.u.media_player_buffering.new_cache = (int)(100 * var_GetFloat( p_input, "cache" ));
libvlc_event_send( p_mi->p_event_manager, &event );
}
`
and in libvlc_events.h
/* media instance */
struct
{
float new_cache;
} media_player_buffering;
Then, I compiled it and it worked. Special thanks to my boy Tracy on the coast!

Android 4.1 virtual keyboard freeze

I have the following strange issue that only occurs on Android 4.1: When the virtual keyboard is opened and the user presses the BACK button, my app just freezes and doesn't react any longer. After a while a system dialog box pops up informing me that the app has died and will be closed. I can hardly imagine that this is a bug in Android because hiding the keyboard by pressing the BACK button is elementary functionality that should just work. Still, my code is so small that I can almost completely rule out an error in my code.
Here is my code for you to review:
static int quit = 0;
static void engine_handle_cmd(struct android_app *app, int32_t cmd)
{
switch(cmd) {
case APP_CMD_TERM_WINDOW:
quit = 1;
break;
}
}
static int32_t engine_handle_input(struct android_app *app, AInputEvent *event)
{
switch(AInputEvent_getType(event)) {
case AINPUT_EVENT_TYPE_MOTION: {
int action = AMotionEvent_getAction(event);
if((action & AMOTION_EVENT_ACTION_MASK) == AMOTION_EVENT_ACTION_DOWN) showkeyboard(app);
break;
}
}
return 0;
}
void android_main(struct android_app* state)
{
int events, fd;
struct android_poll_source *source;
app_dummy();
state->onAppCmd = engine_handle_cmd;
state->onInputEvent = engine_handle_input;
while(!quit) {
if(ALooper_pollOnce(-1, &fd, &events, (void **) &source) >= 0) {
if(source) source->process(state, source);
}
}
exit(0);
}
The showkeyboard() function is implemented in JNI like this:
static void showkeyboard(struct android_app* state)
{
// Attaches the current thread to the JVM.
jint lResult;
jint lFlags = 0;
JavaVM *lJavaVM = state->activity->vm;
JNIEnv *lJNIEnv = state->activity->env;
JavaVMAttachArgs lJavaVMAttachArgs;
jobject lNativeActivity, INPUT_METHOD_SERVICE;
jclass ClassNativeActivity, ClassContext;
jfieldID FieldINPUT_METHOD_SERVICE;
JNIEnv *env;
int attached = 0;
// must check if we're already attached!
switch((*lJavaVM)->GetEnv(lJavaVM, (void**) &env, JNI_VERSION_1_6)) {
case JNI_OK:
break;
case JNI_EDETACHED:
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
lResult = (*lJavaVM)->AttachCurrentThread(lJavaVM, &lJNIEnv, &lJavaVMAttachArgs);
if(lResult == JNI_ERR) return;
attached = 1;
break;
case JNI_EVERSION:
return; // Invalid Java version
}
// Retrieves NativeActivity.
lNativeActivity = state->activity->clazz;
ClassNativeActivity = (*lJNIEnv)->GetObjectClass(lJNIEnv, lNativeActivity);
// Retrieves Context.INPUT_METHOD_SERVICE.
ClassContext = (*lJNIEnv)->FindClass(lJNIEnv, "android/content/Context");
FieldINPUT_METHOD_SERVICE = (*lJNIEnv)->GetStaticFieldID(lJNIEnv, ClassContext, "INPUT_METHOD_SERVICE", "Ljava/lang/String;");
INPUT_METHOD_SERVICE = (*lJNIEnv)->GetStaticObjectField(lJNIEnv, ClassContext, FieldINPUT_METHOD_SERVICE);
{
// Runs getSystemService(Context.INPUT_METHOD_SERVICE).
jclass ClassInputMethodManager = (*lJNIEnv)->FindClass(lJNIEnv, "android/view/inputmethod/InputMethodManager");
jmethodID MethodGetSystemService = (*lJNIEnv)->GetMethodID(lJNIEnv, ClassNativeActivity, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
jobject lInputMethodManager = (*lJNIEnv)->CallObjectMethod(lJNIEnv, lNativeActivity, MethodGetSystemService, INPUT_METHOD_SERVICE);
// Runs getWindow().getDecorView().
jmethodID MethodGetWindow = (*lJNIEnv)->GetMethodID(lJNIEnv, ClassNativeActivity, "getWindow", "()Landroid/view/Window;");
jobject lWindow = (*lJNIEnv)->CallObjectMethod(lJNIEnv, lNativeActivity, MethodGetWindow);
jclass ClassWindow = (*lJNIEnv)->FindClass(lJNIEnv, "android/view/Window");
jmethodID MethodGetDecorView = (*lJNIEnv)->GetMethodID(lJNIEnv, ClassWindow, "getDecorView", "()Landroid/view/View;");
jobject lDecorView = (*lJNIEnv)->CallObjectMethod(lJNIEnv, lWindow, MethodGetDecorView);
// Runs lInputMethodManager.showSoftInput(...).
jmethodID MethodShowSoftInput = (*lJNIEnv)->GetMethodID(lJNIEnv, ClassInputMethodManager, "showSoftInput", "(Landroid/view/View;I)Z");
(*lJNIEnv)->CallBooleanMethod(lJNIEnv, lInputMethodManager, MethodShowSoftInput, lDecorView, lFlags);
if(attached) {
// Finished with the JVM.
(*lJavaVM)->DetachCurrentThread(lJavaVM);
}
}
}
To reproduce, you just have to compile the app, put it onto the emulator with an Android 4.1 image, then click somewhere and the virtual keyboard will be shown. Now try to close the keyboard using the BACK button. The app will freeze now.
Note that this error only occurs on Android 4.1. It works fine on 2.3, 3.0 and 4.0. Could this be a major bug in Android itself? I really have no other explanation because my code is really just a primitive main loop that doesn't do anything at all. But still, trying to close the virtual keyboard will crash the app.
Thanks a lot for help on this issue! I have been grappling with this for hours now :(
UPDATE 1:
The issue also appears on Android 4.2. A ticket has been opened on the Android issues site. Everybody who experiences the same problem should comment on the ticket so that the Android developers become aware of it. Here is the link:
http://code.google.com/p/android/issues/detail?id=43817&thanks=43817&ts=1359632204
Thanks for asking this, it helped me.
I was seeing the freezes too. It seems to be coming from AInputQueue_preDispatchEvent in process_input in android_native_app_glue.c. To work around it, I changed:
LOGI("New input event: type=%d\n", AInputEvent_getType(event));
if (AInputQueue_preDispathEvent(app->inputQueue, event)) {
return;
}
int32_t handled = 0;
to:
int32_t type = AInputEvent_getType(event);
LOGI("New input event: type=%d\n", type);
if (!g_iShowingSoftKeyboard || (type != AINPUT_EVENT_TYPE_KEY)
|| (AKeyEvent_getKeyCode(event) != AKEYCODE_BACK))
{
if (AInputQueue_preDispathEvent(app->inputQueue, event)) {
return;
}
}
int32_t handled = 0;
of course g_iShowingSoftKeyboard is the global that I am using that is true if the soft keyboard is showing.
Edit: and I close the soft keyboard where I handle the back press in my input handler.

Unraveling some JNI C++ code from the Android core

I'm not very experience in C++ and JNI so I have real trouble with some parts of this code (which is a part of the Android framework, more precisely comes from the CPP source of the Surface class):
static void Surface_unlockCanvasAndPost(
JNIEnv* env, jobject clazz, jobject argCanvas)
{
jobject canvas = env->GetObjectField(clazz, so.canvas);
if (canvas != argCanvas) {
doThrow(env, "java/lang/IllegalArgumentException", NULL);
return;
}
const sp<Surface>& surface(getSurface(env, clazz));
if (!Surface::isValid(surface))
return;
// detach the canvas from the surface
SkCanvas* nativeCanvas =
(SkCanvas*) env->GetIntField(canvas, no.native_canvas);
int saveCount = env->GetIntField(clazz, so.saveCount);
nativeCanvas->restoreToCount(saveCount);
nativeCanvas->setBitmapDevice(SkBitmap());
env->SetIntField(clazz, so.saveCount, 0);
// unlock surface
status_t err = surface->unlockAndPost();
if (err < 0) {
doThrow(env, "java/lang/IllegalArgumentException", NULL);
}
}
What particularly bothers me is:
const sp<Surface>& surface(getSurface(env, clazz));
It's just puzzling me. There's no equals sign, and those templates are making it even harder to understand.
Could someone help me out with making out this particular line of code?
This is constructor call syntax for a reference - it's a bit obfuscatory to write it like this, like writing int i(23); instead of int i = 23;, but it is really equivalent to writing
const sp<Surface>& surface = getSurface(env, clazz);.

Categories

Resources