Android 10: IMEI no longer available on API 29. Looking for alternatives
Asked Answered
T

5

34

Our client's app main feature is heavily relaying on tracking their clients' devices, they offer products that are bound to the specific phone(not its owner). This was possible using the device imei, but with the privacy changes in Android 10, they made it unreachable. (https://developer.android.com/about/versions/10/privacy/changes).

Android has a documentation about what identifier to use on specific user cases, but non matches our case since we need it to be unique, constant and bound to the device(or at least difficult to change). https://developer.android.com/training/articles/user-data-ids. I'm considering Android ID to be a possible solution, or using the mac address knowing they aren't 100% reliable.

Any thoughts? recommendations? experiences? at this point anything could be an option

Theotokos answered 25/9, 2019 at 17:19 Comment(2)
What solution you have adopted for this problem, Can you please help me on itMitrailleuse
I'm struggling with this as well.Armandinaarmando
A
26

I advice you to read the official blog of the best practice of google to see what the use case match with your specification : https://developer.android.com/training/articles/user-data-ids.html

For me i occcured the same problem about the unicity of android identifiers and i found the only solution is to use the MediaDrm API ( https://android.googlesource.com/platform/frameworks/base/+/android-cts-4.4_r1/media/java/android/media/MediaDrm.java#539 ) which contains a unique device id and can survive even on the factory reset and doesn't need any additional permission on your manifest file.

Here is the couple of code how can we retreive the unique identifier on Android 10 :

import android.media.MediaDrm
import java.security.MessageDigest
import java.util.*

object UniqueDeviceID {

    /**
     * UUID for the Widevine DRM scheme.
     * <p>
     * Widevine is supported on Android devices running Android 4.3 (API Level 18) and up.
     */
    fun getUniqueId(): String? {

        val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
        var wvDrm: MediaDrm? = null
        try {
            wvDrm = MediaDrm(WIDEVINE_UUID)
            val widevineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)
            val md = MessageDigest.getInstance("SHA-256")
            md.update(widevineId)
            return  md.digest().toHexString()
        } catch (e: Exception) {
            //WIDEVINE is not available
            return null
        } finally {
            if (AndroidPlatformUtils.isAndroidTargetPieAndHigher()) {
                wvDrm?.close()
            } else {
                wvDrm?.release()
            }
        }
    }


    fun ByteArray.toHexString() = joinToString("") { "%02x".format(it) }
}
Allman answered 26/11, 2019 at 11:42 Comment(8)
Did you test it on multiple device ?Teratology
Yes,i test it on my test devices : Samsung Note 5 ,Pixel 3Allman
Okay.. great.. :)Teratology
You can add another DRM UUID to failsafe when a exception is raised and retry. val COMMON_PSSH_UUID = UUID(0x1077EFECC0B24D02L, -0x531cc3e1ad1d04b5L) val CLEARKEY_UUID = UUID(-0x1d8e62a7567a4c37L, 0x781AB030AF78D30EL) val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L) val PLAYREADY_UUID = UUID(-0x65fb0f8667bfbd7aL, -0x546d19a41f77a06bL)Unfrock
it is changed after the factory reset.Fassold
@Unfrock did you try how to extract other UUIDs, like COMMON_PSSH_UUIDPromising
@SofienRahmouni "a unique device id and can survive even on the factory reset". According to [@] Vasu 's comment "it is changed after the factory reset". Can you please both elaborate and give some links to documentation about how WIDEVINE_UUID behaves regarding factory reset? ThanksIndicant
@Fassold "it is changed after the factory reset". Can you please elaborate and give some links to documentation about how WIDEVINE_UUID behaves regarding factory reset? ThanksIndicant
S
21

For Java users that are interested in Sofien's solution, I have:

  1. Converted Sofien's code to Java and further simplified;
  2. Extensively tested on Android 10 (API 29), Android 11 (API 30) and previous versions.

1. Code and discussion

@Nullable
String getUniqueID() {
   UUID wideVineUuid = new UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L);
   try {
      MediaDrm wvDrm = new MediaDrm(wideVineUuid);
      byte[] wideVineId = wvDrm.getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID);
      return Arrays.toString(wideVineId);
   } catch (Exception e) {
      // Inspect exception
      return null;
   }
   // Close resources with close() or release() depending on platform API
   // Use ARM on Android P platform or higher, where MediaDrm has the close() method
}

There are two key differences w.r.t. Sofien's code.

  • I am not using the MessageDigest, which results in a simpler code. Moreover, the MessageDigest.update() method applies the SHA-256 hash function to its argument, which introduces an extremely low probability of losing UUID uniqueness. The only drawback of not hashing the UUID is that you don't have a fixed length UUID, which I don't care about in my application.
  • Instead of the Kotlin function toHexString (which has no one-line counterpart in Java) I am using Arrays.toString. This choice is safe because (A) It throws no Exception and (B) it retains a one-to-one correspondence between the wideVineId and its String representation. If you prefer to stick to hex conversion, the Apache Commons Codec library offers a one-line solution.

Of course, these changes result in a different UUID, needless to say that other choices are possible. Notice also that an UUID generated with Arrays.toString takes the form

[92, -72, 76, -100, 26, -86, 121, -57, 81, -83, -81, -26, -26, 3, -49, 97, -24, -86, 17, -106, 25, 102, 55, 37, 47, -5, 33, -78, 34, 121, -58, 109]

So, if you don't want special characters in your UUID you can remove them with String.replaceAll().

2. Tests

I have tested the persistence of the UUID

  • over reinstallation
  • over reinstallation AND reboot

on the following device/OS combinations:

  • Google Pixel 4A / API 30
  • Samsung Galaxy S10 / API 29
  • Samsung Galaxy S9 / API 29
  • Huawei Nexus 6P / API 27 (tested also factory reset)
  • LG V20 / API 27 (tested also factory reset)
  • Asus ZenFone 2 / API 23
  • Samsung Galaxy J5 / API 23
  • LG Nexus 5 / API 23
  • LG K4 / API 22
  • Samsung Galaxy J3 / API 22
  • Samsung Galaxy S4 / API 21

In all of the tests, the targetSdkVersion is 30. More tests (especially on API 29 and 30) are welcome.

Sandman answered 12/2, 2020 at 18:28 Comment(8)
have you also tested after factory reset? is it the same?Obscene
@IuliaBarbu Thank you for your comment. I have now tested the factory reset on one of the listed devices (see updated answer). I could not test factory reset on all devices, since they belong to actual users.Sandman
is there a way to get a shorter value, or how to make the array shorter?Montparnasse
@Montparnasse You could use the hex conversion I have mentioned, instead of the array representation. That would be Hex.encodeHexString(wideVineId), which gives you a 64-character String in the specific case mentioned above (a bit more or less depending on the phone). If you want an even shorter String, you could hash it. The SHA-256 hash mentioned in the answer by @Sofien is not the most concise, you might want to look for more concise hashes, see also this question.Sandman
Thanks, I will check it out!Montparnasse
You can make shorter value by converting the byte array into Base64 String like Base64.encodeToString(WideVineId , Base64.Default)Adversative
"Does WIDEVINE_UUID survives factory reset or not?" Any documentation about that? (I search, but could not find any resource about it)Indicant
Does not survive factory reset on my side... Am I the only one?Indicant
G
3
  1. On a device first boot, a random value is generated and stored. This value is available via Settings.Secure.ANDROID_ID. It’s a 64-bit number that should remain constant for the lifetime of a device. ANDROID_ID seems a good choice for a unique device identifier because it’s available for smartphones and tablets. To retrieve the value, you can use the following code,
String androidId = Settings.Secure.getString(getContentResolver(),
                                             Settings.Secure.ANDROID_ID);

However, the value may change if a factory reset is performed on the device. There is also a known bug with a popular handset from a manufacturer where every instance has the same ANDROID_ID. Clearly, the solution is not 100% reliable.

  1. Use UUID. As the requirement for most of the applications is to identify a particular installation and not a physical device, a good solution to get the unique id for a user if to use UUID class. The following solution has been presented by Reto Meier from Google in a Google I/O presentation,
SharedPreferences sharedPrefs = context.getSharedPreferences(
PREF_UNIQUE_ID, Context.MODE_PRIVATE);
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
Gunsmith answered 25/9, 2019 at 17:34 Comment(9)
Regarding option 2: That's precisely what not longer is working on Android 10, since it now requires the READ_PRIVILEGED_PHONE_STATE permission, which 3rd party apps can't get.Fulfill
Likewise, reading the serial number now also requires READ_PRIVILEGED_PHONE_STATE.Fulfill
@Fulfill I mentioned that the serial number solution is not reliable.Gunsmith
Well, the question is specifically about Android 10, so it would make sense to remove suggestions 2 and 3 since they are no longer usable.Fulfill
Any solution for this issue?Rebak
@MahmoudHeretani As the google restricted to access IMEI using telephony manager in recent policy changes, You only have solutions given in the answer. You no longer can access the IMEI.Gunsmith
Thanks a lot, I reach the same result :( @KiranManiyaRebak
Is #1 a made up stuff? What DeviceInfoProvider you talked about here? Please be specific and stop confusing people. Thank youSolvable
@jerinho thanks for pointing out. It's been long i've posted this answer.Gunsmith
P
0

MEDIADRM API is one can use

//From Exo player

val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
    val id = MediaDrm(WIDEVINE_UUID)
        .getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)
    var encodedString: String = Base64.encodeToString(id,Base64.DEFAULT)
    Log.i("Uniqueid","Uniqueid"+encodedString)
Pentagram answered 8/5, 2020 at 5:44 Comment(0)
A
-4

I have tested it in Nokia phone "the identifier is changed when I reset my phone on factory reset". Did you test it on factory reset?

Amphibolous answered 19/8, 2020 at 12:1 Comment(3)
Your answer do not make sense. Use punctuation and maintain some grammatical structure.Writing any answer that can not even qualify as decent line is appalling.Weathering
I have modified the answer what I mean is the identifier number changed if you make factory reset to the phone do you test it for factory resetAmphibolous
Use proper "punctuation" in your answer and comment.Weathering

© 2022 - 2024 — McMap. All rights reserved.