androidandroid-ndkjava-native-interfacenative-activityandroid-immersive

How do I enable full screen immersive mode for a Native Activity NDK app?


Reading the documentation at https://developer.android.com/training/system-ui/immersive.html I can't seem to find any information on how to set full screen immersive mode in a Native Activity NDK app (without using JNI) as it seems full screen immersive mode can only be toggled from Java.

As it cannot be set from the manifest (Set Android immersive full screen mode in manifest), is there any way to request it via EGL?

Seems the only way to enable full screen immersive mode is to call setSystemUiVisibility via JNI ahead of requesting a surface via EGL?


Solution

  • Answering my own question, you can set immersive mode via C++/JNI without add java to your project. Here is my snippet, large parts are copied from somewhere on the internet.

    auto portis::android_util::SetImmersiveMode(JNIEnv* env, android_app* iandroid_app) -> bool {
        PORTIS_ASSERT(iandroid_app && env);
    
        jclass activityClass = env->FindClass("android/app/NativeActivity");
        jclass windowClass = env->FindClass("android/view/Window");
        jclass viewClass = env->FindClass("android/view/View");
        jmethodID getWindow = env->GetMethodID(activityClass, "getWindow", "()Landroid/view/Window;");
        jmethodID getDecorView = env->GetMethodID(windowClass, "getDecorView", "()Landroid/view/View;");
        jmethodID setSystemUiVisibility = env->GetMethodID(viewClass, "setSystemUiVisibility", "(I)V");
        jmethodID getSystemUiVisibility = env->GetMethodID(viewClass, "getSystemUiVisibility", "()I");
    
        jobject windowObj = env->CallObjectMethod(iandroid_app->activity->clazz, getWindow);
        jobject decorViewObj = env->CallObjectMethod(windowObj, getDecorView);
    
        // Get flag ids
        jfieldID id_SYSTEM_UI_FLAG_LAYOUT_STABLE = env->GetStaticFieldID(viewClass, "SYSTEM_UI_FLAG_LAYOUT_STABLE", "I");
        jfieldID id_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = env->GetStaticFieldID(viewClass, "SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION", "I");
        jfieldID id_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = env->GetStaticFieldID(viewClass, "SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN", "I");
        jfieldID id_SYSTEM_UI_FLAG_HIDE_NAVIGATION = env->GetStaticFieldID(viewClass, "SYSTEM_UI_FLAG_HIDE_NAVIGATION", "I");
        jfieldID id_SYSTEM_UI_FLAG_FULLSCREEN = env->GetStaticFieldID(viewClass, "SYSTEM_UI_FLAG_FULLSCREEN", "I");
        jfieldID id_SYSTEM_UI_FLAG_IMMERSIVE_STICKY = env->GetStaticFieldID(viewClass, "SYSTEM_UI_FLAG_IMMERSIVE_STICKY", "I");
    
        // Get flags
        const int flag_SYSTEM_UI_FLAG_LAYOUT_STABLE = env->GetStaticIntField(viewClass, id_SYSTEM_UI_FLAG_LAYOUT_STABLE);
        const int flag_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = env->GetStaticIntField(viewClass, id_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
        const int flag_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = env->GetStaticIntField(viewClass, id_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
        const int flag_SYSTEM_UI_FLAG_HIDE_NAVIGATION = env->GetStaticIntField(viewClass, id_SYSTEM_UI_FLAG_HIDE_NAVIGATION);
        const int flag_SYSTEM_UI_FLAG_FULLSCREEN = env->GetStaticIntField(viewClass, id_SYSTEM_UI_FLAG_FULLSCREEN);
        const int flag_SYSTEM_UI_FLAG_IMMERSIVE_STICKY = env->GetStaticIntField(viewClass, id_SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    
        // Get current immersiveness
        const int currentVisibility = env->CallIntMethod(decorViewObj, getSystemUiVisibility);
        const bool is_SYSTEM_UI_FLAG_LAYOUT_STABLE = (currentVisibility & flag_SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
        const bool is_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION = (currentVisibility & flag_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0;
        const bool is_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = (currentVisibility & flag_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0;
        const bool is_SYSTEM_UI_FLAG_HIDE_NAVIGATION = (currentVisibility & flag_SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
        const bool is_SYSTEM_UI_FLAG_FULLSCREEN = (currentVisibility & flag_SYSTEM_UI_FLAG_FULLSCREEN) != 0;
        const bool is_SYSTEM_UI_FLAG_IMMERSIVE_STICKY = (currentVisibility & flag_SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
    
        const auto isAlreadyImmersive =
            is_SYSTEM_UI_FLAG_LAYOUT_STABLE &&
            is_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION &&
            is_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN &&
            is_SYSTEM_UI_FLAG_HIDE_NAVIGATION &&
            is_SYSTEM_UI_FLAG_FULLSCREEN &&
            is_SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
        PORTIS_LOGD()
            << "set_immersive data"
            << is_SYSTEM_UI_FLAG_LAYOUT_STABLE
            << is_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            << is_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
            << is_SYSTEM_UI_FLAG_HIDE_NAVIGATION
            << is_SYSTEM_UI_FLAG_FULLSCREEN
            << is_SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
        auto success = true;
        if (true) {
            const int flag =
                flag_SYSTEM_UI_FLAG_LAYOUT_STABLE |
                flag_SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
                flag_SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                flag_SYSTEM_UI_FLAG_HIDE_NAVIGATION |
                flag_SYSTEM_UI_FLAG_FULLSCREEN |
                flag_SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
            env->CallVoidMethod(decorViewObj, setSystemUiVisibility, flag);
            if(env->ExceptionCheck()) {
                // Read exception msg
                jthrowable e = env->ExceptionOccurred();
                env->ExceptionClear(); // clears the exception; e seems to remain valid
                jclass clazz = env->GetObjectClass(e);
                jmethodID getMessage = env->GetMethodID(clazz, "getMessage", "()Ljava/lang/String;");
                jstring message = (jstring)env->CallObjectMethod(e, getMessage);
                const char *mstr = env->GetStringUTFChars(message, NULL);
                const auto exception_msg = std::string{mstr};
                env->ReleaseStringUTFChars(message, mstr);
                env->DeleteLocalRef(message);
                env->DeleteLocalRef(clazz);
                env->DeleteLocalRef(e);
                PORTIS_LOGW() << "set_immersive exception [" << exception_msg << "]";
                success = false;
            }
            else {
                PORTIS_LOGI() << "set_immersive success";
            }
        }
        env->DeleteLocalRef(windowObj);
        env->DeleteLocalRef(decorViewObj);
        return success;
    }