Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Accessing OnNewIntent from a native application? #275

Closed
Hoodad opened this issue May 17, 2022 · 5 comments
Closed

Accessing OnNewIntent from a native application? #275

Hoodad opened this issue May 17, 2022 · 5 comments
Labels
type: documentation Awareness, docs, examples, etc.

Comments

@Hoodad
Copy link

Hoodad commented May 17, 2022

Hello!

My question is about how I can access the OnNewIntent?

Some background; when a native application is launched from an intent you can fetch the intent using getDataString But if the application is set to singleTop and if application is already running the intent will have to be fetched using OnNewIntent which requires overriding the OnNewIntent. However that function is only accessible from the base class Activity and native applications extend the class NativeActivity which does not expose that function. So from a native application is there a way to access the new intent?

Any help is greatly appreciated 👍

@rib
Copy link
Contributor

rib commented May 18, 2022

I think realistically there are quite a number of things that aren't practically possible for applications that are strictly constrained to using NativeActivity without at least making their own Java subclass.

At this point NativeActivity is quite an old utility abstraction for writing native Android apps and unless you're writing a rather minimal test app / demo (that only needs to do simple rendering without interacting with the rest of the OS) you'll probably find you need to create your own Activity (or NativeActivity) subclass for things like this.

E.g. in my situation working on a Bluetooth library where I want to use the Companion API to discover devices then I need to be able to hook in to onActivityResult so that I can use an Intent to launch the Companion UI and then get back a result with the details of the device that the user selected. That's not possible without subclassing Activity.

At least in my similar situation it also highlighted a slightly different challenge which is, "how can we build applications based on AppCompatActivity?" which NativeActivity doesn't derive from.

AppCompatActivity is a very useful Activity base class that includes numerous back-ported APIs from newer versions of Android so it's possible to practically support a wider range of devices/versions (generally referred to as AndroidX APIs). In the case of onActivityResult then AppCompatActivity actually supports an improved API for starting other applications via an Intent and getting a result back which wouldn't itself require further subclassing of the activity.

I recently opened this issue when I was starting to figure out how I could build a Rust app based on AppCompatActivity: #266

In your case the minimal change might just be to introduce a custom subclass of NativeActivity into your project that can handle onNewIntent and then you may need to define some native functions in Java that will give you a means to pass the data you need to a corresponding #[no_mangle]extern "C" symbol in your Rust cdylib library.

@Hoodad
Copy link
Author

Hoodad commented May 19, 2022

I saw your issue #266 ago but missed your last update, great work on that! GameActivity does seem more like a better long term solution. More refined and updated API. Though it does not expose the onNewIntent method 😢

As you pointed its probably inevitable that there needs to be some java code involvement. Though I fear that I cannot just extend NativeActivity as it does not appear to expose the onNewIntent either. From what I have read it seems that you must inherit from Activity and that's a shame. Unfortunately that probably means the solution will be quite convoluted and (for me) complicated. FFI isn't to foreign for me, but I'm not as skilled in Java and Android. Will see how it goes.

@rib
Copy link
Contributor

rib commented May 23, 2022

Heya, I was meaning to reply to this the other day after I had planned to cook up a quick example of subclassing NativeActivity but ended up getting distracted, sorry :)

I just had a chance to add an example here though:
https://github.com/rib/agdk-rust/tree/main/examples/na-subclass-jni

That particular example is based on another native-activity crate I made a few days ago which I created as an experiment to see if it could be API compatible with the game-activity crate enough to make it practical to support either NativeActivity and GameActivity in a single Winit backend.

Also sorry for the bad etiquette linking away from ndk-glue to an alternative - I actually just remembered someone was looking for an example of subclassing NativeActivity and kinda forgot the original context for this issue 😅 Really though this example doesn't relate to any of the glue details, the same would work fine with ndk-glue.

It probably would have helped to add a few comments to the code but in java code it looks like this:

public class MainActivity extends NativeActivity {

    static {
        System.loadLibrary("na_subclass_jni");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);

        notifyOnNewIntent();
    }

    private native void notifyOnNewIntent();
}

With the main fiddly bits being: 1) we have to use LoadLibrary for Java to know where to find the native function that will be called. 2) we have to declare a native function via private native void notifyOnNewIntent();

(actually I was surprised by the need to call LoadLibrary manually here, I was expecting that to be unnecessary since NativeActivity will be loading the same lib anyway)

Then in Rust we can implement that function with something like:

#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn Java_co_realfit_nasubclassjni_MainActivity_notifyOnNewIntent(
    _env: jni::JNIEnv,
    _class: jni::objects::JObject, // This is the JClass, not the instance,
    _activity: jni::objects::JObject,
) {
    info!("onNewIntent was called!");
}

Just keep in mind that the function will be called from the Java main thread, which won't be the same as your native main thread.

Btw, the example app is set to have a launchMode="singleTop" so it will call onNewIntent if you try launching the same app while it's already running. Sorry I didn't show passing the getDataString, but hopefully you can figure that out. The jni crate has some utility APIs for converting Java strings into Rust strings. For an additional string it'd be something like:

#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn Java_co_realfit_nasubclassjni_MainActivity_notifyOnNewIntent(
    env: jni::JNIEnv,
    _class: jni::objects::JObject, // This is the JClass, not the instance,
    _activity: jni::objects::JObject,
    data: jni::objects::JObject, //
) {
    let s = env.get_string(jni::objects::JString::from(data)).unwrap();
    let s = s.to_str().unwrap();
    info!("onNewIntent was called with {s}!");
}

Hope that helps point you in the right direction for what you're trying to do.

@Hoodad
Copy link
Author

Hoodad commented May 24, 2022

Hey awesome work!

This looks really promising, I was able to get the code you provided to compile and run on my device (the smallest of victories).

Regarding how to fetch the Intent I think the easiest would be in Java to call setIntent(Intent) as that will update the intent you get from calling getIntent which already in my use case works. The app will get the event onResume when switching apps so that's a good point to just check if the intent has changed. Code would look something like this

    @Override
    protected void onNewIntent(Intent intent) {        
        setIntent(intent);
        
        notifyOnNewIntent();
    }

Not sure what the super.onNewIntent(intent) would do, so I removed it. Maybe its needed?

That said I feel there is a gap, and you pointed that out as well, between your code and how to use it in conjunction with winit and ndk-glue. It would be real nice to not having to implement/maintain a separate app flow for this specific use-case. Maybe this is where this issue can loop back into ndk rs terriotry again 😅

I tried looking at ways to get it into the NativeActivity already used today but I cannot say I see a clear way of doing that since it today never touches Java, goes straight to C++/C code. Maybe you can have multiple Activities active in the same app on Android, I don't know...

@rib
Copy link
Contributor

rib commented May 24, 2022

Cool, glad you got something running.

Yeah not, sure if there's any need to chain up with super.onNewIntent(intent), I just did in case there's some default handling of something.

If you were using ndk-glue the Java subclass should be exactly the same and whatever rust code you have with winit + ndk-glue shouldn't need to change.

I tried looking at ways to get it into the NativeActivity already used today but I cannot say I see a clear way of doing that since it today never touches Java, goes straight to C++/C code. Maybe you can have multiple Activities active in the same app on Android, I don't know...

You shouldn't need to worry about messing with the NativeActivity implementation as alluded here. At least in the context of running a native Rust app you can't have multiple Activities.

The main thing is that you need to choose a build system that can handle compiling the java subclass and you need to specify the subclass in your AndroidManifest.xml. The example above uses Gradle, which is what Android Studio uses and personally I'd recommend just using Gradle for any Android projects (including if you use ndk-glue), not because I like it (actually I quite dislike it on many levels) but just because it's practically the de facto standard build system for Android (sort of like using xcode for building for iOS).

@dvc94ch dvc94ch added the type: documentation Awareness, docs, examples, etc. label Dec 22, 2022
@rust-mobile rust-mobile locked and limited conversation to collaborators Dec 22, 2022
@dvc94ch dvc94ch converted this issue into discussion #380 Dec 22, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
type: documentation Awareness, docs, examples, etc.
Projects
None yet
Development

No branches or pull requests

3 participants