3

I can't find how to get ListArray variables from an AndroidJavaObject in C#.
I'm trying to make a for function using a Count for the number of items in the ListArray that is stored as an AndroidJavaObject. But I need to know how to get the Count from the AndroidJavaObject and how to use it properly.
This is what I'm using to get the variables, also notice that "packages" is not an AndroidJavaObject[].

AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentActivity = jc.GetStatic<AndroidJavaObject>("currentActivity");
int flag = new AndroidJavaClass("android.content.pm.PackageManager").GetStatic<int>("GET_META_DATA");
AndroidJavaObject pm = currentActivity.Call<AndroidJavaObject>("getPackageManager");
AndroidJavaObject packages = pm.Call<AndroidJavaObject>("getInstalledApplications", flag);

it's very rudimentary at this point, but it works thanks to some help from this How to get installed apps list with Unity3D?

The progress thus far stalls at getting the icon, everything else works perfect, I need a way to get either a string that links to the icon somehow, or the Texture2D of the icon.
Another alternative would be a to use a AndroidJavaObject that contains a drawable as if it's a Texture2D. I have no idea how to accomplish any of this though.
Another Idea I had was to convert it to another variable, like byte[] that can be transferred and converted back, but I have yet to find a method of that which works under the circumstance.

int count = packages.Call<int>("size");
AndroidJavaObject[] links = new AndroidJavaObject[count];
string[] names = new string[count];
Texture2D[] icons = new Texture2D[count];
int ii =0;
for(int i=0; ii<count;){
    //get the object
    AndroidJavaObject currentObject = packages.Call<AndroidJavaObject>("get", ii );
    try{
        //try to add the variables to the next entry
        links[i] = pm.Call<AndroidJavaObject>("getLaunchIntentForPackage", currentObject.Get<AndroidJavaObject>("processName"));
        names[i] = pm.Call<string>("getApplicationLabel", currentObject);
        icons[i] = pm.Call<Texture2D>("getApplicationIcon", currentObject);
        Debug.Log ("(" + ii + ") " + i +" "+ names[i]);
        //go to the next app and entry
        i++;
        ii++;
    }
    catch
    {
        //if it fails, just go to the next app and try to add to that same entry.
        Debug.Log("skipped "+ ii);
        ii++;
    }

}

I really hate to ask two questions in a row here, and I apologize for having to do so, but this is a difficult and seemingly awkward circumstance, that has little to no documentation (that I can find).

Community
  • 1
  • 1
Jonathan C
  • 117
  • 1
  • 9

2 Answers2

5

First of all the docs on the interop between Unity3D and Android are scarce at best.

The most important thing is that the only interface Unity exposes to work with java object is AndroidJavaObject and that interface has only a couple of methods defined. You can only use those ones and only those.

This means that you don't get a Count object when working with an java array and you'll still be working with AndroidJavaObject.

int count = packages.Call<int>("size"); //getting the size of the array

for( int i = 0; i < count; i++ )
{
 //getting the object at location i
 AndroidJavaObject currentObject = packages.Call("get", i );

 //use currentObject using the same methods as before: Call, CallStatic, Get, GetStatic
}

I know this is verbose, and writing code like this is hard to test, you need to make and apk and deploy it to a device to check that it runs.

Probably a faster way of doing this is to make your own java class that does all this on the java side and expose one method that gets called from the Unity side. This way at least you get the benefit of static typing when making a jar that you'll add in the Plugins/Android folder.

Radu Diță
  • 8,910
  • 1
  • 18
  • 27
  • The method you describe for getting the count doesn't work unfortunately, I tried get, and call, static and not, capital and non. I'm used to the re-deploy after every small change at this point, haha. I don't mind working with `AndroidJavaObject` as long as I get the variables I need long run. to do it java side I would probably end up having to make a call that returns nothing and instead, through java, saves the variables to a file I can parse with C#, I would also have to learn java, and Eclipse or the like as I have next to no experience in both. I'll edit my post to use your example. – Jonathan C Jul 06 '15 at 09:37
  • I've updated my answer, the method you need to call is size – Radu Diță Jul 06 '15 at 10:01
  • This seems to work perfect, had/have trouble with getting the variables, had to get creative with getting the names by doing `pm.Call("getApplicationLabel", currentObject)`. I think I need to change the link string to an `AndroidJavaObject` so I can store a launch intent that I can later figure out how to use `something.Call("startActivity", intent)`. I'll update the post with how it's going thus far, if you got any ideas I'd be glad to hear them, but I'll have to check again in the morning. – Jonathan C Jul 06 '15 at 11:24
  • Updated my post, great progress, stalled at the last possible thing. – Jonathan C Jul 06 '15 at 22:52
  • I'll just vote as correct, I'm giving up and just taking a crash course in java to do it in Android Studio. – Jonathan C Jul 18 '15 at 20:47
3

I'm trying to do the same thing here, but my launcher must only show the CardBoard apps. What i've decided is to make a library in java and import it to Unity as a plugin:

This is my Java class:

public class Launcher extends UnityPlayerActivity {

private List<ApplicationInfo> cbApps;

@Override
protected void onStart() {
    super.onStart();

    PackageManager pm= getPackageManager();
    List<ApplicationInfo> lista = pm.getInstalledApplications(PackageManager.GET_META_DATA);
    cbApps= new LinkedList<ApplicationInfo>();

    for(ApplicationInfo ai : lista){
        Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
        intentToResolve.addCategory("com.google.intent.category.CARDBOARD");
        intentToResolve.setPackage(ai.packageName);
        if(pm.resolveActivity(intentToResolve, 0)!=null) {
            cbApps.add(ai);
        }
    }
}


public int getListSize(){
    return cbApps.size();
}

And here my C# method:

void Start () {
    AndroidJavaClass unity = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
    AndroidJavaObject currentActivity = unity.GetStatic<AndroidJavaObject> ("currentActivity");
    texto.text=""+currentActivity.Call<int> ("getListSize");

}

This way I can create in Java every method that I need to acces the list. The problem I'm still having is trying to get the Texture2D of the icons. I've tried returning the Drawable from Java and accesing with a Call just as you did, but it doesn't work at all. I've been 2 days working with that, I'll let you know if I find a solution.

EDIT: Finally I could get the images:

First in Java I created this method:

public byte[] getIcon(int i) {
        BitmapDrawable icon= (BitmapDrawable)context.getPackageManager().getApplicationIcon(cbApps.get(i));
        Bitmap bmp = icon.getBitmap();
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
        byte[] byteArray = stream.toByteArray();
        return byteArray;
    }

That way I can access the Byte array representing the Drawable from Unity and show it as the main texture of a Plane:

void Start () {
    using (AndroidJavaClass unity = new AndroidJavaClass ("com.unity3d.player.UnityPlayer")) {
        context = unity.GetStatic<AndroidJavaObject> ("currentActivity");
    }
    using (AndroidJavaClass pluginClass=new AndroidJavaClass("com.droiders.launcher.Launcher")) {
        launcher= pluginClass.CallStatic<AndroidJavaObject>("getInstance", context);
        byte[] bytes= launcher.Call<byte[]>("getIcon",0);
        Texture2D tex= new Texture2D(2,2);
        tex.LoadImage(bytes);
        plane.GetComponent<Renderer>().material.mainTexture=tex;
    }

}

Hope it helps you. It's been hard but this beast has been tamed :P Also thanks you all for your ideas.

Varu
  • 301
  • 1
  • 14
  • If you have a new question, please ask it by clicking the [Ask Question](http://stackoverflow.com/questions/ask) button. Include a link to this question if it helps provide context. – Sam FarajpourGhamari Oct 28 '15 at 10:57
  • 2
    It wasn't a new question, he asked 2, I answer the first one but I told him I don't have the answer for the second one. Yet thanks, I'll do what you proposed. – Varu Oct 28 '15 at 11:28
  • Yeah the icon part is very tricky. I know it can be done if you convert the Drawable to an image byte array through java, then convert the byte array to a Texture2D in C#, But I have no idea how to do that. And I have no idea if there is an easier and/or more efficient method. Honestly It's probably just more effective to build it through Android Studio. – Jonathan C Oct 29 '15 at 22:49
  • Yes, it would probably be easier to do it in Java, but I have to do my launcher working fully with cardboard, and I'd like to make some other things that I don't know how to do in Java. And also it is a nice chance to learn how to make plugins as it is my first time :P – Varu Oct 30 '15 at 08:44
  • Thx by the way fot the byte array idea, I'm trying that – Varu Oct 30 '15 at 08:45
  • The edit looks really good, I'll have to keep that in mind for future reference in case I need to do this sort of thing again. Also, not that it matters at this point, but android studio does have full cardboard support, although I understand wanting to stick with an IDE/language you have more experience in. Best of luck with your project. – Jonathan C Nov 03 '15 at 10:31