Android Unique Identifier
The Unique Identifier on Android has gone through a few changes due to bugs, so there is a chance you will end up in a situation where you are relying on an ID that has changed between Unity versions.
This page tries to document the behavior so you can work around this.
Description of the Algorithm
The original plan was to use getDeviceId() from TELEPHONY_SERVICE if permitted and available, and fall back to ANDROID_ID if that failed and MAC address as a last resort.
But in older versions of Unity (4.5 and back) there was a bug; if the application did not have the READ_PHONE_STATE permission, the ANDROID_ID path was not used either, so it always fell back to the MAC address.
This was later “fixed” by checking for the READ_PHONE_STATE permission. But now a new problem was introduced; ANDROID_ID would not be used if we had the permission but getDeviceId() still returned NULL.
Rather then making another fix and creating a third way to calculate the identifier, the old behavior was later reintroduced.
The “fixed” behavior still ended up in 5.0+ so that is why it is the current algorithm.
Another problem is that some Android tablets fail to return MAC address when WiFi is turned off, possibly resulting in a different ID.
The current algorithm (5.0+ and possibly some version of 4.6)
string id;
// Needs android.permission.READ_PHONE_STATE and a phone like device (can fail on tablets)
if(checkPermission(READ_PHONE_STATE))
id = context.getSystemService(Context.TElEPHONY_SERVICE).getDeviceId()
else
id = context.getContentResolver().getString(Secure.ANDROID_ID);
if(!id)
id = getMacAddress();
id = md5hash(id);
The original broken algorithm (4.6-)
string id;
// Needs android.permission.READ_PHONE_STATE and a phone like device (can fail on tablets)
if(!checkPermission(READ_PHONE_STATE))
id = NULL;
else {
id = context.getSystemService(Context.TElEPHONY_SERVICE).getDeviceId()
if(id == NULL)
id = context.getContentResolver().getString(Secure.ANDROID_ID);
}
if(!id)
id = getMacAddress();
id = md5hash(id)
Reading the MAC address
The current implementation iterates over all interfaces, and gets the hardware address of the first interface that is not the loopback interface.
Result is then transformed to a 12 chars long lower case hex string.
If the mac could not be read it is set to “00000000000000000000000000000000” (32 zeroes).
Simulating the ID in C#
The following script is an example on how to generate the ID yourself, to emulate older or newer behavior. It is not fully tested and the way it reads the MAC address is not the same as the native code, but hopefully it is a place to start if you need this functionality.
// Hash an input string and return the hash as
// a 32 character hexadecimal string.
static string getMd5Hash(string input)
{
if (input == "")
return "";
MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));
StringBuilder sBuilder = new StringBuilder();
for (int i = 0; i < data.Length; i++)
sBuilder.Append(data[i].ToString("x2"));
return sBuilder.ToString();
}
static string generateDeviceUniqueIdentifier(bool oldBehavior)
{
string id = "";
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject activity = jc.GetStatic<AndroidJavaObject>("currentActivity");
AndroidJavaClass contextClass = new AndroidJavaClass("android.content.Context");
string TELEPHONY_SERVICE = contextClass.GetStatic<string>("TELEPHONY_SERVICE");
AndroidJavaObject telephonyService = activity.Call<AndroidJavaObject>("getSystemService", TELEPHONY_SERVICE);
bool noPermission = false;
try
{
id = telephonyService.Call<string>("getDeviceId");
}
catch (Exception e) {
noPermission = true;
}
if(id == null)
id = "";
// <= 4.5 : If there was a permission problem, we would not read Android ID
// >= 4.6 : If we had permission, we would not read Android ID, even if null or "" was returned
if((noPermission && !oldBehavior) || (!noPermission && id == "" && oldBehavior))
{
AndroidJavaClass settingsSecure = new AndroidJavaClass("android.provider.Settings$Secure");
string ANDROID_ID = settingsSecure.GetStatic<string>("ANDROID_ID");
AndroidJavaObject contentResolver = activity.Call<AndroidJavaObject>("getContentResolver");
id = settingsSecure.CallStatic<string>("getString", contentResolver, ANDROID_ID);
if(id == null)
id = "";
}
if(id == "")
{
string mac = "00000000000000000000000000000000";
try
{
StreamReader reader = new StreamReader("/sys/class/net/wlan0/address");
mac = reader.ReadLine();
reader.Close();
}
catch (Exception e) {}
id = mac.Replace(":", "");
}
return getMd5Hash(id);
}