Using JNI in Mono to Access Java Library - xamarin.android

I am developing an app using mono for Android and have been strugelling to get the push notifications working, I am using Urban Airship.
So far I have been able to call TakeOff() and EnablePush() and my app is successfully registering, see the following code:
//_____________________________
// Get the airship config class
IntPtr ip_airshipConfigOptions = JNIEnv.FindClass("com/urbanairship/AirshipConfigOptions");
if (ip_airshipConfigOptions == IntPtr.Zero)
{
throw new InvalidOperationException("Counldn't find java class !");
}
//__________________________________________________
// Get the loadDefaults method from the config class
IntPtr methodId = JNIEnv.GetStaticMethodID(ip_airshipConfigOptions, "loadDefaultOptions", "(Landroid/content/Context;)Lcom/urbanairship/AirshipConfigOptions;");
if (methodId == IntPtr.Zero)
{
throw new InvalidOperationException("Couldn't find java class !");
}
//________________________________________________________________
// Call the loadDefaultOptions method passing in this app instance
var methodPtr = JNIEnv.CallStaticObjectMethod(ip_airshipConfigOptions, methodId, new JValue(this));
//________________________
// Get the UAirship class
IntPtr ip_uairship = JNIEnv.FindClass("com/urbanairship/UAirship");
if (ip_uairship == IntPtr.Zero)
{
throw new InvalidOperationException("Couldn't find java class !");
}
//___________________________________________
// Get takeOff method with configoption param
IntPtr methodId2 = JNIEnv.GetStaticMethodID(ip_uairship, "takeOff", "(Landroid/app/Application;Lcom/urbanairship/AirshipConfigOptions;)V");
//______________________________________________
// Get takeOff method without configoption param
//IntPtr methodId3 = JNIEnv.GetStaticMethodID(ip_uairship, "takeOff", "(Landroid/app/Application;)V");
//___________________________________________________________________________________________
// Call UAirship.takeOff(this, options). Not sure if these parameters are specified correctly
JNIEnv.CallStaticVoidMethod(ip_uairship, methodId2, new JValue(this), new JValue(methodPtr));
//________________________________________
// Enable Push in Urban Airship Pushmanager
IntPtr ip_pushmanager = JNIEnv.FindClass("com/urbanairship/push/PushManager");
IntPtr ip_enablePush = JNIEnv.GetStaticMethodID(ip_pushmanager, "enablePush", "()V");
JNIEnv.CallStaticVoidMethod(ip_pushmanager, ip_enablePush);
I now need to make a call to PushManager.shared().setIntentReciever(myClass) but I can't seem to access the shared() class or the setIntentReciever method. I have tried various combinations to try access the method but keep getting class/method not found exceptions.
//IntPtr ip_setReciver = JNIEnv.GetStaticMethodID(ip_PushManager, "shared().setIntentReceiver", "(Landroid/app/Class)V");
Any help with this is appreciated! I'm sure it can be solved with one line, I think I'm just missing something with the syntax.
Regards

Solved this issue, see the code below for calling setIntentReceiver via JNI:
//__________________________
// Get the PushManager class
IntPtr ip_Test = JNIEnv.FindClass("com/urbanairship/push/PushManager");
//_____________________
// Get the class object
IntPtr ip_class = JNIEnv.GetObjectClass(ip_Test);
//_________________________________
// Get the setIntentReciever method
IntPtr ip_setIntent = JNIEnv.GetMethodID(ip_Test, "setIntentReceiver", "(Ljava/lang/Class;)V");
//_________________________________________________
// Create a new jValue from the IntentRecieverClass
JValue val = new JValue(reciever.Class);
//______________________________________________________________________________________
// Set the intent reciever by calling the setIntentReciever method passing in the jValue
JNIEnv.CallStaticVoidMethod(ip_class, ip_setIntent, val);

Related

How to serialize a reference to the managed peer of an Android Callable Wrapper in Xamarin

I have a native Android Activity that receives a callback interface as part of the Intent used to start it:
public interface ICallback : Serializable
{
void invoke(Result result);
}
I want to implement the callback in Xamarin as a lambda:
class CallbackWrapper : Java.Lang.Object, ICallback
{
private Action<Result> onInvoke;
public CallbackWrapper(Action<Result> onInvoke)
{
this.onInvoke = onInvoke;
}
public void Invoke(Result result)
{
this.onInvoke(result);
}
}
...
intent.PutExtra(CALLBACK_EXTRA, new CallbackWrapper(result => { ... }));
StartActivityForResult(intent);
The first problem is that when my callback gets deserialized from the intent bundle, I get the following exceptions:
System.NotSupportedException
Unable to activate instance of type CallbackWrapper from native handle 0xff...
System.MissingMethodException
No constructor found for CallbackWrapper::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)
I add the constructor as explained in the exception:
class CallbackWrapper : Java.Lang.Object, ICallback
{
public CallbackWrapper(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer)
{
}
...
}
The exception is fixed, but now when the activity calls my handler, the onInvoke field is null. How do I get a reference to the onInvoke delegate that was used to create the Intent?
The solution - serialize a handle to the original object.
The first step is to enable object serialization. Serialization in Java is done using specially-named private methods, instead of through interface methods. Xamarin allows you to inject these methods into the generated Android callable wrappers using the Java.Interop.ExportAttribute attribute:
using Java.Interop;
class CallbackWrapper : Java.Lang.Object, ICallback
{
...
[Export("readObject", Throws = new[] { typeof(Java.IO.IOException), typeof(Java.Lang.ClassNotFoundException) })]
private void ReadObject(Java.IO.ObjectInputStream source)
{
}
[Export("writeObject", Throws = new[] { typeof(Java.IO.IOException), typeof(Java.Lang.ClassNotFoundException) })]
private void WriteObject(Java.IO.ObjectOutputStream destination)
{
}
}
Even if an ACW implements Serializable, the ACW itself has no useful fields - that why you need to serialize the managed state through the readObject/writeObject method pair.
Note that for this to work, your project needs to reference the Mono.Android.Export assembly, otherwise you'll get a build-time error.
The second part is getting a serializable reference to CallbackWrapper. This can be achieved using System.Runtime.InteropServices.GCHandle. The first step is to create a handle to the object and write it during serialization:
[Export("writeObject", Throws = new[] { typeof(Java.IO.IOException), typeof(Java.Lang.ClassNotFoundException) })]
private void WriteObject(Java.IO.ObjectOutputStream destination)
{
var handle = GCHandle.Alloc(this);
destination.WriteLong(GCHandle.ToIntPtr(handle).ToInt64());
}
The second step is deserialization:
[Export("readObject", Throws = new[] { typeof(Java.IO.IOException), typeof(Java.Lang.ClassNotFoundException) })]
private void ReadObject(Java.IO.ObjectInputStream source)
{
// deserialize GCHandle from stream
var handle = GCHandle.FromIntPtr(new IntPtr(source.ReadLong()));
// convert handle to object
var trueSelf = handle.Target as NativeValidationHandler;
// copy fields from original callback
this.onInvoke = trueSelf.onInvoke;
// free this handle
handle.Free();
}
The handle doesn't need to be a pinned handle, because we don't ever access the object's address, we just use the handle.
Note that in the above implementation you can only deserialize a callback once, because deserialization will free the handle. Alternatively you can allocate the handle once in the constructor and provide a Dispose method that frees that handle, if you wish to be able to deserialize the handle multiple times. Freeing the handle during deserialization also means that the object will never be collected if it's never deserialized, because the handle will prevent the object from being collected.
If you want to use Serializable than you are right. but i would recommend you to use Parcelable, because
Parcelable is a part of Android sdk and it's mainly made for parcelling purpose.
Parcelable is faster than Serializable because it doesn't use reflection while later does.
Although there is demerit that it has some boilerplate code.
Worth to read => https://android.jlelse.eu/parcelable-vs-serializable-6a2556d51538

Method swizzling a private framework iOS API

I know there are countless resources on method swizzling. However is it possible to swizzle a method from a private API? The problem is that there are no header files. I would like to swizzle a method from a private class in a PrivateFramework such as (random example) Message.framework methods
This is for personal testing, I understand that it will get rejected to oblivion by Apple.
You can use NSClassFromString to get Class and use runtime library to perform method swizzling. No header files required. You just need to know class name and method signature.
sel_getUid can be used when #selector(somePrivateMethod) give your error about somePrivateMethod is not valid selector (because header is not available)
Code taken from my Xcode plugin
SEL sel = sel_getUid("codeDiagnosticsAtLocation:withCurrentFileContentDictionary:forIndex:");
Class IDEIndexClangQueryProviderClass = NSClassFromString(#"IDEIndexClangQueryProvider");
Method method = class_getInstanceMethod(IDEIndexClangQueryProviderClass, sel);
IMP originalImp = method_getImplementation(method);
IMP imp = imp_implementationWithBlock(^id(id me, id loc, id dict, IDEIndex *idx) {
id ret = ((id (*)(id,SEL,id,id,id))originalImp)(me, sel, loc, dict, idx);
// do work
return ret;
});
method_setImplementation(method, imp);
Create a category on the class and add the declaration for the method you want to call. Then you can just instantiate an instance of the class and call the method.
This also works for unit testing private methods in your code.

class instance changed in callback method comparing to the main instance

in cpp:
void Character::jump(CCLayer *layer){
if (this->isAnimationPlaying) return;
up_or_down = UP;
body->runAction(CCSequence::actions(
CCMoveBy::actionWithDuration(0.5, ccp(0, 50)),
CCCallFuncND::actionWithTarget(body, callfuncND_selector(Character::upDownDone), this),
// CCCallFuncN::actionWithTarget(body, callfuncN_selector(Character::upDownDone)),
NULL));
this->isAnimationPlaying = true;
}
void Character::upDownDone(CCNode *node, CCObject *ob){
this->isAnimationPlaying = false; // *this is different from the this(class instance) in jump method, seems this in upDownDone is a new created instance*
}
So How can I get the class instance in a callback method? And can I make the this same for the main class instance and the callback's class instance?
EDIT:
Character is a class which has no parent class, and body is a member variable which is an instance of CCSprite.
Thanks.
because you are using body to call the function Character::upDownDone.
you should use this to call it.
CCCallFuncND* callFunc = CCCallFuncND::actionWithTarget(first_arg, secend_arg, third_arg);
body->runAction(callFunc);
assume your secend_arg is callfuncND_selector(Character::upDownDone)
then,
the first_arg is the caller, ie. the class instance who calls this function, in your code is body. but actually it should be this, or any instance of Charactor class
the CCNode* node (the first para that is been passed to your calling function) is the action runner, ie. body in your code. because you are using body->runAction()
the CCObject* obj (the second para that is been passed to your calling function) is a void pointer which is exactly the same with third_arg.
another way is use
void Character::upDownDone(CCNode *node, void *ob){
(Character*)ob->isAnimationPlaying = false;
}
Seems like you call the Character::upDownDone method using the instance "body" instead of this .May be you want this:
CCCallFuncND::actionWithTarget(this, callfuncND_selector(Character::upDownDone), body),

Static variable becomes zero

I have the following code:
public static int smsCount = 0 ;
public void startListener()
{
SendListener smsListener;
smsListener = new SendListener() {
public boolean sendMessage(Message message) {
++smsCount;
return true;
}
};
SMS.addSendListener(smsListener);
}
When I use a debugger I see that the value of smsCount is increasing. However, for some reason, it returns zero when I try to access it from outside the class. Is there anything wrong with this code ? sendMessage is called whenever an SMS is sent.
To expand on John B's comment, and to be more specific are you accessing it from a different runtime (application) context? Each runtime context has its own global (and therefore) static namespace. To ensure an object is a global singleton you should use the RuntimeStore.

What does Cannot create delegate without target for instance method or closure mean

I am using vala.
This is the source code that gives that compile time bug :
private Gee.HashMap<string,VoidFunc> fill_actions()
{
var actions = new Gee.HashMap<string,VoidFunc>();
MainWindow win = window;
actions["t"] = () => _puts(win.title);
return actions;
}
First I tried to access this.window directly but that gave another error so I tried this with a local scope variable.
Error when doing directly this.window :
This access invalid outside of instance methods
It sounds like VoidFunc is declared with [CCode (has_target = false)]. What that means is that no context information is passed to it, and AFAIK that is the only way delegates work as generic type arguments. The reason for this is limitations in C, so assuming VoidFunc looks like this:
[CCode (has_target = false)]
public delegate void VoidFunc ();
What you'll get in C is something like this:
typedef void (*VoidFunc)();
As opposed to something like this if you didn't have the [CCode (has_target = false)]:
typedef void (*VoidFunc)(gpointer user_data);
When you pass around callbacks in C you generally do so with between one and three arguments. Something with all three would look like this:
void foo (VoidFunc void_func, gpointer user_data, GDestroyNotify notify);
The first parameter is the actual function. The second parameter is the value to pass as user_data to the callback, and is what Vala uses to pass context information to the callback (which is what allows it to act as an instance method, or even a closure). The third parameter is used to specify a function to free user_data when it is no longer needed.
What [CCode (has_target = false)] means is that the delegate doesn't have a user_data argument, and therefore cannot be used as a closure or instance method.
The reason this is necessary with a generic argument is that generics look something like this at the C level:
void foo_bar (gpointer data, GDestroyNotify notify);
The first parameter is the data that you want to use as a generic value, the second is actually only added if the generic argument is owned (as it is in the case of the set methods in Gee), and is called with user_data as an argument when user_data is no longer needed.
As you can see, when trying to use a delegate as a generic, there is nowhere to put the user_data argument, which is why Vala only allows delegates without targets to be generic arguments.
The solution is basically to wrap the delegate in a class:
public delegate void VoidFunc ();
public class YourClass {
private class VoidFuncData {
public VoidFunc func;
public VoidFuncData (owned VoidFunc func) {
this.func = (owned) func;
}
}
private Gee.HashMap<string,VoidFuncData> fill_actions() {
var actions = new Gee.HashMap<string,VoidFuncData>();
string win = "win";
actions["t"] = new VoidFuncData (() => GLib.debug (win));
return actions;
}
}

Resources