3

This is a follow-up to another question I asked: Android -- get MEID from JNI

I am trying to get the ID of a phone in Android. I have some JNI code and a simple test app to call the JNI code. Here is working Java code from my simple test app:

TelephonyManager tm = (TelephonyManager)this.getSystemService(Context.TELEPHONY_SERVICE);
String id = tm.getDeviceId();

The string id is set to the phone ID value that I want. But I need to get it from JNI, and just using the above code and passing the ID value in is not an acceptable solution. (This is to make some JNI code somewhat tamper-proof and I shouldn't trust the Java layer to send a correct ID value.)

Here is the JNI code that I have written, with error handling removed so it is easier to follow. It works until the indicated line, and then the whole app crashes.

// "env" and "obj" are passed to a JNI function and are used unmodified in this code
// JNIEnv *env, jobject obj

jclass cls_context = NULL;
jclass cls_tm = NULL;
jobject tm = NULL;
jmethodID mid;
jfieldID fid;
jstring jstr;
jsize len_jstr;


cls_context = (*env)->FindClass(env, "android/content/Context");
fid = (*env)->GetStaticFieldID(env, cls_context, "TELEPHONY_SERVICE",
        "Ljava/lang/String;");
jstr = (*env)->GetStaticObjectField(env, cls_context, fid);

mid = (*env)->GetMethodID(env, cls_context, "getSystemService",
        "(Ljava/lang/String;)Ljava/lang/Object;");

tm = (*env)->CallObjectMethod(env, obj, mid, jstr);  // THIS LINE CRASHES

cls_tm = (*env)->FindClass(env, "android/telephony/TelephonyManager");

mid = (*env)->GetMethodID(env, cls_tm, "getDeviceId",
        "()Ljava/lang/String;");

jstr = (*env)->CallObjectMethod(env, tm, mid);

len_jstr = (*env)->GetStringUTFLength(env, jstr);

(*env)->GetStringUTFRegion(env, jstr, 0, len_jstr, buf_devid);

I think the problem is that obj isn't the right thing to pass, but if so I have no idea what is the right thing. Isn't obj that gets passed to the JNI function the same thing as this in the Java code?

EDIT: Okay, we have figured out that if we add an extra argument of type jobject to the JNI function, and explicitly pass a copy of this in that argument, and then pass that to CallObjectMethod() (the one that crashes in the above code), everything works. We get our TelephonyManager instance and we can query the Phone ID value.

Using a logging macro, I logged the obj pointer and the passed-in this pointer. They are similar numbers (addresses close to each other) but not identical. So I think obj is some sort of object reference from inside the Java VM... it is not actually the same as this.

In a JNI function, the first two arguments are JNIEnv *env and jobject obj. What is that second one for? What can I do with it? Is there any way I can use it to call getSystemService or will I have to pass an extra argument and pass in this?

Community
  • 1
  • 1
steveha
  • 74,789
  • 21
  • 92
  • 117
  • So did you debug? Start by printing out all variable values to LogCat (use `_android_log_print()` for that), see if all class and method IDs are all valid. Chances are, the error is before the actual method call. – Seva Alekseyev Aug 24 '12 at 13:14
  • As far as I know, if the return value is not `NULL` then the return value is valid; and none of the return values were `NULL`. My code is logging after each step and printing each pointer with a `%p` format; the numbers look plausible. (When I removed the error-checking code for posting here, I also removed the logging code.) – steveha Aug 24 '12 at 18:20
  • The problem is that you should look up the method using the class of `obj`, as that is what you are going to use in `CallObjectMethod()`, rather than the hard wired class you're using, – user207421 Jan 06 '20 at 05:51

3 Answers3

2

The problem seems to be related to Java inheritance: in Java, you can call this.getSystemService() and it works, even though this is not actually an instance of Context. When you make the JNI call, the call simply fails.

So our solution was to have our Android app add a .getApplicationContext() method function actually as part of its own class. This in turn calls the actual getSystemService() and returns the result.

The code didn't change: we are still calling

mid = (*env)->GetMethodID(env, cls_context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");

but now it works, when there is a .getSystemService() method in the class that is calling our JNI function. So, the env argument in a JNI call does represent this (it's not identical... I printed the value of this as a pointer, and printed env, and they are not the same but they are definitely related).

steveha
  • 74,789
  • 21
  • 92
  • 117
  • I have the same problem, I use this code in app and its work, but when I use it in library module I get an exception. please show me a simple for .getApplicationContext() in android to pass this exception. – sma6871 Sep 30 '16 at 14:07
0

If you want to call a superclass method on object, you should use CallNonvirtualObjectMethod() How to call an overriden method in JNI

Community
  • 1
  • 1
Alexey
  • 9
  • 1
0

Try this... It works... "context" came from java :)

jclass ctx = env->FindClass("android/content/Context");
jfieldID fid = env->GetStaticFieldID(ctx,"TELEPHONY_SERVICE","Ljava/lang/String;");
jstring str = (jstring) env->GetStaticObjectField(ctx, fid);
jmethodID mid = env->GetMethodID(ctx, "getSystemService","(Ljava/lang/String;)Ljava/lang/Object;");
jobject tm = env->CallObjectMethod(context, mid, str);

jclass ctx_tm = env->FindClass("android/telephony/TelephonyManager");
jmethodID mid_tm = env->GetMethodID(ctx_tm,"getDeviceId","()Ljava/lang/String;");
jstring str_tm = (jstring) env->CallObjectMethod(tm, mid_tm);

strReturn = env->GetStringUTFChars(str_tm, 0);