Search code examples
androidandroid-ndkjava-native-interfaceaccountmanager

Calling Java method from JNI requiring context (this) for receiving the account name


basic issue, but complicated code required:

I'd like to get the account name used on an Android device using the Android NDK mainly. For security purposes as much as possible should be done in native code. While analyzing the calls suggested in *1 (see below) in Android's source codes, it turned out that it's not possible to access the raw database file and just read out the account name due to missing permissions probably.

Therefore my next approach was to use the code of *1 (see below) and put it into a separated java class "Account" in the src-folder. Unfortunately it requires the activity context and I have no clue how to request it in native code, since everything should be done from native code without including the main activity. I assume it might be the 2nd parameter thiz of the JNI call, but I'm very unsure about it.

My current native code is based on *2 and looks like this, but it's not working so far. Do you have any hints or better solutions? Can I call the AccountManager from native code directly maybe?

// based on http://www.rgagnon.com/javadetails/java-0284.html
JNIEXPORT jstring JNICALL Java_com_example_nils_myapplication_MyNDK_getMyString(JNIEnv* env, jobject thiz){

const char *str;

jclass myclass_class =(jclass) env->NewGlobalRef
        (env->FindClass ("com/example/nils/myapplication/Account"));

jmethodID constructorID = env->GetMethodID
        (myclass_class, "", "()V");

jmethodID methodID = env->GetMethodID
        (myclass_class, "getUsername", "(Landroid/content/Context;)Ljava/lang/String;");

jobject myclass_object =  env->NewObject
        (myclass_class, constructorID);

jstring s = (jstring)  env->CallObjectMethod
        (myclass_object, methodID, thiz);
[...]

Here the Java class

public class Account {
    public Account(){

    };

    // based on https://stackoverflow.com/questions/2727029/how-can-i-get-the-google-username-on-android
        public static String getUsername(Context c) {
            AccountManager manager = AccountManager.get(c);
            android.accounts.Account[] accounts = manager.getAccountsByType("com.google");
            LinkedList<String> possibleEmails = new LinkedList<String>();

            for (android.accounts.Account account : accounts) {
                // TODO: Check possibleEmail against an email regex or treat
                // account.name as an email address only for certain account.type values.
                possibleEmails.add(account.name);
            }

            if (!possibleEmails.isEmpty() && possibleEmails.get(0) != null) {
                String email = possibleEmails.get(0);
                String[] parts = email.split("@");

                if (parts.length > 1)
                    return parts[0];
            }
            return null;
        }


    }

In reply to comment #1: Of course, the native code has to be called somewhere initially. For testing purposes I placed that call in the onCreate() method of my main activity.

public class MainActivity extends AppCompatActivity {

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

        MyNDK a = new MyNDK();
        Log.i("MyTag", a.getMyString());

In reply to the 2nd comment: MyNDK is just a simple class to load the native library, while defining the getMyString() method.

public class MyNDK {

    static{
        System.loadLibrary("MyLibrary");
    }
    public native String getMyString();
}

*1 How can I get the google username on Android?

*2 http://www.rgagnon.com/javadetails/java-0284.html


Solution

  • Well, thanks for the hints. I solved it. So here is the solution:

    The constructor requires < init > as name

    jmethodID constructorID = env->GetMethodID
            (myclass_class, "<init>", "()V")
    

    Furthermore I picked up Michael's advice and just added the context as a parameter in the java and corresponding C function. thiz2 is our desired context, while thiz is the instance of MyNDK as Michael mentioned above already.

    public native String getMyString(Context c);
    
    
    JNIEXPORT jstring JNICALL Java_com_example_nils_myapplication_MyNDK_getMyString
            (JNIEnv * env, jobject thiz, jobject thiz2) {
    

    Well, what's left? The account name is not returned, but for testing purposes I used a fixed return string. That solved the actual JNI problem and the app runs fine, while there must be still an error in the java implementation for receiving the account name.

    Anyway. Sufficiently solved. Thanks everyone.