Skip to main content
Version: 14 Oct 2024

Android Plugin Guide

Prerequisites

  • Install Android Studio. We recommend Koala or later as that is what we will be using for this guide.
  • Install Unity Hub
  • Install Unity 2022.3 LTS or later

Overview

In this guide we will be creating an Android plug-in that we can use inside of Unity. The plug-in will obtain access to hardware sensors on the Magic Leap 2 device, receive sensor value updates, and have the ability to be accessed from a Unity application. These can be very useful as they enable you to access features, like third-party code libraries and operating systems calls, that would otherwise not be available to Unity.

Create Android Studio Project

  1. Open Android Studio, create a New Project, and select No Activity
  2. Name your project, for this guide we will name it ExampleSensorPlugin. Ensure you set Language to Java and Minimum SDK to API 29 ("Q"; Android 10.0). You can also edit your package name if you like. Please take note of this as we will need it later.

Create Library Module

  1. Now we will create our library module. In the project browser, right click the root of your project, New->Module, and select Android Library in the new window that appears.
  2. Give this library module a name. For this example, we will call it AccelerometerLibrary. Ensure that Java is the selected language and the minimum SDK level is set to API 29.
  3. You now have an Android Studio project that is ready for you to begin creating a plugin for Unity! Your project file structure should now look something like this:

Create Library Class

  1. Next, we will create a Java Class that Unity will interact with to obtain sensor data. The location you create this will depend on what “project view” you have selected. For this example, we will set our project view to “Android”
  2. In the project panel, navigate to AccelerometerLibrary/java/com.magicleap.accelerometerlibrary, right click on this folder and select New->Java Class.
  3. Give your class a name. We will call ours AccelerometerListener. You should now have a folder structure like this:

Write Library Logic

  1. In order to access sensor data, we will need to ensure the class we created implements the SensorEventListener Interface. This interface requires that 2 functions be overriden, onSensorChanged and onAccuracyChanged.
package com.magicleap.accelerometerlibrary;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;

public class AccelerometerListener implements SensorEventListener {
@Override
public void onSensorChanged(SensorEvent event) {

}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {

}
}
  1. Next, we need to have a reference to the Unity Activity in order to access sensors from our Unity project that we will create later. Create a static Activity variable and a public function to set this activity from Unity
private static Activity unityActivity;

public static void setUnityActivity(Activity _activity)
{
unityActivity = _activity;
}
  1. Now, let's get a reference to our accelerometer sensor. To do this, we will need 2 new local variables and a new function called startSensorListening. This function will be called from Unity to initialize our sensor variables and allow us to start using the onSensorChanged callback.
package com.magicleap.accelerometerlibrary;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

public class AccelerometerListener implements SensorEventListener
{
private static Activity unityActivity;
private SensorManager sensorManager;
private Sensor accelerometer;

public static void setUnityActivity(Activity _activity)
{
unityActivity = _activity;
}

@Override
public void onSensorChanged(SensorEvent event)
{

}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy)
{

}

public void startSensorListening()
{
//initialize sensorManager
sensorManager = (SensorManager) unityActivity.getSystemService(Context.SENSOR_SERVICE);
//use sensorManager to obtain the devices default accelerometer
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
//register this sensor to begin listening and receiving SensorEventListener callbacks
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
}
  1. Almost done! Now that we have our sensor setup and ready to receive events, we have to access the sensor data through the onSensorChanged event. We do this with the event parameter, event.Values which is an array of floats. In this example, we will also need a new local variable, a float array called latestValues, to store our sensor data. Inside of the onSensorChanged event, let's set latestValues to event.Values.clone().
info

Android sensors always return an array of float values. The number of values inside the array is dependant on what sensor is currently being used. This example uses the accelerometer sensor, which always returns an array of 3 float values. If we were to use the ambient light sensor, it would only return an array of 1 float value. You can find more information about all Android sensors here

  1. Finally, we will create a way for Unity to access our latestValues array with a public function called getLatestValues that just returns the latestValues array.

Your AccelerometerListener class should now looks like this:

package com.magicleap.accelerometerlibrary;

import android.app.Activity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

public class AccelerometerListener implements SensorEventListener
{
private static Activity unityActivity;
private SensorManager sensorManager;
private Sensor accelerometer;
float[] latestValues = new float[3];

public static void setUnityActivity(Activity _activity)
{
unityActivity = _activity;
}

@Override
public void onSensorChanged(SensorEvent event)
{
latestValues = event.values.clone();
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy)
{

}

public void startSensorListening()
{
//initialize sensorManager
sensorManager = (SensorManager) unityActivity.getSystemService(Context.SENSOR_SERVICE);
//use sensorManager to obtain the devices default accelerometer
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
//register this sensor to begin listening and receiving SensorEventListener callbacks
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}

public float[] getLatestValues()
{
return latestValues;
}
}

Building our library into a .aar file

Before we move over to Unity to use our new android library, let's build it into a .aar (Android Archive) file.

info

Android Archive (AAR) is a file type that can contain C++ and Java code, resources such as assets, and a plug-in manifest file. You can use them to reuse components across multiple applications, or build variations of one application with the same core components.

  1. In our project view, select the AccelerometerLibrary module.

  2. Now, use the toolbar at the top to navigate to Build->Make Module "ExampleSensorPlugin.AccelerometerLibrary.main"

  3. Once this is done, open a file browser and navigate to where you saved your project. The aar file we just created will be at this file path: ExampleSensorPlugin\AccelerometerLibrary\build\outputs\aar

Keep this file location up as we will need to copy it into our Unity project.

Create Unity Project

The first thing we need to do is create a new Unity project that is setup for Magic Leap 2 development. Please follow our guide here on how to do this.

Use Android Library in Unity

  1. In your project, Create a folder structure like the following if it doesn't already exist: Assets/Plugins/Android
  2. In Assets/Plugins/Android, copy the .aar file from above that we built with Android Studio into this location
  3. Create a new C# script anywhere in your Assets folder called AccelerometerPluginManager
  4. Now, we will need to initialize 3 variables in order to interact with our Android Library from Unity. We will also need our Android Library Package Name and Java Class name we created.
using UnityEngine;

public class AccelerometerPluginManager : MonoBehaviour
{
private AndroidJavaClass unityClass;
private AndroidJavaObject unityActivity;
private AndroidJavaObject pluginInstance;

void Start()
{
unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
unityActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
//To reference our Android Library, we have to use the packagename and the name of the class we created
pluginInstance = new AndroidJavaObject("com.magicleap.accelerometerlibrary.AccelerometerListener");
}

}
  1. We now have a reference to our Android Library! From here we can begin calling functions from it. First, let's set the unityActivity with setUnityActivity. Then, we will call startSensorListening to initialize our sensor variables and begin receiving callbacks for sensor value updates.
using UnityEngine;

public class AccelerometerPluginManager : MonoBehaviour
{
private AndroidJavaClass unityClass;
private AndroidJavaObject unityActivity;
private AndroidJavaObject pluginInstance;

// Start is called before the first frame update
void Start()
{
unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
unityActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
pluginInstance = new AndroidJavaObject("com.magicleap.accelerometerlibrary.AccelerometerListener");

if (pluginInstance != null)
{
Debug.Log("Plugin Instance Loaded!");
pluginInstance.CallStatic("setUnityActivity", unityActivity);
pluginInstance.Call("startSensorListening");
}
}
}
  1. Now that we have initialized the accelerometer sensor and our library has begun receiving onSensorChanged events, let's see our values in Unity! To do this, inside the Update function, call getLatestValues and just log them to the console for now.
using UnityEngine;

public class AccelerometerPluginManager : MonoBehaviour
{
private AndroidJavaClass unityClass;
private AndroidJavaObject unityActivity;
private AndroidJavaObject pluginInstance;

// Start is called before the first frame update
void Start()
{
unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
unityActivity = unityClass.GetStatic<AndroidJavaObject>("currentActivity");
pluginInstance = new AndroidJavaObject("com.magicleap.accelerometerlibrary.AccelerometerListener");

if (pluginInstance != null)
{
Debug.Log("Plugin Instance Loaded!");
pluginInstance.CallStatic("setUnityActivity", unityActivity);
pluginInstance.Call("startSensorListening");
}
}

// Update is called once per frame
void Update()
{
if (pluginInstance != null)
{
float[] result = pluginInstance.Call<float[]>("getLatestValues");
Debug.Log($"[0]: {result[0]}, [1]: {result[1]}, [2]: {result[2]}");
}
}
}
tip

Instead of using Unity's Update function to get sensor values, you can use callbacks! There are various ways you can achieve this for your plug-in. We have guides you can follow for two of those methods, UnitySendMessage and AndroidJavaProxy

  1. Lastly, we will put this script component on a gameobject in our scene

You can now build and run this on your ML2 device! You should see log output of the sensor values in the format you specified in the Update function.

Congratulations! You built your first Android Library for Unity!