Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Passing AndroidJavaObject as .Call parameter

Discussion in 'Android' started by AngryAnt, Dec 18, 2013.

  1. AngryAnt

    AngryAnt

    Keyboard Operator Moderator

    Joined:
    Oct 25, 2005
    Posts:
    3,045
    OHAI

    I've not had much chance to play around with the JNI bridge before now and of-course I run smack into a problem.

    What I need to achieve is basically to install an APK via an Intent - like so:
    Code (csharp):
    1. Intent intent = new Intent ("android.intent.action.VIEW");
    2. Uri uri = Uri.parse (apkURIString);
    3. intent.setDataAndType (uri, "application/vnd.android.package-archive");
    4. startActivity (intent);
    However I'm facing some difficulty when implementing this via the AndroidJavaObject/Class interface - particularly in the setDataAndType call where the uri is passed in.

    For debugging, I've split that call into two - setData followed by setType:
    Code (csharp):
    1. AndroidJavaObject intent = new AndroidJavaObject ("android.content.Intent", "android.intent.action.VIEW");
    2. AndroidJavaClass uriClass = new AndroidJavaClass ("android.net.Uri");
    3. AndroidJavaObject uri = uriClass.CallStatic<AndroidJavaObject> ("parse", updatePath);
    4. intent.Call ("setData", uri);
    5. intent.Call ("setType", "application/vnd.android.package-archive");
    6. AndroidJavaClass unityPlayerClass = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
    7. AndroidJavaObject currentActivity = unityPlayerClass.GetStatic<AndroidJavaObject> ("currentActivity");
    8. currentActivity.Call ("startActivity", intent);
    When running this, I get as far as the setData call, at which point the JNI layer bails out with a Java exception:
    Code (csharp):
    1. E/Unity   (  616): System.Exception: java.lang.NoSuchMethodError: no method with name='setData' signature='(Landroid.net.Uri$StringUri;)V' in class Landroid/content/Intent;
    2. E/Unity   (  616):   at UnityEngine.AndroidJNISafe.CheckException () [0x00000] in <filename unknown>:0
    3. E/Unity   (  616):   at UnityEngine.AndroidJNISafe.GetMethodID (IntPtr obj, System.String name, System.String sig) [0x00000] in <filename unknown>:0
    4. E/Unity   (  616):   at UnityEngine._AndroidJNIHelper.GetMethodID (IntPtr jclass, System.String methodName, System.String signature, Boolean isStatic) [0x00000] in <filename unknown>:0
    5. E/Unity   (  616):   at UnityEngine.AndroidJNIHelper.GetMethodID (IntPtr javaClass, System.String methodName, System.String signature, Boolean isStatic) [0x00000] in <filename unknown>:0
    6. E/Unity   (  616):   at UnityEngine._AndroidJNIHelper.GetMethodID (IntPtr jclass, System.String methodName, System.Object[] args, Boolean isStatic) [0x00000] in <filename unknown>:0
    7. E/Unity   (  616):   at UnityEngine.AndroidJNIHelper.GetMethodID (IntPtr jclass, System.String methodName, System.Object[] args, Boolean isStatic) [0x0000
    I am particularly curious about the parameter being sent in the call signature "Landroid.net.Uri$StringUri;" - as a bit of digging would seem to indicate that "$" indicates a contained class. So is my passed AndroidJavaObject being resolved into the wrong type somehow?

    Looking at the disassembly of the .Call method, it seems that this is indeed the expected way to pass reference parameters to Java land, so where did I get it wrong?

    Since it breaks down here I also fully expect the later call to activity.startActivity - taking the intent reference - to fail in the same manner.

    Any pointers would be greatly appreciated.
     
  2. AngryAnt

    AngryAnt

    Keyboard Operator Moderator

    Joined:
    Oct 25, 2005
    Posts:
    3,045
    ITS A TRAP!

    So Erik Hemming was nice enough to spare me a bit of time to look at this issue. In case you run into this yourself, here's what happened:

    In the current implementation of AndroidJava*, the call method actually restricts your overload choice by your initial choice of using the generic Call method vs. the basic Call method with no return type. The latter actually assumes that you are only looking for overloads with a void return type.

    Which sort of makes sense, but it's a pretty nasty trap as I at least am not accustomed to seeking out overloads by looking at their return type as opposed to just their parameter list.

    Hopefully we'll soon see a change here where the basic Call method doesn't restrict to void, but in stead looks for it first and then falls back on a non-void match to the parameter list.

    Well, nothing to see here. Thanks for stopping by. I hope you found this juggling act enlightening ;)
     
  3. jvil

    jvil

    Joined:
    Jul 13, 2012
    Posts:
    263
    If you've a custom Android plugin extending UnityPlayerActivity it should be something like this:

    Android code
    Code (csharp):
    1. public void installAPK(String mUri, String mType)
    2. {
    3.     Intent intent = new Intent(Intent.ACTION_VIEW);
    4.     intent.setDataAndType(Uri.parse(mUri), mType);
    5.     startActivity(intent);
    6. }
    Then call this function on Unity side:

    Unity code
    Code (csharp):
    1.  
    2. public void installAPKAndroid(string mUri, string mType)
    3. {
    4.     using (AndroidJavaClass jc = new AndroidJavaClass("com.your.package.class"))
    5.     {
    6.         jc.Call("installAPK", new object[] { mUri, mType });
    7.     }
    8. }
    As Android Intent only requires 2 strings (can parse Uri from string) you can pass them as object[].
     
    chrisk8er likes this.
  4. AngryAnt

    AngryAnt

    Keyboard Operator Moderator

    Joined:
    Oct 25, 2005
    Posts:
    3,045
    Thanks. Yea I would indeed have preferred a much cleaner separation between Java land and C# via a plugin. Unfortunately in my specific case that is not an option. Hence my aggressive use of the introspection API :)
     
  5. Hippiecode

    Hippiecode

    Joined:
    Feb 13, 2012
    Posts:
    110
    hi...
    i am doing same stuff..what we pass in these parameter

    public void installAPKAndroid(string mUri, string mType)
     
  6. Hippiecode

    Hippiecode

    Joined:
    Feb 13, 2012
    Posts:
    110
    plz provide unity package for what u made.Bcoz i am trying the same thing.Thanks in advance...
     
  7. estufa

    estufa

    Joined:
    Mar 25, 2016
    Posts:
    2
    I love you, man
     
  8. Ran_Intendu

    Ran_Intendu

    Joined:
    Jan 20, 2015
    Posts:
    2
    This code "works on my machine":

    Code (CSharp):
    1. AndroidJavaObject intent = new AndroidJavaObject("android.content.Intent", "android.intent.action.VIEW");
    2. AndroidJavaClass Uri = new AndroidJavaClass("android.net.Uri");
    3. AndroidJavaObject uri = Uri.CallStatic<AndroidJavaObject>("parse", "file://mnt/sdcard/Download/sample.apk");
    4. intent.Call<AndroidJavaObject> ("setDataAndType", uri, "application/vnd.android.package-archive");
    5.  
    6. AndroidJavaClass unity = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    7. AndroidJavaObject currentActivity = unity.GetStatic<AndroidJavaObject>("currentActivity");
    8. currentActivity.Call("startActivity", intent);
     
    A-Zhdanov likes this.
  9. Privateer

    Privateer

    Joined:
    Jun 5, 2016
    Posts:
    23
    I have a similar problem.
    I'm making a Unity wrapper for an Android library and I need to take an instance of a class that is defined inside the library, save it, and later, send it back as a parameter for a library method. I have no choice but to save it as an AndroidJavaObject. Could this be a problem when I try to pass it as a parameter? How can I solve this?