Android 4.0x, OpenGL?


#1

Sorry if combined topics is bad form. I was basically wondering if anyone had looked at Android 4.0 and 4.03 support or Android OpenGL?

Using the Android Emulator I get a black screen for both 4.0 and 4.03, but no errors in the Console or Log, 3.3 and below (at least back to 2.1) are fine.

Picking the OpenGL Renderer with JuceDemo also doesn’t crash, but the rendering is haywire. It looks like it might be something fairly simple, like coordinate space. The 4.0x issue, who knows…

But, before I dig into either, I wondered if anyone else had already taken a look. Perhaps there is an easier way to do NDK debugging on Android that the flaky ndk-gdb, but I haven’t found it, and it get’s really flaky for me whenever I have to mix in Eclipse debugging on the Java side with it.


#2

FWIW, I took a quick look at 4.0x at lunch today and the seemingly big problem was relatively easy to find.

Just using Logger::outputDebugString in the first visible component showed that the constructor was getting called, resize was getting repeatedly called, but paint was never getting called.

So I stuck a message in the Android handlePaintCallback method and found that it also wasn’t being called. A quick breakpoint in the draw method on the Java side showed it was getting called, so I looked at findPeerForJavaView. Sure enough, it loops through the peers, but never finds a match. That is:

    static AndroidComponentPeer* findPeerForJavaView (jobject viewToFind)
    {
        for (int i = getNumPeers(); --i >= 0;)
        {
            AndroidComponentPeer* const ap = static_cast <AndroidComponentPeer*> (getPeer(i));
            jassert (dynamic_cast <AndroidComponentPeer*> (getPeer(i)) != 0);

            if (ap->view == viewToFind)
                return ap;
        }

        return nullptr;
    }

Always returns nullptr because the line “if (ap->view == viewToFind)” is never true. I was running short on time, but ap->view appears to be a wrapper class that is holding a GlobalRef created from the ‘create view’ helper. The viewToFind appears to be the instance jobject passed into the callback. I think looked at this page:

http://developer.android.com/sdk/android-4.0.html

And found this short message:

As it happens, I have minSdkVersion set to 7, and tried setting targetSdkVersion to 7 as well, no change. But the "JNI Tips link in the text says:

Taken at face value, the == comparison is not guaranteed to work. So I quickly passed in the JNIEnv* to and tried replacing the == comparison with env->IsSameObject (ap->view, viewToFind) and, although it compiles without warning, got a SIGSEGV for my trouble…

At that point I released that a) lunchtime was over, b) I was hungry, and c) I still really hate Android. I’ll try to dig a little deeper sometime soon.


#3

For what it is worth, the same problem seems to have bit other people:

http://code.google.com/p/android/issues/detail?id=21674


#4

Thanks for the info!

Haven’t got time right now to go through the whole android build rigmarole and try it, but surely…

[code] //==============================================================================
static AndroidComponentPeer* findPeerForJavaView (JNIEnv* env, jobject viewToFind)
{
for (int i = getNumPeers(); --i >= 0;)
{
AndroidComponentPeer* const ap = static_cast <AndroidComponentPeer*> (getPeer(i));
jassert (dynamic_cast <AndroidComponentPeer*> (getPeer(i)) != nullptr);

        if (env->IsSameObject (ap->view.get(), viewToFind))
            return ap;
    }

    return nullptr;
}

[/code]

and…

#define JUCE_VIEW_CALLBACK(returnType, javaMethodName, params, juceMethodInvocation)
JUCE_JNI_CALLBACK (ComponentPeerView, javaMethodName, returnType, params)
{
AndroidComponentPeer* const peer = AndroidComponentPeer::findPeerForJavaView (env, view);
if (peer != nullptr)
peer->juceMethodInvocation;
}

JUCE_VIEW_CALLBACK (void, handlePaint, (JNIEnv* env, jobject view, jobject canvas), handlePaintCallback (env, canvas)) JUCE_VIEW_CALLBACK (void, handleMouseDown, (JNIEnv* env, jobject view, jfloat x, jfloat y, jlong time), handleMouseDownCallback ((float) x, (float) y, (int64) time)) JUCE_VIEW_CALLBACK (void, handleMouseDrag, (JNIEnv* env, jobject view, jfloat x, jfloat y, jlong time), handleMouseDragCallback ((float) x, (float) y, (int64) time)) JUCE_VIEW_CALLBACK (void, handleMouseUp, (JNIEnv* env, jobject view, jfloat x, jfloat y, jlong time), handleMouseUpCallback ((float) x, (float) y, (int64) time)) JUCE_VIEW_CALLBACK (void, viewSizeChanged, (JNIEnv* env, jobject view), handleMovedOrResized()) JUCE_VIEW_CALLBACK (void, focusChanged, (JNIEnv* env, jobject view, jboolean hasFocus), handleFocusChangeCallback (hasFocus))

?


#5

That’s exactly what I tried today. The call:

env->IsSameObject (ap->view.get(), viewToFind)

Blows up (SIGSEGV). If I replace the first param with NULL:

env->IsSameObject (NULL, viewToFind)

It no longer explodes, but, of course, it is no longer a meaningful comparison. When I have time I’ll dig deeper on why the (seemingly to me) GlobalRef stored in the peer can’t get passed in to IsSameObject.


#6

That really is bizarre… I’m tempted to think that it might be a JNI bug - even if the pointer is invalid (which it isn’t!) then it shouldn’t crash.


#7

No argument here. I didn’t have time to dig into your GlobalRef class or this particular usage. It might be possible that the invokation is coming through early, or retain (the conversion to a GlobalRef) might not be occuring prior to the first call, etc. but it shouldn’t explode, unless there is some weird context shift in the JNI environment itself.

The maddening part to me is that both the documentation and tools suck. I’d have gotten a lot farther yesterday if the 4.0 emulator didn’t fragment itself into confused reboot-everything-death every run or two - and don’t even get me started about Eclipse… :wink:

I am actually pretty busy at work for a few days, but I’ll try to get another look at this soon. If I do a tribal dance and shake a bag of chicken bones, the Android spirits might let actually let ndk-gdb break and resolve symbols, then I can step into the dispatch itself and figure out what is specifically exploding.


#8

FWIW, I just ran it twice while I was waiting on another compile. In wading back through the logcat, dalvik is crashing on purpose (0xdeadd00d), and the reason is “Not a valid JNI object”. Stabbing in the dark, I added env->NewLocalRef (ap->view.get()) prior to the call.

Dalvik then crashes on that line, but says that I am using a stale local reference. Looking at the GlobalRef code and Peer constructor code, it certainly looks like you are taking the localref returned by the create function and calling getEnv()->NewGlobalRef().

Normally, this is where I’d break on the peer view constructor and step through that initialization to make sure that it is, in fact, getting converted. But I don’t have time to get the source level debugging crud working today. Sorry.


#9

OK, mystery solved. It should have occurred to me earlier, but finally dawned on me in the car driving home from work. Your changes above are correct, but with one addition, this:

class AndroidComponentPeer  : public ComponentPeer
{
public:
    AndroidComponentPeer (Component* const component, const int windowStyleFlags)
        : ComponentPeer (component, windowStyleFlags),
          view (android.activity.callObjectMethod (iPanelAppActivity.createNewView, component->isOpaque())),
          usingAndroidGraphics (false),
          fullScreen (false),
          sizeAllocated (0)
    {
        if (isFocused())
            handleFocusGain();
    }

Has to be changed to something like this:

class AndroidComponentPeer  : public ComponentPeer
{
public:
    AndroidComponentPeer (Component* const component, const int windowStyleFlags)
        : ComponentPeer (component, windowStyleFlags),
          usingAndroidGraphics (false),
          fullScreen (false),
          sizeAllocated (0)
    {
        GlobalRef gr (android.activity.callObjectMethod (iPanelAppActivity.createNewView, component->isOpaque()));
        view = gr;
        gr.clear();
        
        if (isFocused())
            handleFocusGain();
    }

In the original, the Java Class is being created in the constructor of the GlobalRef view. But constructing the java class triggers event callbacks. IsSameObject can handle null, but not random. Since obj inside GlobalRef hasn’t been initialized, random is what we get.

In the revised version, view uses the empty constructor, which sets it to 0. So when the java view is created, callbacks are compared to null until view is assigned. It seems that just one is missed (focus, which is handled in the constructor anyway).

With your change to IsSameObject above and this constructor change, Android 4.0 and 4.03 both seem to work. Again, sorry it took me so long to realize that the Java construction as a re-entrancy condition.


#10

Ah! That makes sense! Thanks, I’ll get that sorted out!


#11

FWIW, what you checked in works fine. I also tried cranking up min and targetSDK to 14 and looked for the extended JNIChecks. It runs clean (no JNI legacy mode required).


#12

Good to know, thanks!


#13

No problem. I was going to take a quick look at OpenGL last night, but got side tracked into trying to figure out what is wrong with ndk-gdb. Source debugging of the ndk samples is fine. With juce, I get symbols (ex. ‘info functions’), and can set breakpoints, but ‘list’ shows me the contents of various random header files. My best hypothesis is that I’m an idiot, but I’m willing to consider the possibility that the tools suck (all the samples are in C and compiled with gcc, not g++).

I won’t have a chance to look at OpenGL again until sometime next week. I guess I’ll stick to reading the code and using Logger, things my feeble brain can handle!


#14

With android, the tools definitely suck. I’ve never managed to get it to debug successfully either, but am reluctant to spend too much time trying.


#15

Jules, FWIW, the latest Android changes look really good so far. The unique bundle id stuff saves me a ton of hacking after each Introjucer run, and it didn’t break some other methods I’d added (suspend/resume, a way to open a URL so that hyperlink buttons work, etc.).

I haven’t run your OpenGL changes yet (I had a horrible hack so that GLES20 could be invoked even with SDK levels below 8, so I’ll need to clean up some merging and download 8 (15 keeps howling about deprecated stuff), but, again, so far it looks really good. Thanks!


#16

Thanks! OpenGL isn’t ready for use yet, I’m still hacking away on it, but getting gradually closer…

How did you do the URL opening stuff? I should add that, it’s probably quite easy.


#17

On the java side I added this to the Activity class:

import android.content.Intent;
import android.net.Uri;
.
.
    //==============================================================================
    public final void launchURL (String s)
    {
    	Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(s));
    	startActivity(browserIntent);    	
    }

Stuck the method in juce_android_JNIHelpers.h:

 METHOD (launchURL,              "launchURL",            "(Ljava/lang/String;)V") \

And changed Process::openDocument in juce_android_Files.cpp to this:

//==============================================================================
bool Process::openDocument (const String& fileName, const String& parameters)
{
    const LocalRef<jstring> t (javaString (fileName));
    android.activity.callVoidMethod (iPanelAppActivity.launchURL, t.get());
}

At first I thought about passing up a path and data type to more generically open other intents, but then I wasn’t sure how to deal with the java side exceptions (no compatible app found, etc.), so I just put the above in to solve my immediate need.


#18

Excellent stuff! Thanks!