Skip to content

Hide navigation bar (in native acitivity) #95

Open

Description

@MarijnS95

Hey @rib, perhaps you have a better idea for this. With WindowManagerFlags::FULLSCREEN the status bar can be hidden (still available when swiping inwards from the top of the screen), and I'd like the same to happen on the navigation bar (bottom of the screen) instead of being drawn over my native activity. There doesn't seem to a be a setWindoWFlags() flag available to disable this (or that we can pass as the "remove" argument), and most answers seem to point to doing this via DecorView for which no native API is available (have to go through jni): https://stackoverflow.com/a/50831255

I can barely believe that native activities - intended for e.g. full-screen games - lack this functionality in an easily accessible manner. Did I miss something? Is this possible on GameActivity? If not, should we implement the above JNI logic on AndroidApp to be more easily accessible?

Activity

  1. self-assigned this
    on Jul 27, 2023
  2. rib commented on Jul 28, 2023

    @rib
    Member

    GameActivity doesn't have any special support for this either. The examples I've seen just deal with hiding navigation bars in the Java subclass of GameActivity. E.g. that's what the rust-android-examples do.

    Slightly related to this - I'm currently looking at binding the KeyCharacterMap java API so we'd have a way to access unicode characters for key presses (equivalent to getUnicodeChar) and that might help figure out a few boilerplate details for how we could potentially create some form of binding via JNI for this too.

  3. maciekglowka commented on Dec 28, 2023

    @maciekglowka

    @MarijnS95 hi, did you manage to solve it in a somewhat simple way? (eg. without creating a custom java class).
    I am facing a similar problem with my game.
    Another (worse) option would be to decrease the app window to accomodate the bottom bar. But disabling full screen flag doesn't seem to be working for me either.

  4. CrazyCraftix commented on May 23, 2024

    @CrazyCraftix

    I think it'd be great if that was a feature.

    Following the link you sent, I managed to translate the JNI calls into Rust. However, since "only the original thread can touch its views", this call has to be made from the Java thread. I managed to hack this directly into native_activity/glue.rs in response to notify_focus_changed, but I'm not sure how this could be done properly.

    It's probably possible to trigger this from the main thread somehow, feels hacky to me though. Triggering this in response to MainEvent::GainedFocus would mean crossing threads twice, which would make the timing unpredictable. I could imagine that the navigation bar might flicker once or something, as it may have already started showing by the time the function is run. Would have to try that.

    Here's some ideas I had:

    • enable some form of configuration before starting the main thread, like setting a flag hide_navigation or even full event hooks to be run on the main thread
    • add a feature flag hide-navigation
      probably the simplest and least flexible solution
    • some form of run_on_java_thread(|| { ... })
    • use JNI to call Activity.runOnUiThread(Runnable)
      creating that Runnable seems like it would be quite involved
  5. CrazyCraftix commented on Feb 17, 2026

    @CrazyCraftix

    Any context on this? Is there a proper way to hide the navigation bar with NativeActivity now?

    When I was still working on my project, I think this was pretty much the only thing standing in the way of me being able to write my App in pure rust. My solution ended up being to patch in the JVM call in the right place because I didn't feel like adding Java stuff over this.

    Would be cool if that JVM call could somehow be made to run on the main thread without changing the code of this crate.

  6. rib commented on Feb 17, 2026

    @rib
    Member

    Any context on this?

    unfortunately, I think I frustrated @MarijnS95 because I chose to (temporarily) revert a change on main (re: #218) and they have closed a bunch of issues as "not planned".

    I guess they are frustrated that I haven't been actively maintaining the project and not had the time to move issues like this forward.

    If there is indeed a good solution for this issue that doesn't involve support from a java class then it could be good to try and support that.

  7. reopened this on Feb 17, 2026
  8. MarijnS95 commented on Feb 18, 2026

    @MarijnS95
    MemberAuthor

    I just closed all my self reported issues that I don't see myself or anyone else caring to work on any time soon.

    As long as we keep claiming that NativeActivity is the problem and not the implementation of android-activity (and related crates) itself based on the upstream "app glue" abstraction, I don't see a way forward with this crate.

    All these issues run downstream from there.

  9. CrazyCraftix commented on Feb 18, 2026

    @CrazyCraftix

    Okay, fair enough. Thanks for the info!

  10. rib commented on Feb 18, 2026

    @rib
    Member

    I just closed all my self reported issues that I don't see myself or anyone else caring to work on any time soon.

    at least from my pov, my lack of activity here doesn't mean that there's no interest or a lack of care but I'm sorry if it looks that way.

    I've been working pretty hard on trying to get the jni 0.22 release done (the jni crate hadn't had a release in ~3 years) and unfortunately not had a lot of time to be active here.

    The reason I'm here now though is that I finally managed to release jni 0.22 and so I'm now trying to shift some time and attention here. I was pretty happy yesterday to finally get a chance to start catching up here.

    for the other issues - I do wish we could have a more structured break down of the technical issues you see. Maybe a tracker issue for supporting multiple activities that could enumerate what we think the blockers are.

    we've known for a long time that there are design issues with ndk-context that affect our ability to run more than one activity.

    That's one of the bigger issues and it's a general rust android ecosystem problem not an android-activity problem per se. We need a replacement for ndk-context. It wouldn't currently be practical if android-activity would just stop initializing ndk-context while other crates depend on it being initialized. Maybe as a hacky workaround we could at least try to wrap ndk-context initialization in catch_unwind but otherwise there's not much we can do currently.

    I haven't made progress on trying to replace ndk-context but no one should have to apologies for that.

    As long as we keep claiming that NativeActivity is the problem and not the implementation of android-activity (and related crates) itself based on the upstream "app glue" abstraction, I don't see a way forward with this crate.

    I didn't think it was controversial to say that NativeActivity isn't well suited to supporting multiple activities - it was created for porting fullscreen main-loop based games to Android with the expectation that you would run a traditional main function in the android_main thread (i.e. one per process). There's also a limit to what we can do to try and subvert those original design ideas while the native side to NativeActivity gets bootsrapped by ndk code that we aren't in control of.

    we've also talked in the past about ideally writing a RustActivity that could hopefully do a better job of supporting multiple activities and I'd still be interested in prototyping this.

  11. MarijnS95 commented on Feb 18, 2026

    @MarijnS95
    MemberAuthor

    Note that this is in no way a complaint towards you for lack of activity/maintenance. The Rust Ecosystem around Android has similarly curbed my enthusiasm so I haven't been picking up any tasks or finishing those multi-activity rewrites for android-activity either. The boilerplate and requested API improvements on the ndk crate is already there though (which that revert makes harder once again).

    You're right that ndk-context and especially every crate using it is only holding us back. But with some different primitives and a v2 release it still has its uses.

    android_main is something invented by android-activity (this Rust crate), not NativeActivity (the Android abstraction). I've already proven in my forks that it's very well possible to handle multiple activities with just NativeActivity. And a custom RustActivity makes it even nicer to forward the missing callbacks such as intents etcetera - but it is by no means a requirement to solve this limitation.

    Alas this issue is not at all the place to discuss the technical limitations and design direction of android-activity.

  12. rib commented on Feb 18, 2026

    @rib
    Member

    Note that this is in no way a complaint towards you for lack of activity/maintenance. The Rust Ecosystem around Android has similarly curbed my enthusiasm so I haven't been picking up any tasks or finishing those multi-activity rewrites for android-activity either.

    sorry to hear this for sure.

    personally I've been struggling a fair amount with motivation to contribute so much time to things like jni and android-activity while they can feel like they take an endless amount of time and effort and can feel like a thankless task. The jni crate in particular has sunk so much of my time and considering I'm not paid to work on any of these things currently it can sometimes be hard to justify the time spent.

    if that's at all similar for you then I certainly couldn't blame you for losing enthusiasm.

    fwiw, I value your input even if we've disagreed over details before.

    android_main is something invented by android-activity (this Rust crate), not NativeActivity (the Android abstraction). I've already proven in my forks that it's very well possible to handle multiple activities with just NativeActivity.

    even though this isn't the ideal place to be having this discussion I do feel like I should at least reply to this assertion

    the design, including the android_main function, comes from Google who are responsible for the design and implementation of NativeActivity and GameActivity:

    Google are also responsible for the ANativeActivity glue layer that NativeActivity.java calls into before passing control to ANativeActivity_onCreate (i.e. google controls the implementation of app_android_NativeActivity_loadNativeCode)

    the initial implementation of android-activity used to be based on Google's app glue, which they wrote to be paired with NativeActivity and that was then ported to Rust.

    the same design was adopted by GameActivity also written by Google.

    that's not to say that it isn't possible to write glue code that looks at the open source implementation of NativeActivity.java and aims in some way to follow a different design but wanted to be clear that android-activity hasn't invented a custom android_main glue layer.

  13. rib commented on Mar 22, 2026

    @rib
    Member

    @CrazyCraftix there have been two recent changes that might be able to help here...

    1. I recently added AndroidApp::run_on_java_main_thread() which makes it easy to run boxed closures on the java main thread: Add AndroidApp::run_on_java_main_thread #232

      Since MarijnS95 linked to a DecorView JNI solution (which had a caveat that it would only be safe from the main thread), then maybe this would make that practical: https://stackoverflow.com/a/50831255

    2. I recently added support for an optional android_on_create application entrypoint that runs within the onCreate callback of your Activity while still running on the Java main thread: Support an optional 'android_on_create' entrypoint #239

      If this is something that you want to initialize once and you're responsible for the application itself (i.e. responsible for implementing the android_main entrypoint and not a general-purpose library crate) then this could also potentially enable a JNI solution from the java main thread.

  14. CrazyCraftix commented on Apr 8, 2026

    @CrazyCraftix

    @rib hey, thanks for the info!

    run_on_java_main_thread sounds like it should absolutely work for this.

    I can't easily test it right now because my old project is set up a bit weird, but here's the patch I used to hack it in, in case someone is interested.

    Maybe the hide_navigation_bar function I added can more or less be called from within run_on_java_main_thread somehow. That should do the trick.

    I'll report back in case I use this for a new project at some point.

    diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs
    index 2e4371c..653fb4c 100644
    --- a/android-activity/src/native_activity/glue.rs
    +++ b/android-activity/src/native_activity/glue.rs
    @@ -405,7 +405,55 @@ impl WaitableNativeActivityState {
             guard.write_cmd(AppCmd::LowMemory);
         }
     
    +    fn hide_navigation_bar(
    +        jvm: jni::JavaVM,
    +        activity: jni::objects::JObject,
    +    ) -> crate::error::InternalResult<()> {
    +        let mut env = jvm.attach_current_thread_permanently()?;
    +        env.with_local_frame::<_, _, jni::errors::Error>(10, |env| {
    +            let window = env
    +                .call_method(&activity, "getWindow", "()Landroid/view/Window;", &[])?
    +                .l()?;
    +            let decor_view = env
    +                .call_method(&window, "getDecorView", "()Landroid/view/View;", &[])?
    +                .l()?;
    +
    +            let flag_strings = vec![
    +                "SYSTEM_UI_FLAG_LAYOUT_STABLE",
    +                "SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION",
    +                "SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN",
    +                "SYSTEM_UI_FLAG_HIDE_NAVIGATION",
    +                "SYSTEM_UI_FLAG_FULLSCREEN",
    +                "SYSTEM_UI_FLAG_IMMERSIVE_STICKY",
    +            ];
    +            let mut combined_flag = 0;
    +            for flag_string in flag_strings {
    +                combined_flag |= env
    +                    .get_static_field("android/view/View", flag_string, "I")?
    +                    .i()?;
    +            }
    +
    +            env.call_method(
    +                decor_view,
    +                "setSystemUiVisibility",
    +                "(I)V",
    +                &[combined_flag.into()],
    +            )?
    +            .v()?;
    +
    +            Ok(())
    +        })
    +        .map_err(|e| crate::jni_utils::clear_and_map_exception_to_err(&mut env, e))
    +    }
    +
         pub fn notify_focus_changed(&self, focused: bool) {
    +        if let Ok(jvm) = unsafe { jni::JavaVM::from_raw((*self.activity).vm) } {
    +            let activity = unsafe { jni::objects::JObject::from_raw((*self.activity).clazz) };
    +            Self::hide_navigation_bar(jvm, activity)
    +                .inspect_err(|e| log::error!("error while hiding navigation bar: {}", e))
    +                .ok();
    +        }
    +
             let mut guard = self.mutex.lock().unwrap();
             guard.write_cmd(if focused {
                 AppCmd::GainedFocus
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions