Search Unity

  1. We want you to join us at GDC this year! We've added new sessions to Unity Central, space is limited so sign up now!
    Dismiss Notice
  2. Magic Leap’s Lumin SDK Technical Preview for Unity lets you get started creating content for Magic Leap One™. Find more information on our blog!
    Dismiss Notice
  3. Unity 2017.3 has arrived! Read about it here.
    Dismiss Notice
  4. ARCore is out of developer preview! Read about it here.
    Dismiss Notice
  5. Tell us about your experience here and you’ll get early access to the 2018 Game Studios report + more goodies.
    Dismiss Notice
  6. Be the first to take a peek at upcoming 2D Animation Preview. Drop into the forum and check it out!
    Dismiss Notice
  7. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Unique Identifier Details

Discussion in 'Android' started by jonas-minnberg, Sep 7, 2015.

  1. jonas-minnberg


    Unity Technologies

    Oct 8, 2014
    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)

    Code (pseudo):
    2. string id;
    3. // Needs android.permission.READ_PHONE_STATE and a phone like device (can fail on tablets)
    4. if(checkPermission(READ_PHONE_STATE))
    5.     id = context.getSystemService(Context.TElEPHONY_SERVICE).getDeviceId()
    6. else
    7.     id = context.getContentResolver().getString(Secure.ANDROID_ID);
    8. if(!id)
    9.     id = getMacAddress();
    10. id = md5hash(id);

    The original broken algorithm (4.6-)

    Code (pseudo):
    2.  string id;
    3.  // Needs android.permission.READ_PHONE_STATE and a phone like device (can fail on tablets)
    4.  if(!checkPermission(READ_PHONE_STATE))
    5.     id = NULL;
    6.  else {
    7.     id = context.getSystemService(Context.TElEPHONY_SERVICE).getDeviceId()
    8.     if(id == NULL)
    9.         id = context.getContentResolver().getString(Secure.ANDROID_ID);
    10.  }
    11.  if(!id)
    12.     id = getMacAddress();
    13.  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.

    Code (CSharp):
    2. // Hash an input string and return the hash as
    3. // a 32 character hexadecimal string.
    4. static string getMd5Hash(string input)
    5. {
    6.     if (input == "")
    7.         return "";
    8.     MD5CryptoServiceProvider md5Hasher = new MD5CryptoServiceProvider();
    9.     byte[] data = md5Hasher.ComputeHash(Encoding.Default.GetBytes(input));
    10.     StringBuilder sBuilder = new StringBuilder();
    11.     for (int i = 0; i < data.Length; i++)
    12.         sBuilder.Append(data[i].ToString("x2"));
    13.     return sBuilder.ToString();
    14. }
    16. static string generateDeviceUniqueIdentifier(bool oldBehavior)
    17. {
    18.     string id = "";
    19.     AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
    20.     AndroidJavaObject activity = jc.GetStatic<AndroidJavaObject>("currentActivity");
    21.     AndroidJavaClass contextClass = new AndroidJavaClass("android.content.Context");
    22.     string TELEPHONY_SERVICE = contextClass.GetStatic<string>("TELEPHONY_SERVICE");
    23.     AndroidJavaObject telephonyService = activity.Call<AndroidJavaObject>("getSystemService", TELEPHONY_SERVICE);
    24.     bool noPermission = false;
    25.     try
    26.     {
    27.         id = telephonyService.Call<string>("getDeviceId");
    28.     }
    29.     catch (Exception e) {
    30.         noPermission = true;
    31.     }
    32.     if(id == null)
    33.         id = "";
    34.     // <= 4.5 : If there was a permission problem, we would not read Android ID
    35.     // >= 4.6 : If we had permission, we would not read Android ID, even if null or "" was returned
    36.     if((noPermission && !oldBehavior) || (!noPermission && id == "" && oldBehavior))
    37.     {
    38.         AndroidJavaClass settingsSecure = new AndroidJavaClass("android.provider.Settings$Secure");
    39.         string ANDROID_ID = settingsSecure.GetStatic<string>("ANDROID_ID");
    40.         AndroidJavaObject contentResolver = activity.Call<AndroidJavaObject>("getContentResolver");
    41.         id = settingsSecure.CallStatic<string>("getString", contentResolver, ANDROID_ID);
    42.         if(id == null)
    43.             id = "";
    44.     }
    45.     if(id == "")
    46.     {
    47.         string mac = "00000000000000000000000000000000";
    48.         try
    49.         {
    50.             StreamReader reader = new StreamReader("/sys/class/net/wlan0/address");
    51.             mac = reader.ReadLine();
    52.             reader.Close();
    53.         }
    54.         catch (Exception e) {}
    55.         id = mac.Replace(":", "");
    56.     }
    57.     return getMd5Hash(id);
    58. }
  2. tricket


    Feb 10, 2016
    Thanks, jonas.minnberg -- a further question: in the various posts, I keep seeing that the reported identified will change between 5.3 and 5.4. I can't see from your pseudo-code examples why that is -- won't we receive the hash of either (a) the deviceId() value if available, or (b) the ANDROID_ID?

    We're trying to determine how much storing of old value => new value we need to do in our code, so if the result *isn't* going to change from 5.3 to 5.4, that'll save us some headaches. :)

    Also: for Android 6.0, we want to explicitly TURN OFF the requested READ_PHONE_STATE permission (as it's creating a scary "Allow app to make and manage phone calls?" ... does requesting deviceUniqueIdentifier automatically add this, and do we need to strip out this permission via a post-build script, in order to make sure we're not asking for that permission?
  3. gwiazdorrr


    Sep 29, 2014
    Bump. I, too, would like to know. Unique id is useful, but sometimes not at a price of a rather scary dialog.
  4. djarcas


    Nov 15, 2012
    Bump and ditto.
  5. monark


    May 2, 2008
    Getting a bizarre situation with this using U5.6.3
    I have 11 identically coded projects that just use different resources.
    All 11 have been published before and did not exhibit any unexpected permissions issues with U5.3.8
    Now 9 of them are fine and 2 request permissions for the phone.
    All have been updated to the same plugin version - I only use 1 from Prime31
    Before building the manifest files are all identical, just the product name changes
    I do use SystemInfo.deviceUniqueIdentifier, which apparently is fixed, so why would 2 of them have an issue with that now suddenly??
  6. monark


    May 2, 2008
  7. Klik303


    Dec 10, 2016
    Short question. Why hash method is use on "unique identifier"?