The java code returns an MEID (Android Device ID) on CDMA phones (Verizon, Sprint), an IMEI on GSM phones (AT&T, T Mobile) and an Android ID on WIFI-only devices (no cellular network).
This code is based on Android Java Plugin for Unity example project in the Unity manual.
Why bother with a plugin?
I tried using Unity 3.5’s built-in SystemInfo.deviceUniqueIdentifier getter for obtaining a unique device ID but it didn’t meet our ad network’s requirements.
The set up in Unity 3.5
- Drag the AndroidJava.jar file inside /Assets/Plugins/Android/. /Assets/Plugins/Android/bin/ worked too. This .jar file is created via Eclipse or command line javac based on the Java source code
- Drag the example libjni.so file inside /Assets/Plugins/Android/. Make sure it’s inside /Plugins/Android or you’ll get a “DllNotFoundException: jni” error when you test on device with adb.
- Drag JNI.cs into /Assets/Plugins/
- Drag JavaVM.cs into /Assets/Plugins/
- Make sure your project has an AndroidManifest.xml file inside /Assets/Plugins/Android/ with android.permission.READ_PHONE_STATE permission set. android.permission.READ_PHONE_STATE is needed to access an Android phone/tablet’s Android ID, IMEI or MEID. If you’re making HTTP calls you’ll also need additional permissions, etc.
Code that worked:
...
private static IntPtr JavaClass;
private static int GetDeviceId;
...
///if you're calling the plugin from a static method, you still have to make sure an instance of this script is attached to
///an empty GameObject in Hierarchy so Start() method runs & the below code initializes:
void Start() {
#if UNITY_ANDROID
// attach our thread to the java vm; obviously the main thread is already attached but this is good practice..
JavaVM.AttachCurrentThread();
// first we try to find our main activity..
IntPtr cls_Activity = JNI.FindClass("com/unity3d/player/UnityPlayer");
int fid_Activity = JNI.GetStaticFieldID(cls_Activity, "currentActivity", "Landroid/app/Activity;");
IntPtr obj_Activity = JNI.GetStaticObjectField(cls_Activity, fid_Activity);
Debug.Log("obj_Activity = " + obj_Activity);
// create a JavaClass object...
//"com/yourCompany/yourProjectName" should be your Bundle ID in Unity's Player Settings & should be the package name in your Java class
IntPtr cls_JavaClass = JNI.FindClass("com/yourCompany/yourProjectName/JavaClass");
int mid_JavaClass = JNI.GetMethodID(cls_JavaClass, "<init>", "(Landroid/app/Activity;)V");
IntPtr obj_JavaClass = JNI.NewObject(cls_JavaClass, mid_JavaClass, obj_Activity);
Debug.Log("java Helper object = " + obj_JavaClass);
// create a global reference to the JavaClass object and fetch method id(s)..
JavaClass = JNI.NewGlobalRef(obj_JavaClass);
GetDeviceId = JNI.GetMethodID(cls_JavaClass, "GetDeviceId", "()Ljava/lang/String;");
Debug.Log("JavaClass global ref = " + JavaClass);
Debug.Log("JavaClass GetDeviceId method id = " + GetDeviceId);
#endif
}
...
/// <summary>
/// Gets the unique device for Android devices (IMEI, MEID or Android ID).
/// </summary>
/// <returns>
/// Returns the id as a string.
/// </returns>
private string GetTheDeviceID()
{
//didn't work... supposed to return IMEI but didn't
//return SystemInfo.deviceUniqueIdentifier;
String dID = "";
#if UNITY_ANDROID
// again, make sure the thread is attached..
JavaVM.AttachCurrentThread();
// get the Java String object from the Helper java object
IntPtr str_cacheDir = JNI.CallObjectMethod(JavaClass, GetDeviceId);
Debug.Log("str_cacheDir = " + str_cacheDir);
// convert the Java String into a Mono string
IntPtr stringPtr = JNI.GetStringUTFChars(str_cacheDir, 0);
Debug.Log("stringPtr = " +stringPtr);
dID = Marshal.PtrToStringAnsi(stringPtr);
JNI.ReleaseStringUTFChars(str_cacheDir, stringPtr);
#endif
Debug.Log(" Device ID value is = " + dID);
return devID;
}
/// call GetTheDeviceID() when you need to use it
...
The Java Code
The below code is based on requirements by ad networks that need to use a specific device ID for metrics like conversion tracking. It’s the main source file for generating the AndroidJava.jar you need to import into Unity’s Plugins folder.
The Device ID & hashing methods implementations come directly from Millennial Media‘s public wiki.
package com.yourCompany.yourProjectName;
import android.app.Activity;
import android.content.Context;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;
import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class JavaClass
{
private Activity mActivity;
public JavaClass(Activity currentActivity)
{
Log.i("JavaClass", "Constructor called with currentActivity = " + currentActivity);
mActivity = currentActivity;
}
public String GetDeviceId()
{
// Get the device ID
String auid = android.provider.Settings.Secure.ANDROID_ID + "android_id";
Context context = mActivity.getApplicationContext();
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
if(tm != null)
{
try{
auid = tm.getDeviceId();
}
catch (SecurityException e){
e.printStackTrace();
}
tm = null;
}
if(((auid == null) || (auid.length() == 0)) && (context != null))
auid = Secure.getString(context.getContentResolver(), Secure.ANDROID_ID);
if((auid == null) || (auid.length() == 0))
auid = null;
return auid;
}
public String GetHashedDeviceIdSHA1()
{
String deviceId = GetDeviceId();
String hashedDeviceId = hashInputSHA1(deviceId);
return hashedDeviceId;
}
public String GetHashedDeviceIdMD5()
{
String deviceId = GetDeviceId();
String hashedDeviceId = hashInputMD5(deviceId);
return hashedDeviceId;
}
/**
* <p>
* Hashes the given plain text using the MD5 algorithm.
* </p>
*
* <p>
* Example:
* </p>
*
* <p>
* 098f6bcd4621d373cade4e832627b4f6
* </p>
*
* @param input
* The raw input which must be hashed.
* @return A String representing the MD5 hashed output.
*/
public static String hashInputMD5(String input)
{
String rv = null;
if (input != null)
{
try
{
// Hash the user ID
byte[] hashBytes = null;
MessageDigest md = MessageDigest.getInstance("MD5");
synchronized (md)
{
hashBytes = md.digest(input.getBytes());
}
// Convert the hashed bytes into a properly formatted String
if (hashBytes != null)
{
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes)
{
String hexString = Integer.toHexString(0x00FF & b);
sb.append((hexString.length() == 1) ? "0" + hexString
: hexString);
}
rv = sb.toString();
}
}
catch (NoSuchAlgorithmException exception)
{
// This exception should never occur
}
}
return rv;
}
/**
* <p>
* Hashes the given plain text using the SHA-1 algorithm.
* </p>
*
* <p>
* Example:
* </p>
*
* <p>
* a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
* </p>
*
* @param userId
* The raw input which must be hashed.
* @return A String representing the SHA-1 hashed input.
*/
public static String hashInputSHA1(String input)
{
String rv = null;
if (input != null)
{
try
{
// Hash the user ID
byte[] hashBytes = null;
MessageDigest md = MessageDigest.getInstance("SHA1");
synchronized (md)
{
hashBytes = md.digest(input.getBytes());
}
// Convert the hashed bytes into a properly formatted String
if (hashBytes != null)
{
StringBuilder sb = new StringBuilder();
for (byte b : hashBytes)
{
String hexString = Integer.toHexString(0x00FF & b);
sb.append((hexString.length() == 1) ? "0" + hexString
: hexString);
}
rv = sb.toString();
}
}
catch (NoSuchAlgorithmException exception)
{
// This exception should never occur
}
}
return rv;
}
}
AndroidManifest.xml
If you don’t specify the correct permissions for your app and try to do things like make an HTTP request or grab the Device ID, you’ll get an error, like this:
10-02 15:06:49.223 7776 7784 W System.err: java.lang.SecurityException: Requires READ_PHONE_STATE: Neither user 10109 nor current process has android.permission.READ_PHONE_STATE.
10-02 15:06:49.223 7776 7784 W System.err: at android.os.Parcel.readException(Parcel.java:1322)
10-02 15:06:49.223 7776 7784 W System.err: at android.os.Parcel.readException(Parcel.java:1276)
10-02 15:06:49.223 7776 7784 W System.err: at com.android.internal.telephony.IPhoneSubInfo$Stub$Proxy.getDeviceId(IPhoneSubInfo.java:150)
10-02 15:06:49.231 7776 7784 W System.err: at android.telephony.TelephonyManager.getDeviceId(TelephonyManager.java:216)
10-02 15:06:49.231 7776 7784 W System.err: at com.yourCompany.yourProjectName.JavaClass.GetDeviceId(JavaClass.java:44)
10-02 15:06:49.231 7776 7784 W System.err: at com.unity3d.player.UnityPlayer.nativeRender(Native Method)
10-02 15:06:49.231 7776 7784 W System.err: at com.unity3d.player.UnityPlayer.onDrawFrame(Unknown Source)
10-02 15:06:49.231 7776 7784 W System.err: at android.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1363)
10-02 15:06:49.231 7776 7784 W System.err: at android.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1118)
I grabbed the default one from inside Unity.app on my Mac:
/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidDevelopmentPlayer/AndroidManifest.xml
Kept all as is, just added my permissions and it worked:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
android:installLocation="preferExternal"
android:versionCode="1"
android:versionName="1.0">
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"
android:anyDensity="true"/>
<application
android:icon="@drawable/app_icon"
android:label="@string/app_name"
android:debuggable="true">
//...
</application>
<!-- PERMISSIONS -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
</manifest>
Some Other Possible Errors
Here’s a fun one, based on a dumb mistake I made. I put the libjni.so into the wrong folder, /Assets/Plugins/, instead of /Assets/Plugins/Android/:
10-02 14:43:17.147 7587 7596 E Unity : Unable to find jni
10-02 14:43:17.147 7587 7596 E Unity : Unable to find jni
10-02 14:43:17.155 7587 7596 E Unity : Unable to find jni
10-02 14:43:17.163 7587 7596 E Unity : Unable to find jni
10-02 14:43:17.171 7587 7596 E Unity : Unable to find jni
10-02 14:43:17.171 7587 7596 E Unity : Unable to find jni
10-02 14:43:17.179 7587 7596 E Unity : Unable to find jni
10-02 14:43:17.186 7587 7596 E Unity : Unable to find jni
10-02 14:43:17.897 1410 1410 I wpa_supplicant: WPS-AP-AVAILABLE
10-02 14:43:17.905 1320 1411 V WifiMonitor: Event [WPS-AP-AVAILABLE ]
10-02 14:43:17.905 1320 1411 D WifiMonitor: WPS Event: WPS-AP-AVAILABLE
10-02 14:43:18.264 1391 1391 D RadioSignalLevel: evdo dbmLevel: 4, snrLevel: 3
10-02 14:43:18.522 7587 7596 I Unity : DllNotFoundException: jni
10-02 14:43:18.522 7587 7596 I Unity : at (wrapper managed-to-native) JavaVM:AttachCurrentThread ()
10-02 14:43:18.522 7587 7596 I Unity : at MyUnityDeviceIdScript.Start () [0x00000] in <filename unknown>:0
10-02 14:43:18.522 7587 7596 I Unity :
10-02 14:43:18.522 7587 7596 I Unity : (Filename: Line: -1)
10-02 14:43:18.522 7587 7596 I Unity :
10-02 14:43:21.561 7587 7596 I Unity : checkDeviceID() called
10-02 14:43:21.561 7587 7596 I Unity :
10-02 14:43:21.561 7587 7596 I Unity : (Filename: ./Runtime/ExportGenerated/AndroidManaged/UnityEngineDebug.cpp Line: 43)
10-02 14:43:21.561 7587 7596 I Unity :
10-02 14:43:21.671 7587 7596 E Unity : Unable to find jni
10-02 14:43:21.679 7587 7596 E Unity : Unable to find jni
10-02 14:43:21.679 7587 7596 E Unity : Unable to find jni
10-02 14:43:21.679 7587 7596 E Unity : Unable to find jni
10-02 14:43:21.694 7587 7596 I Unity : DllNotFoundException: jni
10-02 14:43:21.694 7587 7596 I Unity : at (wrapper managed-to-native) JavaVM:AttachCurrentThread ()
10-02 14:43:21.694 7587 7596 I Unity : at MyUnityDeviceIdScript.GetUniqueDeviceID () [0x00000] in <filename unknown>:0
10-02 14:43:21.694 7587 7596 I Unity : at MyUnityDeviceIdScript.callTheUrlWithDeviceIDParam () [0x00000] in <filename unknown>:0
10-02 14:43:21.694 7587 7596 I Unity : at GameManager.checkDeviceID () [0x00000] in <filename unknown>:0
10-02 14:43:21.694 7587 7596 I Unity :
As far as I’ve seen, this code only works on a device. If you try to test inside Unity, you’ll get a similar error:
DllNotFoundException: jni
MyUnityDeviceIdScript.Start () (at Assets/Scripts/MyUnityDeviceIdScript.cs:21)
Once you test on a device, if all else if good, this error should go away.
NOTE: looks like this code requires some time to load up the JNI .so file. Not absolutely sure yet, but it appears if I have a fairly complex project and I try to call a JNI related method very soon after app loading, the app crashes with weird mystery output in adb logcat. Need to look into this more to be sure about the details.